Using the Azure Files preview with the Storage Client Library

In the first post of this series on the Azure Files preview, I discussed what the feature entailed, and what you can do with it, as well as how to sign up for the preview. In the second post, I showed how to get the PowerShell cmdlets for the preview, and how to use them to manage your share and transfer files to/from your share. In the third post, I showed you how to create a VM in Azure, map the share, and update the share via the VM. In this post, I’m going to show you how to programmatically access your shares, get a list of the files on them, and upload/download files using the Storage Client Library.

To write code to manage your share(s), you need to download the newest Storage Client Library (4.1.0 at the time of this writing). This supports the new Azure Files preview, and has all the APis you need. At this time, the only documentation available for the Storage Client Library is the object model. Intellisense and trial & error are my friends.

I’ve created a Windows Forms project that will create shares, create folders, upload files, download files, list files, and delete files. The application looks like this:

image

I’ll make the project available at the bottom of this blog post.

Setup for accessing the file share

To access the file share, you  need three pieces of information: the Storage Account Name, Storage Account Key, and Share Name.

Here’s how you create the connectionString using the storage account name and key:

private string connectionString
{
    get
    {
        return @"DefaultEndpointsProtocol=https;AccountName=" + StorageAccountName +
            ";AccountKey=" + StorageAccountKey;
    }
}

So now we’re ready to write the code block to set everything up we need to execute all of our commands. The first thing you need to do is get an instance of the CloudStorageAccount.

CloudStorageAccount cloudStorageAccount = CloudStorageAccount.Parse(connectionString);

Next, we need to get an instance of the CloudFileClient that will be used to execute requests against the File service.

CloudFileClient cloudFileClient = cloudStorageAccount.CreateCloudFileClient();

Now get a reference to the share using the CloudFileClient and the share name. I’m also going to call CreateIfNotExists() so if the share isn’t already there, it will create it. This will only create it the first time, but keeps you from having to remember to create a share every time you need a new one that you’re going to access programmatically.

CloudFileShare cloudFileShare = cloudFileClient.GetShareReference(ShareName);
cloudFileShare.CreateIfNotExists();

I want to use the same code to process files in the root directory and in the subfolders, so I have some variables that I’m setting, such as writeToRoot and fileDirectory to make this simpler. Depending on writeToRoot, fileDirectory will be null or will point to the subfolder.

So let’s set up the reference to the root directory of the share first. Then I’ll set writeToRoot that tells me if I’m going to be using the root directory or a subfolder.

CloudFileDirectory rootDirectory = cloudFileShare.GetRootDirectoryReference();
writeToRoot = string.IsNullOrWhiteSpace(FolderPath);

Now I’m going to set up the fileDirectory that will point to the subfolder if applicable.

CloudFileDirectory rootDirectory = cloudFileShare.GetRootDirectoryReference();
writeToRoot = string.IsNullOrWhiteSpace(FolderPath);

CloudFileDirectory fileDirectory;
if (writeToRoot)
{
    fileDirectory = null;
}
    else 
{
    fileDirectory = rootDirectory.GetDirectoryReference(FolderPath);
    fileDirectory.CreateIfNotExists();
}

And last but not least, I’ll set up directoryToUse, which is a CloudFileDirectory object, to point at either the root or the subfolder, depending on which one we’re targeting.

directoryToUse = writeToRoot ? rootDirectory : fileDirectory;

I’ve put this in a method called SetUpFileShare() that I will execute each time I want to execute a command against the share. I’m doing this because the user of the application can change the criteria such as storage account at any time, and I’m providing those textbox values when I instantiate the AzureFiles object.

Upload files to the share

On my screen, I have a place for local directory (and a browse button). When you click Upload, it uploads all the files in that directory. It is not recursive – it won’t pick up subfolders. (Maybe later.) If the folder is blank, it will upload them to the root directory; if it is not blank, it will upload them to the folder.

I discovered there are a couple of ways to upload files to a share. Here’s one in which you create the URL to the file and execute the upload request with the storage account credentials.

string stringUri = @"https://" + StorageAccountName + ".file.core.windows.net/" + ShareName + "/";
if (!writeToRoot)
    stringUri += FolderPath + @"/";
stringUri += fileNameOnly;

Uri theUri = new Uri(stringUri);
CloudFile cloudFile = new CloudFile(theUri, Credentials);
FileInfo fi = new FileInfo(fileList[i]);
long fileSize = fi.Length;
cloudFile.Create(fileSize);

The Credentials object is created this way:

public Microsoft.WindowsAzure.Storage.Auth.StorageCredentials Credentials
{
  get
  {
    return 
      new Microsoft.WindowsAzure.Storage.Auth.StorageCredentials(StorageAccountName, StorageAccountKey);
  }
}

This works okay, but you have to call the FileInfo class to get the file size, which is one more step than you need. However, if this is being executed by some code that doesn’t know how to format the connection string, or if you have a Uri from another source and you’re not creating it yourself, this could be the way to go.

Here’s the second way:

CloudFile cloudFile2 = directoryToUse.GetFileReference(fileNameOnly);
cloudFile2.UploadFromFile(fileList[i], System.IO.FileMode.Open);

Note that it has run through SetUpFileShare(), which sets directoryToUse and has created the CloudFileClient with the connection string. This is (obviously) much shorter and to the point.

One note: in the method call for UploadFromFile, the FileMode specified is for the local file, not the file in the cloud.

Download files from a folder on the share

Downloading files is very similar to uploading them. In the test application, it downloads the files that are selected in the ListBox. In the interest of simplicity, I’m downloading them to a folder called “Downloads” that I create in the local folder selected for uploads. For each file, this is the command that I’m executing. Note the use of the CloudFileDirectory “directoryToUse”, and remember that SetUpAzureFiles() has been run before this is called to set all of the references accordingly.

CloudFile cloudFile2 = directoryToUse.GetFileReference(fileNameOnly);
cloudFile2.DownloadToFile(localPath, FileMode.Create);

As with the uploads, tie FileMode specified is for the local file. FileMode.Create means it will write the file if it doesn’t exist, and it will overwrite it if it does.

Get a list of files in a folder on the share

First, I’ve set up a class for the items in the listbox on my form. ListBox contains a list of ListBoxFileItems.

public enum ListBoxFileItemType { File, Directory, Other };

public class ListBoxFileItem
{
    public string FileName { get; set; }

    public ListBoxFileItemType FileItemType { get; set; }

    public override string ToString()
    {
        return FileName;
    }
}

Note the field “FileItemType”. To get a list of files, you get an IENumerable<IListFileItem> from the method call to directory.ListFilesAndDirectories(). This returns both files and directories in the requested path. There is no property that tells you the difference between the files and the directories. To tell the difference, I cast the item as a CloudFile, and if the result was null, I cast it as a directory and checked that for null. It’s probably always going to be one or the other, but I never assume, so I allowed a type of “other”, in case they later expand the possibilities.

So this is how I’m retrieving the list of files or directories, and putting them into my List<ListBoxFileItem> to be returned to the screen and loaded.

public bool LoadListOfFiles(out List<ListBoxFileItem> listOfFiles)
{
    bool success = true;
    listOfFiles = new List<ListBoxFileItem>();
    foreach (IListFileItem fileItem in directoryToUse.ListFilesAndDirectories())
    {
        try
        {
            ListBoxFileItem fi = new ListBoxFileItem();
            fi.FileName = Path.GetFileName(fileItem.Uri.LocalPath);
            fi.FileItemType = GetFileItemType(fileItem);
            listOfFiles.Add(fi);
        }
        catch (Exception ex)
        {
            success = false;
            System.Diagnostics.Debug.Print("Exception thrown loading list of files = {0}", 
                ex.ToString());
        }
        //if any file fails to upload, stop looping
        if (!success)
            break;
    }
    return success;
}

And this is how I determine ListBoxFileItemType:

private ListBoxFileItemType GetFileItemType(IListFileItem fileItem)
{
    ListBoxFileItemType thisType;
    CloudFile fileCheck = fileItem as CloudFile;
    //cast it to a file and see if it's null. If it is, check to see if it's a directory. 
    if (fileCheck == null)
    {
        //check and see if it's a directory
        //you can probably assume, but since I haven't found it documented, 
        //  I'm going to check it anyway
        CloudFileDirectory dirCheck = fileItem as CloudFileDirectory;
        if (dirCheck == null)
        {
            thisType = ListBoxFileItemType.Other;
        }
        else
        {
            thisType = ListBoxFileItemType.Directory;
        }
    }
    else
    {
        thisType = ListBoxFileItemType.File;
    }
    return thisType;
}

So this sends back a list to the UI, and it has the types, so you can change ToString() to show the type if you want to.

Delete files from the share

It’s relatively easy to delete files from the share. In my case, the UI is sending a list of ListBoxFileItem, and I’ve already called SetUpFileShare() to set the appropriate references.

Because I have a list of ListBoxFileFileItems, I have the type (file or directory) for each object to be deleted. This is how I delete one entry (oneItem, which is a ListBoxFileItemType). Note that I am using the CloudFileDirectory object “directoryToUse”, which I set in SetUpFileShare() to point to either the root or a subfolder, depending on the user’s selection.

Note that you can not delete a directory that has files in it. It will throw an StorageException. I’m catching that and sending back the appropriate error message.

if (oneItem.FileItemType == ListBoxFileItemType.Directory)
{
   try
    {
        CloudFileDirectory cloudDir = directoryToUse.GetDirectoryReference(oneItem.FileName);
        cloudDir.Delete();
    }
    catch (Microsoft.WindowsAzure.Storage.StorageException ex)
    {
        errorMessage =
            string.Format("Could not delete directory {0}; it has files in it.", oneItem.FileName);
    }
    catch (Exception ex)
    {
        errorMessage = string.Format("Could not delete directory {0}. Ex = {1}", 
          oneItem.FileName, ex.ToString());
    }
}
else if (oneItem.FileItemType == ListBoxFileItemType.File)
{
    CloudFile cloudFile = directoryToUse.GetFileReference(oneItem.FileName);
    cloudFile.Delete();
}

Download the completed project

The completed project is here. I did not include the packages, so you need to have Visual Studio set to retrieve the packages when you build. You can change that in Tools/Options/NuGet package manager/General. The project was created using VS2013/SP2.

Summary

In this article, I’ve shown you how to use the Storage Client Library to get reference to a share and create it if it doesn’t exist, upload and download files between the local computer and your share, list the files in a folder on the share, and delete files in a folder on the share. Much of the Storage Client Library code is similar to that used when access Blob Storage, so if there’s something else you need to do, Intellisense and trial & error can be your best friends, too!

Tags: ,

Leave a comment