The Live Framework (aka LiveFX), part of the Azure Services Platform, has been available for almost two weeks now. In that time one of the least talked about features within LiveFX has been Resource Scripts however (perhaps because of my SQL Server background) its one of the topics that I find the most interesting and potentially most powerful.
Put simply:
Resource Scripts are a collection of RESTful operations, governed by procedural workflow, that are to be executed against the Live Operating Environment either on demand from an HTTP client, by a scheduler or triggered by another POST/PUT/DELETE operation.
If I replace the words in bold with terminology that is pertinent to SQL Server then this becomes
Stored Procedures are a collection of T-SQL statements, governed by procedural workflow, that are to be executed against a SQL Server instance either on demand from a T-SQL client, by SQL Server Agent or triggered by another INSERT/UPDATE/DELETE operation.
You can clearly see that the two are analogous to each other and gives further credence to my assertion that database pros are relevant on the RESTful web.
In this blog entry I’m going to demonstrate a simple example of what can be achieved using Resource Scripts and I’ll provide the code along with it. This demo will:
- Prompt the user for a Twitter username and a number, X
- Fetch the last X tweets by that Twitter user
- Create a Live Mesh folder
- Create an XML file in that Mesh folder for each tweet which contains all the metadata about that tweet
I’ll examine the pertinent parts of the code a bit at a time so as to explain what’s going on.This won’t include the code that goes out and gets list of tweets because that’s not really relevant to the subject of this blog entry but I’ll post the full code listing at the end so you can see that code if you want to.
First of all we create a MeshObjectResource object that we will eventually add into the Live Operating Environment (LOE). We want this object to be a folder that shows up in the web experience (which eventually will be http://www.mesh.com) so we set its type to be LiveMeshFolder and we give it an appropriate name:
//Create a MeshObject
string moName = twitterusername + "_tweets_" + System.DateTime.Now.ToString("yyyyMMddHHmmss");
MeshObjectResource mo = new MeshObjectResource(moName);
mo.Type = "LiveMeshFolder";
|
Next we need a DataFeedResource. All data within a MeshObject is organised by feeds so we have to have at least one. If we want the files contained within the feed to be displayed in the web experience then the type of this feed needs to be LiveMeshFiles:
//Create a feed in that MeshObject
DataFeedResource df = new DataFeedResource("TweetFeed");
df.Type = "LiveMeshFiles";
|
Now comes the interesting bit. A Resource script is actually a collection of constituent objects called Statements. There are many different types of statements (none of which I’m going to discuss in any detail here) but essentially what we need to do is create a Statement for each resource that we are going to create within the LOE. We nest those resource creation statements inside an outer control-flow statement called a SequenceStatement that determines the order in which the resources are created.
Firstly we declare a list in which to hold our statements and then add the Statement.CreateResource<T>() statements that will, when we execute the script, push the folder and feed that we created earlier up into the LOE:
//Create a List of statements that need to get executed
// and add the MeshObject and feed creation statements
List<Statement> childStatements = new List<Statement>();
Uri meshObjsUri = new Uri(uriRoot + "/V0.1/Mesh/MeshObjects");
childStatements.Add(Statement.CreateResource<MeshObjectResource>(
"moHandle", meshObjsUri, mo
));
childStatements.Add(Statement.CreateResource<DataFeedResource>(
"feedHandle", null, df, Statement.Bind(
"CollectionUrl", "moHandle", "Response.DataFeedsLink")
));
|
Notice that we use Statement.Bind to bind the DataFeedResource to the MeshObjectResource using the handle (“moHandle”) that we assigned to the MeshObjectResource when we created it. That handle is available to later Statements in the same script because, remember, these statements will be executed in a single atomic script.
The next step is to loop over our list of tweets and declare a Statement.CreateMediaResource<T>() statement for each one of them. Notice again how we are binding to the feed that we created earlier using the handle (“feedHandle”) that we assigned to it when it got created:
//Loop over the list of tweet IDs and create a MediaResource Statement for each one
foreach (var tweetID in tweetsIDs)
{
Uri tweet = new Uri(String.Format(@"http://twitter.com/statuses/show/{0}.xml", tweetsIDs[0]));
childStatements.Add(Statement.CreateMediaResource<DataEntryResource>(
"tweet" + tweetID, null, tweet, Statement.Bind(
"CollectionUrl", "feedHandle", "Response.MediaResourcesLink")
));
}
|
At this stage we have all the statements that we need so last step is to compile them into a script and execute it:
//Convert list of Statements into a single atomic statement and compile
SequenceStatement scriptSource = Statement.Sequence(childStatements.ToArray());
ResourceScript<SequenceStatement> compiledScript = scriptSource.Compile();
//Execute the script!!
NetworkCredential creds = new NetworkCredential(user, pwd, uriRoot);
compiledScript.Run(creds);
|
At that stage you should (if everything has worked) have a new folder on your Live Desktop containing the most recent tweets of the Twitterer that you specified. I have produced a video that shows what’s going on here and have embedded it below. It may not show up in an RSS reader in which case visit this blog entry at http://blogs.conchango.com/jamiethomson/archive/2008/11/10/livefx-introduction-to-resource-scripts.aspx.
If you have any questions, please ask away in the comments. The entire code listing is provided below. Note that you will not be able to run the code unless you have access to the Live Framework developer Tech Preview for which you need to request access at http://www.azure.com.
If this isn’t enough for you and you want more demo material then head to for Oran Dennison’s blog entry Live Mesh Resource Script Demo.
The final thing I need to do is thank Shelly Guo from Microsoft who, when I enquired about resource scripts, basically provided all of the code that you see above. Thanks again Shelly.
Comments are welcome by the way. I love getting comments.
-Jamie
using System;
using Microsoft.LiveFX.ResourceModel;
using Microsoft.LiveFX.ResourceModel.Scripting;
using System.Net;
using System.IO;
using System.Xml.Linq;
using System.Xml;
using System.Linq;
using System.Collections.Generic;
namespace Exploring_scripts
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Twitter username?");
string twitterUsername = Console.ReadLine();
Console.WriteLine("How many tweets?");
int numberOfTweets = Convert.ToInt32( Console.ReadLine() );
string[] tweetIDs = GetTweetIDs(twitterUsername, numberOfTweets);
CreateMeshFolderOfTweets(twitterUsername, MyCreds.liveFXUsername,
MyCreds.liveFXPassword,
"https://user-ctp.windows.net", tweetIDs);
}
static void CreateMeshFolderOfTweets(string twitterusername, string user, string pwd, string uriRoot, string[] tweetsIDs)
{
//Create a MeshObject
string moName = twitterusername + "_tweets_" + System.DateTime.Now.ToString("yyyyMMddHHmmss");
MeshObjectResource mo = new MeshObjectResource(moName);
mo.Type = "LiveMeshFolder";
//Create a feed in that MeshObject
DataFeedResource df = new DataFeedResource("TweetFeed");
df.Type = "LiveMeshFiles";
//Create a List of statements that need to get executed
// and add the MeshObject and feed creation statements
List<Statement> childStatements = new List<Statement>();
Uri meshObjsUri = new Uri(uriRoot + "/V0.1/Mesh/MeshObjects");
childStatements.Add(Statement.CreateResource<MeshObjectResource>(
"moHandle", meshObjsUri, mo
));
childStatements.Add(Statement.CreateResource<DataFeedResource>(
"feedHandle", null, df, Statement.Bind(
"CollectionUrl", "moHandle", "Response.DataFeedsLink")
));
//Loop over the list of tweet IDs and create a MediaResource Statement for each one
foreach (var tweetID in tweetsIDs)
{
Uri tweet = new Uri(String.Format(@"http://twitter.com/statuses/show/{0}.xml", tweetsIDs[0]));
childStatements.Add(Statement.CreateMediaResource<DataEntryResource>(
"tweet" + tweetID, null, tweet, Statement.Bind(
"CollectionUrl", "feedHandle", "Response.MediaResourcesLink")
));
}
//Convert list of Statements into a single atomic statement and compile
SequenceStatement scriptSource = Statement.Sequence(childStatements.ToArray());
ResourceScript<SequenceStatement> compiledScript = scriptSource.Compile();
//Execute the script!!
NetworkCredential creds = new NetworkCredential(user, pwd, uriRoot);
compiledScript.Run(creds);
}
static string[] GetTweetIDs(string twitterUsername, int count)
{
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(String.Format(@"http://twitter.com/statuses/user_timeline/{0}.atom?count={1}", twitterUsername, count.ToString()));
request.Method = "GET";
XDocument doc = XDocument.Load(
XmlReader.Create(
new StreamReader(
((HttpWebResponse)request.GetResponse())
.GetResponseStream())
)
);
XNamespace ns = @"http://www.w3.org/2005/Atom";
IEnumerable<string> tweets = from e in doc.Descendants(ns + "entry").Descendants(ns + "id")
select e.Value.Substring(e.Value.LastIndexOf("/") + 1);
return tweets.ToList<string>().ToArray();
}
}
}