Windows Azure Walkthrough: Blob Storage Sample

Please see the updated post for November 2009 and later.

This walkthrough covers what I found to be the simplest way to get a sample up and running on Windows Azure that uses the Blob Storage service. It is not trying to be comprehensive or trying to dive deep in the technology, it just serves as an introduction to how the Windows Azure Blob Storage Service works.

Please take the Quick Lap Around the Tools before doing this walkthrough.

Note: The code for this walkthrough is attached, you will still have to add and reference the Common and StorageClient projects from the Windows Azure SDK.

After you have completed this walkthrough, you will have a Web Role that is a simple ASP.Net Web Application that shows a list of files that are stored and can be downloaded from Blob Storage. You can use the Web Role to add files to Blob storage and make them available in the list.

image

Blob Concepts

Each storage account has access to blob storage. For each account there can be 0..n containers. Each container contains the actual blobs, which is a raw byte array. Containers can be public or private. In public containers, the URLs to the blobs can be accessed over the internet while in a private container, only the account holder can access those blob URLs.

Each Blob can have a set of metadata set as a NameValueCollection of strings.

image

Creating the Cloud Service Project

1. Start Visual Studio as an administrator

2. Create a new project: File à New à Project

3. Select “Web Cloud Service”. This will create the Cloud Service Project and an ASP.Net Web Role

image

4. Find the installation location of the Windows Azure SDK. By default this will be: C:\Program Files\Windows Azure SDK\v1.0

a. Find the file named “samples.zip”

b. Unzip this to a writeable location

5. From the samples you just unzipped, add the StorageClient\Lib\StorageClient.csproj and HelloFabric\Common\Common.csproj to your solution by right-clicking on the solution in Solution Explorer and selecting Add –> Existing Project.

image

a. Common and StorageClient and libraries that are currently distributed as samples that provide functionality to help you build Cloud Applications. Common adds Access to settings and logging while StorageClient provides helpers for using the storage services.

6. From your Web Role, add references to the Common and StorageClient projects you just added.

image

7. Add the code to connect to the Blob Storage Service in Default.aspx.cs.

a. This code gets account information from the Service Configuration file and uses that to create a BlobStorage instance.

b. From the BlobStorage instance, a container is created with a name taken from the Service Configuration. The name used for the container is restricted to valid DNS names.

c. The container is made public so that the URLs for the blobs are accessible from the internet.

 using Microsoft.Samples.ServiceHosting.StorageClient;

 namespace DownloadSite_WebRole
{
    public partial class _Default : System.Web.UI.Page
    {
        private BlobContainer _Container = null;

        protected void Page_Load(object sender, EventArgs e)
        {
            try
            {
                // Get the configuration from the cscfg file
                StorageAccountInfo accountInfo = StorageAccountInfo.GetDefaultBlobStorageAccountFromConfiguration();

                // Container names have the same restrictions as DNS names
                BlobStorage blobStorage = BlobStorage.Create(accountInfo);
                _Container = blobStorage.GetBlobContainer(RoleManager.GetConfigurationSetting("ContainerName"));

                // returns false if the container already exists, ignore for now
                // Make the container public so that we can hit the URLs from the web
                _Container.CreateContainer(new NameValueCollection(), ContainerAccessControl.Public);
                UpdateFileList();
            }
            catch (WebException webExcept)
            {
            }
            catch (Exception ex)
            {
            }
        }

8. In order for the StorageAccountInfo class to find the configuration settings, open up ServiceDefinition.csdef and add the following to <WebRole/>. These define the settings.

     <ConfigurationSettings>
      <Setting name="AccountName"/>
      <Setting name="AccountSharedKey"/>
      <Setting name="BlobStorageEndpoint"/>
      <Setting name="ContainerName"/>
    </ConfigurationSettings>

9. Likewise, add the actual local development values to the ServiceConfiguration.cscfg file. Note that the settings between both files have to match exactly otherwise your Cloud Service will not run.

 <ConfigurationSettings>
  <Setting name="AccountName" value="devstoreaccount1"/>
  <Setting name="AccountSharedKey" value="Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw=="/>
  <Setting name="BlobStorageEndpoint" value="https://127.0.0.1:10000/"/>

  <!-- Container, lower case letters only-->
  <Setting name="ContainerName" value="downloadsite"/>
</ConfigurationSettings>

10. When you run in the Cloud, the AccountName and AccountSharedKey will be set the values you will get back from the Portal for your account. The BlobStorageEndpoint will be set the URL for the Blob Storage Service: https://blob.core.windows.net

a. Because these are set in the ServiceConfiguration.cscfg file, these values can be updated even after deploying to the cloud by uploading a new Service Configuration.

11. For the local development case, the local host and port 10000 (by default) will be used as the Blob Storage Endpoint. The AccountName and AccountSharedKey are hard coded to a value that the Development Storage service is looking for (it’s the same for all 3, Table, Blob and Queue services).

12. Next open up Default.aspx and add the code for the UI. The UI consists of:

a. a GridView at the top

b. Label and FileUpload control

c. 2 Label and TextBox pairs (File Name and Submitter)

d. Field validators to ensure that all of the fields are filled out before the file is uploaded.

Add the following between the template generated <div></div> elements:

         <asp:GridView ID="fileView"
        AutoGenerateColumns="false" DataKeyNames="BlobName"
        Runat="server" onrowcommand="RowCommandHandler">
        <Columns>
            <asp:ButtonField Text="Delete" CommandName="DeleteItem"/>
            <asp:HyperLinkField
                HeaderText="Link"
                DataTextField="FileName"
                DataNavigateUrlFields="FileUri" />
            <asp:BoundField DataField="Submitter" HeaderText="Submitted by" />
        </Columns>
    </asp:GridView>

    <br />
    <asp:Label id="filePathLabel" 
        Text="File Path:" 
        AssociatedControlID="fileUploadControl"
        Runat="server" />
    <asp:FileUpload ID="fileUploadControl" runat="server"/>
    <asp:requiredfieldvalidator id="filUploadValidator"
      controltovalidate="fileUploadControl"
      validationgroup="fileInfoGroup"
      errormessage="Select a File"
      runat="Server">
    </asp:requiredfieldvalidator>
    <br />
    <asp:Label
        id="fileNameLabel"
        Text="File Name:"
        AssociatedControlID="fileNameBox"
        Runat="server" />
    <asp:TextBox
        id="fileNameBox"
        Runat="server" />
    <asp:requiredfieldvalidator id="fileNameValidator"
      controltovalidate="fileNameBox"
      validationgroup="fileInfoGroup"
      errormessage="Enter the File Name"
      runat="Server">
    </asp:requiredfieldvalidator>
    <br />
    <asp:Label
        id="submitterLabel"
        Text="Submitter:"
        AssociatedControlID="submitterBox"
        Runat="server" />
    <asp:TextBox
        id="submitterBox"
        Runat="server" />
    <asp:requiredfieldvalidator id="submitterValidator"
      controltovalidate="submitterBox"
      validationgroup="fileInfoGroup"
      errormessage="Enter the Submitter Name"
      runat="Server">
    </asp:requiredfieldvalidator>
    <br />
    <asp:Button
        id="insertButton"
        Text="Submit"
        causesvalidation="true"
        validationgroup="fileInfoGroup"
        Runat="server" onclick="insertButton_Click"/>
    <br />
    <br />
    <asp:Label id="statusMessage" runat="server"/>

13. If you now switch to design view, you will see:

image

14. In order to simplify the databinding to the UI, lets add a FileEntry class that contains the metadata for each blob.

 public class FileEntry
{
    public FileEntry(string blobName, Uri fileAddress, string name, string user)
    {
        BlobName = blobName;
        FileUri = fileAddress;
        FileName = name;
        Submitter = user;
    }

    public Uri FileUri
    {
        get;
        set;
    }

    public string BlobName
    {
        get;
        set;
    }

    public string FileName
    {
        get;
        set;
    }

    public string Submitter
    {
        get;
        set;
    }
}

15. Back to Default.aspx.cs, let’s add code to populate the GridView. One row for each blob found in storage. A FileEntry for each blob found in the container is create and put in a List.

a. Note that in order to get the metadata from a blob, you need to call BlobContainer.GetBlobProperties(), the list of blobs returned from BlobContainer.ListBlobs() does not contain the metadata.

 using System.Collections.Generic;
using System.Collections.Specialized;

 private void UpdateFileList()
{
    IEnumerable<object> blobs = _Container.ListBlobs(string.Empty, false);
    List<FileEntry> filesList = new List<FileEntry>();

    foreach (object o in blobs)
    {
        BlobProperties bp = o as BlobProperties;
        if (bp != null)
        {
            BlobProperties p = _Container.GetBlobProperties(bp.Name);
            NameValueCollection fileEntryProperties = p.Metadata;
            filesList.Add(new FileEntry(p.Name, bp.Uri, fileEntryProperties["FileName"], fileEntryProperties["Submitter"]));
        }
    }

    fileView.DataSource = filesList;
    fileView.DataBind();
}

16. Add the code to upload a file to blob storage.

a. Create a unique blob name by using a Guid. Appended the existing file extension.

b. Add metadata

i. For the file name (friendly name to show instead of the blob name or URL)

ii. For the Submitter

c. Add the bytes and create the Blob

d. The UI is refreshed after this operation

 protected void insertButton_Click(object sender, EventArgs e)
{
    // Make a unique blob name
    string extension = System.IO.Path.GetExtension(fileUploadControl.FileName);
    BlobProperties properties = new BlobProperties(Guid.NewGuid().ToString() + extension);

    // Create metadata to be associated with the blob
    NameValueCollection metadata = new NameValueCollection();
    metadata["FileName"] = fileNameBox.Text;
    metadata["Submitter"] = submitterBox.Text;

    properties.Metadata = metadata;
    properties.ContentType = fileUploadControl.PostedFile.ContentType;

    // Create the blob
    BlobContents fileBlob = new BlobContents(fileUploadControl.FileBytes);
    _Container.CreateBlob(properties, fileBlob, true);

    // Update the UI
    UpdateFileList();
    fileNameBox.Text = "";
    statusMessage.Text = "";
}

17. Add code to delete the blob. This is as simple as calling BlobContainer.DeleteBlob() passing in the blob name. In this case, it is the Guid + file extension generated during the upload.

 protected void RowCommandHandler(object sender, GridViewCommandEventArgs e)
{
    if (e.CommandName == "DeleteItem")
    {
        int index = Convert.ToInt32(e.CommandArgument);
        string blobName = (string)fileView.DataKeys[index].Value;

        if (_Container.DoesBlobExist(blobName))
        {
            _Container.DeleteBlob(blobName);
        }
    }
    UpdateFileList();
}

18. Finally, lets just round out some of the error handling on Page_Load(). Right after _Container.CreateContainer() lets update the UI and properly catch any exceptions that could get thrown.

 using System.Net;

     _Container.CreateContainer(new NameValueCollection(), ContainerAccessControl.Public);
    UpdateFileList();
}
catch (WebException webExcept)
{
    if (webExcept.Status == WebExceptionStatus.ConnectFailure)
    {
        statusMessage.Text = "Failed to connect to the Blob Storage Service, make sure it is running: " + webExcept.Message;
    }
    else
    {
        statusMessage.Text = "Error creating container: " + webExcept.Message;
    }
}
catch (Exception ex)
{
    statusMessage.Text = "Error creating container: " + ex.Message;
}

19. Build and hit F5 to run the application.

a. Notice that the Development Fabric and the Development Storage startup on your behalf and will continue to run until you close them.

b. Note that the FileUpload control has a file size limit. You can modify it by changing the httpRuntime maxRequestLength attribute in web.config.

image

Please see the Deploying a Cloud Service to learn how to modify the configuration of this Cloud Service to make it run on Windows Azure.

DownloadSite.zip