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:
- The blob has a content type of
text/xml
. In my testing, I found that in some browsers, Silverlight wouldn’t accept aClientAccessPolicy.xml
with the wrong content type. - We’re using
allow-from http-methods
to add support for HTTP verbs other thanGET
andPOST
. - We’re using
allow-from http-request-headers
to allow custom headers. This allows us to support things like thex-ms-version
header. - 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
- Give your
ClientAccessPolicy.xml
the right content type, put it in the publicly readable root container. - Allow the right HTTP verbs and the right HTTP headers.
- 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:
- Part 1 – Shared Access Signatures
- Part 2 – Enabling Cross-Domain Access to Blobs
- Part 3 – Handling Big Files by Using the Block APIs
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.