May
15
2012
A new feature in MVC 4 Beta is the ASP.NET Single Page Application.
SPA provides a very nice way of delivering high-end websites with amazing client-side interaction.
On Steven Sanderson's blog there is a great post about SPA with a very nice DeliveryTracker sample and on the ASP.NET SPA website there are several good walkthroughs and samples explaining how to build common scenarios with SPA.
These samples, however, all show how you can build a SPA site with Entity Framework as the database back-end.
This post will explain how you can use SPA (or upshot to be specific) without Entity Framework.
A part of SPA is the upshot JavaScript library which provides data access and caching. Upshot uses the new WebAPI for retrieving and saving the data.
The UpshotContext HtmlHelper is provided, which generates the necessary JavaScript that upshot needs to be able to use the WebAPI datasource you specified.
Below shows the UpshotContext as it is used in the Home view in the DeliveryTracker sample from Steven Sanderson.
Html.UpshotContext(bufferChanges: true).DataSource<DataServiceController>(x => x.GetDeliveriesForToday())
Suppose we have a simple Customer POCO.
public class Customer
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
If we want upshot to retrieve a list of Customers, we need to add a WebAPI Controller to our application and use this in the UpshotContext in our view.
The UpshotContext expects a TDataController where TDatacontroller is of type System.Web.Http.Data.DataController, which derives from ApiController.
In the existing samples i found, this TDataController is always of type System.Web.Http.Data.EntityFramework.DbDataController, which means it is using EF.
When we implement the new controller and derive it from DataController, we are not using any EF specific classes, so we're not using EF at all.
namespace MvcApplication27.Controllers
{
public class CustomerServiceController : DataController
{
public IEnumerable<Customer> GetCustomers()
{
List<Customer> result = new List<Customer>();
result.Add(new Customer() { Id = result.Count + 1, FirstName = "John", LastName ="Doe" });
result.Add(new Customer() { Id = result.Count + 1, FirstName = "Jane", LastName = "Doe" });
return result;
}
}
}
Off course in a real-world application the list of Customers will not be recreated each time in the GetCustomers method, but will be retrieved from some kind of storage/database.
At this time we can add the UpshotContext to our view.
Html.UpshotContext().DataSource<MvcApplication27.Controllers.CustomerServiceController>(x => x.GetCustomers())
But now, when we run our application, the UpshotContext will give us this error : "queryOperation 'GetCustomers' must return an entity type or an IEnumerable/IQueryable of an entity type" 
After digging through the MVC 4 Beta sourcecode (available here) i found out that this error is occuring because upshot doesn't know what the 'identity' property of Customer is.
Thankfully we can tell upshot what property this is by using the System.ComponentModel.DataAnnotations.KeyAttribute in our Customer POCO.
public class Customer
{
[Key]
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
After this we can add a small ViewModel to our project.
function CustomersViewModel() {
var self = this;
self.dataSource = upshot.dataSources.Customers.refresh();
self.customers = self.dataSource.getEntities();
}
And add a bit of Knockout.js to our view and we're done
@(Html.UpshotContext().DataSource<MvcApplication27.Controllers.CustomerServiceController>(x => x.GetCustomers()))
<h2>Customers</h2>
<ul data-bind="foreach: customers">
<li>
<div>Name: <em data-bind="text: FirstName"></em> <strong data-bind="text: LastName"></strong></div>
</li>
</ul>
<script src="~/Scripts/CustomersViewModel.js" type="text/javascript"></script>
<script type="text/javascript">
$(function () {
ko.applyBindings(new CustomersViewModel());
});
</script>
The next post will explain what needs to be done to have upshot understand relationships between POCO's and how to handle CRUD operations.
It will continue were this post ends and will add Order, Product and OrderProduct POCO's so we have one-to-many and many-to-many relationships.