Mon, 12 Apr 2010

Uploading Windows Azure Blobs From Silverlight – Part 2: Enabling Cross-Domain Access to Blobs

In this series of blog posts, I’ll show you how to use Silverlight to upload blobs directly to Windows Azure storage. At the end of the series, we’ll have a complete solution that supports uploading multiple files of arbitrary size. You can try out the finished sample at http://slupload.cloudapp.net.

Part 2: Enabling Cross-Domain Access to Blobs

In Part 1: Shared Access Signatures, we saw how to construct a Shared Access Signature that can be used by a client to access blob storage without needing to know the account shared key. For most clients, a Shared Access Signature is all that’s needed to enable access to read and write Windows Azure blobs. However, in the case of Silverlight, there are restrictions on what kind of cross-domain access is allowed. In this post, we’ll see how to enable full access to blob storage through Silverlight.

ClientAccessPolicy.xml

When a Silverlight application makes a cross-domain call (other than those that are allowed by default), it first fetches a file called ClientAccessPolicy.xml from the root of the target server. In our case, our URLs look like http://slupload.blob.core.windows.net/…, so Silverlight will try to access the policy file at http://slupload.blob.core.windows.net/ClientAccessPolicy.xml. We’ll need to make that the correct policy file is served from that location.

Every blob in Windows Azure storage lives within a container, but there’s a special root container which lets us store blobs directly off the root of the domain. This is where we’ll put our ClientAccessPolicy.xml file. The following code creates a publicly readable root container and creates a blob named ClientAccessPolicy.xml within it:

private void CreateSilverlightPolicy(CloudBlobClient blobs)
{
    blobs.GetContainerReference("$root").CreateIfNotExist();
    blobs.GetContainerReference("$root").SetPermissions(
        new BlobContainerPermissions() {
            PublicAccess = BlobContainerPublicAccessType.Blob
        });
    var blob = blobs.GetBlobReference("clientaccesspolicy.xml");
    blob.Properties.ContentType = "text/xml";
    blob.UploadText(@"<?xml version=""1.0"" encoding=""utf-8""?>
            <access-policy>
              <cross-domain-access>
                <policy>
                  <allow-from http-methods=""*"" http-request-headers=""*"">
                    <domain uri=""*"" />
                    <domain uri=""http://*"" />
                  </allow-from>
                  <grant-to>
                    <resource path=""/"" include-subpaths=""true"" />
                  </grant-to>
                </policy>
              </cross-domain-access>
            </access-policy>");
}

There are a few important things to note about the ClientAccessPolicy.xml we create:

  1. The blob has a content type of text/xml. In my testing, I found that in some browsers, Silverlight wouldn’t accept a ClientAccessPolicy.xml with the wrong content type.
  2. We’re using allow-from http-methods to add support for HTTP verbs other than GET and POST.
  3. We’re using allow-from http-request-headers to allow custom headers. This allows us to support things like the x-ms-version header.
  4. We’re using domain uri=”http://*” to allow non-SSL clients to access blob storage over HTTPS.

You can read more about the format and semantics of ClientAccessPolicy.xml in the Silverlight documentation on MSDN.

Using client HTTP handling

By default, Silverlight uses the browser HTTP stack to make web requests, which limits you to the GET and POST verbs. Starting with Silverlight 3, however, a client HTTP stack is provided which can use other verbs (such as PUT, which we’ll use to create blobs).

To ensure that we’re using the client HTTP stack, we’ll construct our web requests by using the WebRequestCreator.ClientHttp.Client() method. To read more about how to choose the right HTTP stack in your code, see the Silverlight documentation on MSDN.

Creating a blob in Silverlight

The .NET storage client library that comes with the Windows Azure SDK won’t work in Silverlight (where the HTTP stack is a bit different), so we’ll need to write our own method to write a blob. Fortunately, the REST API for blob storage is quite simple. The following Silverlight code stores the text “Hello World” in a blob (assuming a URI which already has the appropriate Shared Access Signature attached to it):

var webRequest = (HttpWebRequest)WebRequestCreator.ClientHttp.Create(sasBlobUri);
webRequest.Method = "PUT";
webRequest.ContentType = "text/plain";
webRequest.BeginGetRequestStream((ar) =>
{
    using (var writer = new StreamWriter(webRequest.EndGetRequestStream(ar)))
    {
        writer.WriteLine("Hello, World!");
    }
    webRequest.BeginGetResponse((ar2) =>
        {
            ((HttpWebRequest)ar2.AsyncState).EndGetResponse(ar2);
        }, null);
}, null);

That code is a bit condensed… you may prefer to write different methods for each step of the web request, rather than using anonymous methods.

Tips to remember

  1. Give your ClientAccessPolicy.xml the right content type, put it in the publicly readable root container.
  2. Allow the right HTTP verbs and the right HTTP headers.
  3. Use the client HTTP stack in order to get access to all the HTTP verbs.

Read the Whole Series

Read the entire three-part series to see how http://slupload.cloudapp.net was developed:

You can download the full source code for the series as a Visual Studio 2010 RC solution here: http://cdn.blog.smarx.com/files/SilverlightUpload_source.zip.