At next week's TechDays conference in Antwerp/Belgium, I wanted to show an example of how an Azure worker role can update data in a user's Live Mesh Desktop. Instead of doing it in the common "demo" way of simply supplying a hardcoded username/password combination (or, in a similar vain, by simple storing the user's Mesh username/password in a database), I wanted to make this as real as possible.
Reality of course means delegated authentication. Now, there are a few things harder than getting this to work for the first time, but I'd be hard pressed to name a lot of them. To make a long story short, it seems that, currently, delegated authentication is only possible for Mesh Applications (web sites which run directly inside the users mesh ... think "Facebook App" for Mash). But that's not what I wanted ... I wanted to asynchronously, update data in the Mesh at a time when the user is not online.
Turns out that this is absolutely possible: you can simply act as if your external web site is a mesh application by registering for a "new mesh application" (in the azure portal) and sending the given AppID to the Live's delegation authentication URL when your external web site needs the user's consent to interact with Mesh data. This will however - behind the scenes - also cause your application to be installed in the user's Mesh Desktop so that I ended up with an "Empty" application on my desk. But that's something I can absolutely live with.
What I can't live with is something I noticed a few hours later. I planned to deploy the part of my application which uploads a file to the user's Mesh into Azure. For my first tests, I've simply used a console application which references Microsoft.LiveFX.Client, Microsoft.LiveFX.ResourceModel and Microsoft.Web from the Live Framework SDK. I've created a folder called "DemoFolder" in my Mesh desktop and ran code similar to the following (which worked perfectly well in my console application):
static void UploadWithAPI()
{
string token = "... delegation token I've received from Live ....";
LiveOperatingEnvironment loe = new LiveOperatingEnvironment();
Uri serviceUrl = new Uri("https://user-ctp.windows.net/V0.1");
Console.WriteLine("Connecting");
loe.Connect(token, AuthenticationTokenType.DelegatedAuthToken,
serviceUrl, new LiveItemAccessOptions(true));
Console.WriteLine("Connected");
var demoFolder =
loe.Mesh.MeshObjects.Entries.First(p => p.Resource.Title == "DemoFolder");
string path = @"C:\images\";
string filename = "IMG_3957.jpg";
var feed = demoFolder.DataFeeds.Entries.First(p => true);
using (FileStream fs = File.OpenRead(path + filename))
{
feed.DataEntries.Add(fs, filename);
}
Console.WriteLine("File added");
}
Nice and short. Even very understandable for a non-REST person like myself. Only problem: It won't work in Azure. You'll get a SecurityException telling you that the assembly does not allow partically trusted callers. I think that this is simply an oversight (to forget APTCA) or at least I don't fully understand the rationale. But the end-result is absolutely the same: I can't run this code in Azure.
Fortunately enough, Live Mesh's base API is actually HTTP/REST-based and the .NET API is just a small wrapper on top of it. So now I just needed to find out how to pass my delegated authentication token to the Live Mesh service. I've googled quite a bit (and used Fiddler ... thank god for SSL interception!) before I found that the missing HTTP header was called AppDelegationToken. Now I could remove the reference to the client DLLs and go about my business in a very low-level way:
static void UploadWithREST()
{
string token = "... delegation token I've received from Live ....";
string path = @"C:\images\";
string filename = "IMG_3957.jpg";
string serviceUrl = "https://user-ctp.windows.net/V0.1/";
string folderName = "DemoFolder";
string relativeUrl = GetRelativeUrlForFolder(token, serviceUrl, folderName);
PostToLive(serviceUrl + relativeUrl, token, path, filename, "image/jpeg");
}
// return the URL for a folder's MediaResources based on the folder's name
private static string GetRelativeUrlForFolder(string token, string baseUrl, string folderName)
{
string relativeUrl = "Mesh/MeshObjects?$filter=(Title%20eq%20'" + folderName + "')&$expand=DataFeeds";
XmlDocument doc = GetLiveResponse(baseUrl + relativeUrl, token);
XmlNamespaceManager mgr = new XmlNamespaceManager(doc.NameTable);
mgr.AddNamespace("atom", "http://www.w3.org/2005/Atom");
mgr.AddNamespace("win", "http://user.windows.net");
XmlNode nod = doc.SelectSingleNode("/atom:feed/atom:entry/atom:link[@rel='LiveFX/DataFeeds']" +
"/win:Inline/atom:feed/atom:entry/atom:link[@rel=" +
"'LiveFX/MediaResources']", mgr);
relativeUrl = nod.Attributes["href"].Value;
return relativeUrl;
}
// execute a simple GET request and return the XmlDocument
static XmlDocument GetLiveResponse(string url, string token)
{
HttpWebRequest req = (HttpWebRequest)HttpWebRequest.Create(url);
req.Headers.Add("AppDelegationToken", token);
req.Method = "GET";
XmlDocument doc = new XmlDocument();
using (HttpWebResponse res = (HttpWebResponse) req.GetResponse())
{
using (Stream st = res.GetResponseStream())
{
doc.Load(st);
}
}
return doc;
}
// POST a file to the Live Mesh
static void PostToLive(string url, string token, string path, string filename, string contentType)
{
HttpWebRequest req = (HttpWebRequest)HttpWebRequest.Create(url);
req.Headers.Add("AppDelegationToken", token);
req.ContentType = "image/jpeg";
req.Headers.Add("Slug", filename);
req.Method = "POST";
using (Stream st = req.GetRequestStream())
{
byte[] buf = File.ReadAllBytes(path + filename);
st.Write(buf, 0, buf.Length);
}
using (HttpWebResponse resp = (HttpWebResponse)req.GetResponse())
{
// not really needed ...
}
}
I have to admit that I didn't yet try this in Azure, but I think that it should work ...
You can get around the security issue by applying the enableNativeCodeExecution attribtute in your .csdef file. eg. WebRole name=Web enableNativeCodeExecution=true
Posted by: Scott | 04/26/2009 at 03:48 PM