Creating an Azure Table Storage Repository
When I got started working with Azure Table Storage I referenced the Authoritative Source on MSDN and started trying to make sense of things. With the new November 2009 SDK it consisted of doing the following things:
- Create the data structure class that inherits from TableServiceEntity.
- Creating a class that inherits from TableServiceContext.
- For each data structure, add a method on the TableServiceContext class to return a IQueryable<T> object.
Here is an example of what you may end up with:
public class ExampleContext : TableServiceContext
{
public ExampleContext(string baseAddress, StorageCredentials credentials)
: base(baseAddress, credentials)
{
}
public IQueryable<bar> BarTable
{
get
{
return CreateQuery<bar>("BarTable");
}
}
public IQueryable<foo> FooTable
{
get
{
return CreateQuery<foo>("FooTable");
}
}
public void AddBar(Bar item)
{
AddObject("BarTable", item);
}
public void AddFoo(Foo item)
{
AddObject("FooTable", item);
}
}
public class Bar : TableServiceEntity
{
public Bar()
{
}
public Bar(string partitionKey, string rowKey)
: base(partitionKey, rowKey)
{
}
public string Name { get; set; }
public DateTime Due { get; set; }
}
public class Foo : TableServiceEntity
{
public Foo()
{
}
public Foo(string partitionKey, string rowKey)
: base(partitionKey, rowKey)
{
}
public string Title { get; set; }
}
What I don’t like about this is that the class that inherits from TableServiceContext is a tightly coupled to the classes and the table names that are being stored. For example, every time a new TableServiceEntity class is created, methods need to be created that know how to insert and query. In addition to this we still haven’t handled the code to create the tables. The other thing I didn’t like about this code was that the table names were just strings and were used in more than one spot. As I was coming up with a better design this was the first thing I tackled. What I ended up coming up with was a class that allowed me to easily get the name of the table.
AddObject(For<Foo>.Table(), newItem);
As you can see, this uses generics to get the name of the table. This results in being able to use one method for insert, but that still leaves the Query properties in a way that they cannot use generics. If we change it to a method we can get it down to one method for Create as well.
public class ExampleContext : TableServiceContext
{
public ExampleContext(string baseAddress, StorageCredentials credentials)
: base(baseAddress, credentials)
{
}
public IQueryable<T> Table<T>() where T : TableServiceEntity
{
return CreateQuery<T>(For<T>.Table());
}
public void Add<T>(T item) where T : TableServiceEntity
{
AddObject(For<T>.Table(), item);
}
}
Back earlier I mentioned that you needed to create tables, or at least make sure they were there before you started using them. Should the consumers of this class be responsible for doing this? I didn't think they should, so what I ended up doing was creating a generic repository class that exposes CRUD and query operations and also makes sure that tables are created. What this allows me to do is have my next layer up only take a dependency on IRepository
public interface IRepository<T> where T : TableServiceEntity
{
IQueryable<T> Table { get; }
void Attach(T existingItem);
void Delete(T itemToDelete);
void Insert(T newItem);
T Load(string partitionKey, string rowKey);
IEnumerable<T> Load(string partitionKey);
IEnumerable<T> Query(IQueryable<T> query);
IEnumerable<T> Last(int count);
T Last();
void Update(T post);
}
public class AzureRepository<T> : IRepository<T> where T : TableServiceEntity
{
private readonly TableServiceContext _serviceContext;
private readonly CloudStorageAccount _storageAccount;
private readonly CloudTableClient _tableClient;
public AzureRepository(CloudStorageAccount storageAccount)
{
_storageAccount = storageAccount;
_serviceContext = new TableServiceContext(_storageAccount.TableEndpoint.ToString(), _storageAccount.Credentials);
_tableClient = _storageAccount.CreateCloudTableClient();
}
protected virtual void EnsureTable()
{
_tableClient.CreateTableIfNotExist(For<T>.Table());
}
public void Attach(T existingItem)
{
EnsureTable();
_serviceContext.AttachTo(For<T>.Table(), existingItem, "*");
}
public void Delete(T itemToDelete)
{
EnsureTable();
_serviceContext.AttachTo(For<T>.Table(), itemToDelete, "*");
_serviceContext.DeleteObject(itemToDelete);
_serviceContext.SaveChanges();
}
public void Insert(T newItem)
{
EnsureTable();
_serviceContext.AddObject(For<T>.Table(), newItem);
_serviceContext.SaveChanges();
}
public T Last()
{
EnsureTable();
var results = from p in Table select p;
var query = results.AsTableServiceQuery();
var queryResults = query.Execute();
return queryResults.LastOrDefault();
}
public T Load(string partitionKey, string rowKey)
{
EnsureTable();
var results = from p in Table
where p.PartitionKey == partitionKey && p.RowKey == rowKey
select p;
var query = results.AsTableServiceQuery();
var queryResults = query.Execute();
return queryResults.FirstOrDefault();
}
public IEnumerable<T> Load(string partitionKey)
{
EnsureTable();
var results = from p in Table
where p.PartitionKey == partitionKey
select p;
var query = results.AsTableServiceQuery();
return query.Execute();
}
public IEnumerable<T> Query(IQueryable<T> query)
{
var azureQuery = query.AsTableServiceQuery();
return azureQuery.Execute();
}
public IQueryable<T> Table
{
get
{
EnsureTable();
return _serviceContext.CreateQuery<T>(For<T>.Table());
}
}
public IEnumerable<T> Last(int count)
{
EnsureTable();
var linqQuery = (from p in Table select p).Take(count);
var azureTable = linqQuery.AsTableServiceQuery();
var results = azureTable.Execute();
return results;
}
public void Update(T post)
{
EnsureTable();
_serviceContext.UpdateObject(post);
_serviceContext.SaveChanges();
}
}
Sunday, February 14, 2010
Comments
I need some help here, how do I easily test my controller?
If you have ever tried to test any of the framework aspects of a controller, such as making sure the response code is a 404, you probably know that it's not as easy as you had hoped. The following is a base class that I have created that I use in my testing to set up all of the dependencies using Rhino Mocks. In addition to this it is necessary to create a "Testable" version of your controller, of which I have an example bellow.
Test Base Class
public class ControllerBaseTester
{
protected readonly IBlogConfiguration Configuration = MockRepository.GenerateStub<IBlogConfiguration>();
protected readonly HttpContextBase HttpContext = MockRepository.GenerateStub<HttpContextBase>();
protected readonly RouteData RouteData = new RouteData();
protected readonly RequestContext Context;
protected readonly StringBuilder ResponseString = new StringBuilder();
protected readonly TextWriter ResponseWriter;
protected readonly HttpResponseBase Response;
public ControllerBaseTester()
{
ResponseWriter = new StringWriter(ResponseString);
Response = new HttpResponseWrapper(new HttpResponse(ResponseWriter));
HttpContext.User = Test.AuthorizedUser;
HttpContext.Expect(x => x.Response).Return(Response);
Context = new RequestContext(HttpContext, RouteData);
Configuration.Stub(x => x.Configuration).Return(Test.Configuration);
}
}
Creating a Testable Controller
class TestableController : BlogController
{
TestableController(IBlogService blogService, IBlogConfiguration configuration)
: base(blogService, configuration)
{
}
private void Init(RequestContext context)
{
Initialize(context);
}
public static BlogController Create(RequestContext context, IBlogService blogService, IBlogConfiguration configuration)
{
var result = new TestableController(blogService, configuration);
result.Init(context);
return result;
}
}
Tuesday, February 02, 2010
Comments
Passing Configuration Data to the Master Page in ASP.NET MVC
One of the common activities that I have found myself doing lately whenever I create a new project is to identify a way of loading configuration information and customizing the behavior of the Master Page. The way I initially went about solving this problem was to create a base class and have all of my Page ViewModel’s inherit from it. This caused two different things that I had to constantly maintain, making sure all of my ViewModel’s inherited from this MasterViewModel and then remembering to set the data every time a ViewResult is returned in an Action.
public class MasterViewModel : IMasterViewModel
{
public string SiteName { get; set; }
}
I was able to take care of the second concern by simply creating a base controller that all of my controllers would inherit from and then override View(string viewName, string masterName, object model) { … } so that the settings are automatically injected into the model.
protected override ViewResult View(string viewName, string masterName, object model)
{
((MasterViewModel)model).SiteName = "Example";
return base.View(viewName, masterName, model);
}
In order to get around the smell of forcing my ViewModel’s to inherit from a base class I put the MasterViewModel into the ViewData dictionary that is returned in the ViewResult.
protected override ViewResult View(string viewName, string masterName, object model)
{
ViewResult result = base.View(viewName, masterName, model);
result.ViewData[Data.Site] = _blogConfiguration.Configuration;
return result;
}
Once I had this in place, I created a new ViewMasterPage with a property that exposed the data that was put into ViewData. This then allowed me to have a strongly typed referenced from the Master Page.
<%@ Import Namespace="Azure.Domain.Models"%>
<%@ Master Language="C#" Inherits="Azure.Web.Views.ViewMasterPage" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title><%= SiteConfiguration.Name %><asp:ContentPlaceHolder ID="TitleContent" runat="server" /></title>
<link href="../../Content/Site.css" rel="stylesheet" type="text/css" />
<script src="http://ajax.microsoft.com/ajax/jquery/jquery-1.4.1.min.js" type="text/javascript" language="javascript"></script>
<script src="http://ajax.microsoft.com/ajax/jQuery.Validate/1.6/jQuery.Validate.min.js" type="text/javascript" language="javascript"></script>
<asp:ContentPlaceHolder ID="HeaderContent" runat="server" />
</head>
<body>
<div id="header">
<h1><%= SiteConfiguration.Name %></h1>
</div>
<div id="main">
<asp:ContentPlaceHolder ID="MainContent" runat="server" />
</div>
<div id="footer">
©<%= DateTime.UtcNow.Year %> <%= SiteConfiguration.Owner %>
</div>
</body>
</html>
All examples are taken from the work I have been doing as a part of creating my own blog engine on Azure. You can track my progress and grab source code on my AzureBlog project site on CodePlex.
Tuesday, February 02, 2010 CommentsIE 6 is Dead
I just received this email from google. I think we can all celebrate this.
Tuesday, February 02, 2010 CommentsDear Google Apps admin,
In order to continue to improve our products and deliver more sophisticated features and performance, we are harnessing some of the latest improvements in web browser technology. This includes faster JavaScript processing and new standards like HTML5. As a result, over the course of 2010, we will be phasing out support for Microsoft Internet Explorer 6.0 as well as other older browsers that are not supported by their own manufacturers.
We plan to begin phasing out support of these older browsers on the Google Docs suite and the Google Sites editor on March 1, 2010. After that point, certain functionality within these applications may have higher latency and may not work correctly in these older browsers. Later in 2010, we will start to phase out support for these browsers for Google Mail and Google Calendar.
Google Apps will continue to support Internet Explorer 7.0 and above, Firefox 3.0 and above, Google Chrome 4.0 and above, and Safari 3.0 and above.
Starting this week, users on these older browsers will see a message in Google Docs and the Google Sites editor explaining this change and asking them to upgrade their browser. We will also alert you again closer to March 1 to remind you of this change.
In 2009, the Google Apps team delivered more than 100 improvements to enhance your product experience. We are aiming to beat that in 2010 and continue to deliver the best and most innovative collaboration products for businesses.
Thank you for your continued support!
Sincerely,
The Google Apps team