RIA Services and Windows Azure Table Storage
We worked right up to the deadline, but I’m happy to say we got a preview of RIA support for Windows Azure Table Storage into the October Toolkit release (that coincides with the SP1 Beta release). You could integrate with table storage previously, but our goal was to make using it as simple as possible. With all the great new Azure features coming down the pipe (announced at PDC10), we want to give you first-class experience using table storage with WCF RIA Services.
Where to Start?
If you’re new to Windows Azure (and to tell the truth, we all fall in that bucket) then you need to start with Jim Nakashima’s blog. I always refer back to it, and it’s proved an invaluable resource. For an introduction to table storage, this post is a good start. The difference between Jim’s approach and what I’ll show here are the steps we take to integrate with existing DomainService patterns. Without further introduction, I’ll jump into the things you need to know to get the most out of our Microsoft.ServiceModel.DomainServices.WindowsAzure assembly preview.
Let’s Get a Project Set Up
We’ll use this section to go step-by-step and set up a clean Silverlight project ready for Azure. If you’re interested in starting with the Business Application Template instead, take a look at this introductory post.
1) Make sure you have the latest Windows Azure SDK installed (https://www.microsoft.com/windowsazure/)
2) Open Visual Studio and create a new Silverlight project with WCF RIA Services enabled
3) Add a Windows Azure Cloud Service project to your solution. When the New Cloud Service Wizard pops up, select ‘Ok’.
4) In your Cloud Service project, Add a Web Role Project from your solution.
In the Wizard, select your Web Application project.
5) The next step is to add the RIA Services reference you need to your Web Application project. My favorite way to do this is to add an empty DomainService using the wizard and then immediately delete it.
6) Finally, we need to make sure our application can be deployed by setting the RIA Services references to ‘Copy Local = True’.
In your web project, make sure to update the reference to both System.ServiceModel.DomainServices.Hosting and System.ServiceModel.DomainServices.Server.
7) Now that we’re ready to go, let’s add the references we’ll need to work with Windows Azure. Add references to System.Data.Services.Client, Microsoft.WindowsAzure.ServiceRuntime, Microsoft.WindowsAzure.StorageClient, and Microsoft.ServiceModel.DomainServices.WindowsAzure to your Web Application project.
You’ll also need to set Copy Local to True for both Microsoft.WindowsAzure.StorageClient and Microsoft.ServiceModel.DomainServices.WindowsAzure.
A Windows Azure Entity
Now that we have a project set up, we can focus on using Windows Azure Table Storage. Windows Azure provides queryable structured storage. You can create an unlimited number of tables which in turn can have an unlimited number of entities. There are a few constraints to the structure, but it’s pretty flexible (for more information take a look at the Table Storage whitepaper). One hard rule is that every entity must have three properties, the PartitionKey, the RowKey and the Timestamp. Together these form a unique key for an entity. Additionally, query results are returned sorted by PartitionKey and then by RowKey.
To make it as simple as possible to define entities, we’ve provided the base class TableEntity that includes these properties. The first step to working with table storage is to define your entity. For this post, we’re starting from scratch and the properties on our entity will define the schema of our table.
public class MyEntity : TableEntity
{
public string MyName { get; set; }
}
With this small snippet, I’ve defined an entity with four properties; PartitionKey, RowKey, Timestamp, and MyName. Now with an entity in hand, we’ll look at defining a context for working with table storage.
A Context for Table Storage
The second step for getting running with Windows Azure Table Storage is to create a context the DomainService can use. We wanted to create a familiar feel for developers used to working with RIA, so we’ve provided you with two types TableEntitySet and TableEntityContext.
public class MyEntityContext : TableEntityContext
{
public MyEntityContext() :
base(RoleEnvironment.
GetConfigurationSettingValue("DataConnectionString"))
{
}
public TableEntitySet<MyEntity> MyEntities
{
get { return base.GetEntitySet<MyEntity>(); }
}
}
In the snippet above, we reference a connection string specified in the Web Role configuration. To add a connection string, open the Web Role properties.
Now, with a small snippet of code, we’re ready to write our DomainService.
A Table Storage Domain Service
With our entity defined and our context ready, we can begin to write a DomainService. Once again, we’ve provided you with a base class to start with. The TableDomainService takes care of a lot of the table-storage-specific concerns so you can concentrate on your business logic.
[EnableClientAccess]
public class MyDomainService : TableDomainService<MyEntityContext>
{
public IQueryable<MyEntity> GetMyEntities()
{
return this.EntityContext.MyEntities;
}
public void AddMyEntity(MyEntity entity)
{
this.EntityContext.MyEntities.Add(entity);
}
public void DeleteMyEntity(MyEntity entity)
{
this.EntityContext.MyEntities.Delete(entity);
}
public void UpdateMyEntity(MyEntity entity)
{
this.EntityContext.MyEntities.Update(entity);
}
}
For this sample, I just stubbed out simple CRUD operations. This is only a start, but we’ve now completed everything that’s required to get you up and running with a DomainService that reads and writes to table storage.
The Best Part
The best part is that once you have written your DomainService, all the RIA things you’re familiar with just work. You get change tracking. You get validation. You get the works. For instance, this small snippet uses a DomainDataSource to hook a DataGrid up to your DomainService.
<UserControl … >
<StackPanel …>
<dds:DomainDataSource ElementName="myEntitiesDDS" QueryName="GetMyEntitiesQuery">
<dds:DomainDataSource.DomainContext>
<web:MyDomainContext />
</dds:DomainDataSource.DomainContext>
</dds:DomainDataSource>
<sdk:DataGrid ItemsSource="{Binding ElementName=myEntitiesDDS, Path=Data}" />
</StackPanel>
</UserControl>
This post was just a brief intro, but there was a lot of area to cover. In the next couple of weeks, I’ll try to flesh out some of the scenarios where things get a little more interesting. Also, if there are any Azure-related topics you’re curious about or want to see covered, just let me know and I’ll try to include them.
Comments
Anonymous
November 15, 2010
So does the table storage context manage CloudStorageAccount under the covers? What about using CreateTableIfNotExist? Any documentation out there? I can add entities no problem but cannot query.Anonymous
November 15, 2010
It's taken care of in the TableEntitySet, but I've considered making it more obvious in the future. From what I've seen, there might be issues with development storage if you tables don't exist, but they don't translate to the cloud. For complex queries, add a dummy entity to development storage (if it's empty) to give it structure. Also, There are a number of LINQ operations that are only partially supported in Table storage (here's the list msdn.microsoft.com/.../dd135725.aspx). Some operations (like sorting) you'll have to do locally (this.EntityContext.MyEntities.ToArray()...).Anonymous
November 16, 2010
sigh Just found a major bug in the October Tookit. Guess you won't be able to use this with the DDS (or otherwise sort from the client). I'm working to get a Toolkit refresh out there.Anonymous
November 17, 2010
The comment has been removedAnonymous
November 17, 2010
@jamesstill Harsh words, but in some measure deserved. I've been able to get my RIA projects running fine, but there was a bit of a learning curve. I've found work on Azure forced me to learn how to use IntelliTrace (which is a great tool BTW). It's the only way I've been able to nail down issues in my deployed applications. From what I've heard, the next version of the Azure tools will have a lot of new features to streamline some of this as well. I'm not sure when they come out, but maybe check back in half a year instead.Anonymous
November 24, 2010
I am able to add entities, but when I call the base.GetEntitySet<MyEntity>() in my TableEntityContext, the TableEntitySet is always empty. However, if use base.CreateQuery<MyEntity>("MyEntity") it correctly returns all entities added to the table. Are there any gotchas that are preventing the TableEntitySet from getting populated?Anonymous
December 01, 2010
@clemg Check out my newer post on PartitionKeys (blogs.msdn.com/.../windows-azure-table-partitionkey-options-for-tabledomainservices.aspx). By default, the query is optimized to filter on partition key, so the two snippets you show above are expected to behave differently.Anonymous
December 23, 2010
Kyle, Thanks for the post. Are there any Channel 9 videos, etc. that can take me through this process? I have a SL4 app, wrapped in a Cloud Service project and have deployed it successfully to Azure. I'm even using the ASP.Net Membership and Role object on Azure to authenticate. But I'm ready to use RIA to created the rest of my SL4 pages and am struggling where to start. I really like the Channel 9 videos or those similar. Once I get a Summary page (i.e. with grid) and a Detail page (i.e. Child-like form) to house detail information such that user's may update. I should be cooking with gas. Any thoughts? Appreciate it.Anonymous
January 04, 2011
@Snoopy I haven't seen any videos on the subject. It sounds like you've got the hard stuff out of the way. If you're using Entity Framework and SQL Azure, you can just set your connection string to the one provided in the SQL Azure portal and everything is the same as before. If you aren't using SQL Azure, consider using Windows Azure Table Storage, a feature I talk about in some of my more recent posts.Anonymous
January 06, 2011
Thanks Kyle. Yes, I forgot to mention, my desire is to use Table Storage, rather than SQL Azure. I'll check out your recent posts.Anonymous
January 07, 2011
is it possible that Microsoft.ServiceModel.DomainServices.WindowsAzure; don't exist with last versione of azure skd?Anonymous
January 10, 2011
@gioCap It's part of the latest RIA toolkit release. www.silverlight.net/.../riaservicesAnonymous
January 20, 2011
Kyle, As I am just starting out with RIA and Azure as well, I'd like to see this sample include odata and soap endpoints as well. Regards, RonAnonymous
January 20, 2011
@Ron There shouldn't be any difference running those endpoints in Azure. Take a look at Deepesh's post where he shows how to use the various endpoints. blogs.msdn.com/.../silverlight-tv-episode-26-exposing-soap-json-and-odata-endpoints-from-ria-services.aspx Also, make sure you remember to set CopyLocal to true for each of the RIA assemblies.Anonymous
January 20, 2011
The comment has been removedAnonymous
January 21, 2011
@Ron RIA works on 1.2 as well (though you'll have to look up the PDC10 release of the Toolkit to get v1.2-compatible build of the TableDomainService). Take a look at this series of posts to get you started. blogs.msdn.com/.../azureAnonymous
January 28, 2011
Is there any way that you can create a shell project in VS2010 that hooks up Silverlight 4, WCF Ria Services, and Windows Azure (Table Storage) and post for download so that we can just start out with the raw plumbing hooked up? Please :)Anonymous
May 25, 2011
I am trying to follow this code but get an error at public class MyDomainService : TableDomainService<MyEntityContext> The error is Inconsistent Accessibility: base class .... Any help?Anonymous
May 26, 2011
@Martin Sure, but I'll need more information. Usually inconsistent accessibility messages are pretty helpful.Anonymous
May 26, 2011
The error is Error 1 Inconsistent accessibility: base class 'Microsoft.ServiceModel.DomainServices.WindowsAzure.TableDomainService<RiskAggregation.ClassLibrary.Web.WorkSpaceStorageContext>' is less accessible than class 'RiskAggregation.ClassLibrary.Web.WorkSpaceStorageRepository' C:UsersMartinDocumentsVisual Studio 2010ProjectsWindowsAzureRiskAggregatorCompRiskAggregation.ClassLibrary.WebWorkSpaceStorageWorkSpaceStorageRepository.cs 15 18 RiskAggregation.ClassLibrary.WebAnonymous
May 26, 2011
@Martin It looks like you context type (the generic parameter to TableDomainService) is internal while your WorkSpaceStorageRepository is public. You should either make the both internal or both public.Anonymous
June 01, 2011
Most probably a basic question. In the example above, instead of accessing the domain service directly from the view/xaml. How do I reference the domain service from the view model i.e. what object type my I create and domain service function call to get all my entities so that I can bind to a data grid. Thanks MartinAnonymous
June 02, 2011
@Martin Here's a good site to start with: msdn.microsoft.com/.../ee707370(v=VS.91).aspx After that, I have a few posts you might find interesting: blogs.msdn.com/.../collection-binding-options-in-wcf-ria-services-sp1.aspx blogs.msdn.com/.../mvvm-pattern-for-ria-services.aspxAnonymous
June 02, 2011
Kyle, Thanks for all your help. Do you know of any simple examples of accessing azure storage tables through RIA services using Silverlight MMVM?Anonymous
June 05, 2011
@Martin The great part about RIA Services is the same client pattern will work against any type of DomainService. For example, you can have one DS using Entity Framework and another using Table Storage and your client is blissfully ignorant. Feel free to just take a Table Storage sample (the DomainService side) and an MVVM sample (the SL side) and mash them together.Anonymous
January 08, 2012
Kyle - I'm just getting started with RIA and table services. I need to dynamically bind the context to the azure storage account of the logged in user - so using the dataconnectionstring in the context constructor doesn't work. Previously with the storage client api I've done something like: public MyContext(CloudStorageAccount account) : base(account) { } That doesn't seem to work with the TableEntityContext as I get errors that the constructor must be parameterless when I compile. What do you suggest? Thanks -MarkAnonymous
January 16, 2012
@MarkAArnold The constructor constraint is in TableDomainService, so you need to do two things to get this to work. First, add a parameterless constructor to your context (even if you don't intend it to be used). Second, override TableDomainService.CreateEntityContext to correctly call the constructor that takes a CloudStorageAccount. Here's the default implementation if you're interested. protected virtual TEntityContext CreateEntityContext() { TEntityContext context = new TEntityContext(); context.PartitionKey = this.PartitionKey; // Use batching semantics unless otherwise specified context.SaveChangesDefaultOptions = string.IsNullOrEmpty(this.PartitionKey) ? SaveChangesOptions.None : SaveChangesOptions.Batch; return context; }