The third and final post in our blog series on basic usage of the Cloud Storage API (CloudStorage.API). This time we will show how to interact with the Cloud Entity Storage.
The implementation we use for the API is developed against Azure but the API should be reusable for any type of Cloud Storage. While doing this our main goals are to:
- Enable testability
- Abstract away storage
- Create an extensible and easy to evolve application that supports good developments practices
Here are other posts in this series:
Note: We are about to publish all of these samples on Azure Contrib. (Soonish…)
Overview
As in the previous posts we will use a really simple application as a proof of concept. This time it’s a List of people that we will show in a regular GridViewControl.
So, this application takes the content of the form. It adds a new person to the set of Person Entities and stores it. Then it fetches all persons and shows them in the grid.
The tests for the controller
Since the goal is testability we move as much of the logic as possible away from the ASP page into a class that we can easily test. For the Queue Service we created a controller class which took an interface from the Cloud Storage API as a parameter. The controller class then handled all the logic for the corresponding ASP page. We now had a decoupled and testable class. We’ll do the same thing here :-)
Here are the tests for interacting with the Cloud Entity Storage.
private IEntityTableDataContext _dataContext;
private EntitySampleController _entitySampleController;
[TestInitialize]
public void TestInitialize()
{
_dataContext = MockRepository.GenerateStub<IEntityTableDataContext>();
_entitySampleController = new EntitySampleController(_dataContext);
}
[TestMethod]
public void AddPerson_CalledWithEmptyParameters_ShouldAddPerson()
{
string firstName = string.Empty;
string lastName = string.Empty;
string address = string.Empty;
string mobile = string.Empty;
_entitySampleController.AddPerson(firstName, lastName, address, mobile);
_dataContext.AssertWasCalled(x => x.AddObject(string.Empty, null), options => options.IgnoreArguments());
_dataContext.AssertWasCalled(x => x.SaveChanges(), options => options.IgnoreArguments());
}
[TestMethod]
public void GetPersonEntities_WhenStorageIsEmpty_ShouldReturnEmptyCollection()
{
_dataContext.Stub(x => x.GetEntitySet<PersonEntity>())
.Return((Enumerable.Empty<PersonEntity>())
.AsQueryable());
var personEntities = _entitySampleController.GetPersonEntities();
Assert.AreEqual(0, personEntities.Count());
}
[TestMethod]
public void GetPersonEntities_WhenStorageContainsEntities_ShouldReturnCollectionWithPersonEntities()
{
var entities = new List<PersonEntity>
{
new PersonEntity("a", "b", "c", "d"),
new PersonEntity("e", "f", "g", "h")
};
_dataContext.Stub(x => x.GetEntitySet<PersonEntity>())
.Return((entities)
.AsQueryable());
var personEntities = _entitySampleController.GetPersonEntities();
Assert.AreEqual(2, personEntities.Count());
}
[TestMethod]
public void GetPersonEntities_WhenStorageOver100Entities_ShouldReturnCollectionContaining100()
{
var entities = new List<PersonEntity>();
for (int i = 0; i < 150; i++)
{
entities.Add(new PersonEntity("a","b","c",i.ToString()));
}
_dataContext.Stub(x => x.GetEntitySet<PersonEntity>())
.Return((entities)
.AsQueryable());
var personEntities = _entitySampleController.GetPersonEntities();
Assert.AreEqual(100, personEntities.Count());
}
Perhaps a few more tests for data being sent in is the same data that is actually stored would be pertinent but you get the general picture.
Using the controller in the page
The code behind is kept simple containing only DataBinding and forwarding of information to the controller.
private EntitySampleController _entitySampleController;
protected void Page_Load(object sender, EventArgs e)
{
var dataServiceContext = CloudStorageDataContextFactory.GetEntityTable<PersonEntity>();
_entitySampleController = new EntitySampleController(dataServiceContext);
RefreshDataGrid();
}
protected void SubmitButton_OnClick(object sender, EventArgs e)
{
string firstName = FirstNameTextBox.Text;
string lastName = LastNameTextBox.Text;
string address = AddressTextBox.Text;
string mobile = MobileTextBox.Text;
_entitySampleController.AddPerson(firstName, lastName, address, mobile);
RefreshDataGrid();
}
private void RefreshDataGrid()
{
DataGridView.DataSource = _entitySampleController.GetPersonEntities();
DataGridView.DataBind();
}
Sure we could have used an ASP.NET MVC application here – in the latest release of the Azure SDK (July 09 CTP) you can deploy any ASP.NET application type to the cloud. But we felt that adding more concepts to this sample would not be beneficial. We could also have added logic for not fetching the Person Entities twice in case of a post back. We omitted that too.
OK – that’s it. We showed how we, in a simple way, can abstract storage away from our ASP.NET App by adding a Controller class. This makes the ASP.NET Page a “humble dialog” meaning it does not contain business logic only data forwarding and UI concerns. We can then test all logic in the Controller class without having to bother with a real Cloud Storage source. All real data access is mocked out with the help of the API and some Rhino Mocks magic.
The API may be implemented against any data store. It works well for Windows Azure and should in theory work for any other Cloud Storage out there. We would love feedback and assistance on this. The thing is that this API works against any kind of storage – not just clouds. You could work against a local database, against a company server farm or against a mock. Honestly; who cares? All our app cares about is the service; to be able to store and get Entities.
M. (and P.)
posted @ Thursday, August 20, 2009 1:21 PM