WHAT IS THIS?
This is an article about an ASP.NET MVC application I ported to use Entity Spaces as the data access layer, or model.
Requirements:
- Northwind database - you need to have the Northwind database installed
- Entity Spaces - you'll need the trial version at least, of Entity Spaces to have this application work as is.
This doesn't mean that you can't learn something if you don't use Entity Spaces. This exercise is to demonstrate how to build an ASP.NET MVC application without using Linq.
INSTALLATION
Installation instructions are at the end of this article.
ENTITY SPACES
Ok a brief word about Entity Spaces. It is a really cool ORM tool but much more than that. It reads your database and builds entity classes. Now you might say that there are many tools that do that like NHibernate, SubSonic, etc., and you would be correct. Entity Spaces actually costs money where SubSonic is free. But I was using SubSonic before I trialed Entity Spaces. After about a half a day playing with it, I purchased it and I'm going on my second year (2008) using it and I couldn't be happier. Go to their web site to find out more. But this article isn't about Entity Spaces, it's about building an ASP.NET MVC application without using Linq.
UPDATE to post (6/3/2008):
Part Deux of this series will show you how to build the application to use any data layer provider instead of Entity Spaces. Stay tuned...
SO...
I've been interested in MVC for ASP.NET ever since I heard about it about six months ago, primarily for the ease of testing and the separation of concerns. So I decided to build (or port) an application using the ASP.NET MVC Preview 3 framework that was released at the end of May 2008.

I found a simple Northwind demo that Phil Haack put together and downloaded it so I could dissect it. And this application like others I've downloaded all use the same data access method for their model, which is Linq, or more accurately, Linq to Sql.
Now I don't have anything against Linq, I just don't know it and I'm not sure I want to or need to. I have Entity Spaces! So after looking at the Northwind demo, I decided to take on the task of porting it over to use Entity Spaces to see if I have the right idea of what the model (and data access layer) does. And to also see if I could do it.
<DISCLAIMER>
I am presenting this application to all those developers who do not presently know, or plan to know or use Linq. I wanted to see how easy an ASP.NET MVC application can be built using a different data access provider and I built this in such a way so that developers can easily swap out my Entity Spaces provider with their own without ever touching the higher layers.
Also, I may have my head up my okole (Hawaiian for buttocks), so cut me some slack.
</DISCLAIMER>
The way I understand MVC is that the Model loosely translates to the data access layer of the application. If I'm correct in this assumption, since no documentation seems to actually say "the Model is your data access layer", I just assume it is based on the description of what it does. (For some reason everyone knowledgable of MVC assumes newbies will know what a Model is. Unfortunately, I don't, or didn't. I've never heard the term prior to hearing about MVC. Vent!)
So based on my assumption, I rebuilt the application to not only try and integrate Entity Spaces as the Model part of this framework, but also build it using a psuedo-repository pattern in the data layer. Let me be clear about this, I AM NO EXPERT in MVC or Design Patterns, although I did just finish the Head First Design Patterns book and I like what I read.
The repository pattern I'm following is one that is employed by Rob Conery in his new MVC Storefront real-world application. So I jumped in the deep end and I decided to take on much more than I probably should have, but in the end it works and I was able to port the entire application in an afternoon. (June 1, 2008 to be exact)
WHAT HAVE I DONE?
The original application was built using the default web site structure as created by Visual Studio, primarily one project.

I've built this application in four (4) projects:
- ESNorthwind.MVC.Web - the web site, controllers, and views, no models.
- ESNorthwind.MVC.Data - this is the Model project that contains the /Custom and /Generated folders for the Entity Spaces classes, Interfaces (INorthwindRepository) and Repository classes.
- ESNorthwind.MVC.Services - this contains the service layer that sits on top of the Data layer and is directly called from the Controller class. This just adds another layer of abstraction. (Does it sound like I'm talking out of my ass yet?)
- ESNorthwind.MVC.TestProjects - this contains unit tests that turns the only color that was available, which is green!

Also a note about NUnit, I used NUnit instead of the integrated Testing framework in Visual Studio 2008, for one specific reason... I have VS 2008 Standard which does not have the Unit Testing framework. Ok, so I got the application for free from one of the Microsoft 2008 launches. I can deal with that for a while. Otherwise, I'm getting the same functionality I need for unit testing.

So back to my projects... I abstracted the application out like this for a few reasons:
- I'm a glutton for punishment
- I want to build a real-world MVC application using a pattern similar to this by not having the model in the web site project
- I want to later see if I can easily change the data store to MySql instead of SQL Server, without affecting the rest of the application
WHAT DID I CHANGE?
Most of what I had to change, besides the overall structure of the application (four projects instead of one), was just the model or probably more precisely, the data access layer of the model. (Again, my terminology might not be correct, so try not to flame me. Just post a nice response indicating how wrong I am.)
I also removed all using statements that pointed to System.Linq. I wanted to make sure that this application could live on it's own without any dependencies on Linq.
ALSO, this is the change that affected the biggest impact on whether it would work or not. I had to change every reference from Products to Product, except of course the actual entity classes created by Entity Spaces. The original demo application had a partial class called Product that it used as the reference for the model. But my application was to use the ES Classes as the model which has a class called Products, which I found out was ambiguous to the compiler.
I wanted to try and build the application with out using any manufactured classes that would partial-ize an entity class.
So the Controller name is called ProductController (singular) not ProductsController. It really was a pretty easy fix to implement site-wide, so it isn't really a problem.
VIEWS
The Views for the most part are as-is. I did have to change a few things relationship-wise to take advantage of the Entity Spaces built-in relational mapping.
This is the Edit.aspx view in the original application:
<input type="submit" value="Save" /> <%= Html.ActionLink("cancel", "List", new { id = ViewData.Model.Product.Category.CategoryName })%>
And this is the same line of code in my version:
<input type="submit" value="Save" /> <%= Html.ActionLink("cancel", "List", new { id = ViewData.Model.Product.UpToCategoriesByCategoryID.CategoryName })%>
CONTROLLER
I also tried to move all or most of the data querying out of the Controller and move it into the Model where it belongs.
Here is some ProductsController code from the original application using Linq:
public class ProductsController : Controller {
public ProductsController()
: this(new NorthwindRepository(new NorthwindDataContext()))
{ }
public ProductsController(NorthwindRepository context)
{
this.repository = context;
}
NorthwindRepository repository;
public object Index()
{
return Categories();
}
public object Categories()
{
return View("Categories", repository.Categories.ToList());
}
public object Detail(int id)
{
Product product = this.repository.Products.SingleOrDefault(p => p.ProductID == id);
return View(product);
}
public object List(string id)
{
var category = repository.Categories.SingleOrDefault(c => c.CategoryName == id);
var products = from p in repository.Products
where p.CategoryID == category.CategoryID
select p;
ViewData["Title"] = "Hello World!";
ViewData["CategoryName"] = id;
//this.ViewEngine = new MvcContrib.NHamlViewEngine.NHamlViewFactory();
return View("ListingByCategory", products.ToList());
}
public object Category(int id)
{
Category category = repository.Categories.SingleOrDefault(c => c.CategoryID == id);
return View("List", category);
}
public object Edit(int id)
{
ProductsEditViewData viewData = new ProductsEditViewData();
Product product = repository.Products.SingleOrDefault(p => p.ProductID == id);
viewData.Product = product;
if (TempData.ContainsKey("ErrorMessage")) {
foreach (var item in TempData) {
ViewData[item.Key] = item.Value;
}
}
ViewData["CategoryID"] = new SelectList(repository.Categories.ToList(), "CategoryID", "CategoryName", ViewData["CategoryID"] ?? product.CategoryID);
ViewData["SupplierID"]= new SelectList(repository.Suppliers.ToList(), "SupplierID", "CompanyName", ViewData["SupplierID"] ?? product.SupplierID);
return View("Edit", viewData);
}
public object Update(int id)
{
Product product = repository.Products.SingleOrDefault(p => p.ProductID == id);
if(!IsValid())
{
Request.Form.CopyTo(TempData);
TempData["ErrorMessage"] = "An error occurred";
return RedirectToAction("Edit", new { id = id });
}
BindingHelperExtensions.UpdateFrom(product, Request.Form);
repository.SubmitChanges();
return RedirectToRoute(new RouteValueDictionary(new { Action = "List", ID = product.Category.CategoryName }));
}
....
And here is my new Controller code:
public class ProductController : Controller
{
NorthwindService service;
#region Constructors
public ProductController()
{
service = new NorthwindService();
}
#endregion
#region View Methods
public object Index()
{
return Categories();
}
public object Categories()
{
return View("Categories", service.GetCategories());
}
public object List(string id)
{
ViewData["CategoryName"] = id;
return View("ListingByCategory", service.GetProductsByCategoryName(id));
}
public object Detail(int id)
{
return View("Detail", service.GetProductById(id));
}
public object Edit(int id)
{
ProductsEditViewData viewData = new ProductsEditViewData();
Products product = service.GetProductById(id);
viewData.Product = product;
if (TempData.ContainsKey("ErrorMessage"))
{
foreach (var item in TempData)
{
ViewData[item.Key] = item.Value;
}
}
ViewData["CategoryID"] = new SelectList(service.GetCategories(), "CategoryID", "CategoryName", ViewData["CategoryID"] ?? product.CategoryID);
ViewData["SupplierID"] = new SelectList(service.GetSuppliers(), "SupplierID", "CompanyName", ViewData["SupplierID"] ?? product.SupplierID);
return View("Edit", viewData);
}
public object Update(int id)
{
Products product = service.GetProductById(id);
if (!IsValid())
{
Request.Form.CopyTo(TempData);
TempData["ErrorMessage"] = "An error occurred";
return RedirectToAction("Edit", new { id = id });
}
BindingHelperExtensions.UpdateFrom(product, Request.Form);
service.SubmitChanges(product);
return RedirectToRoute(new RouteValueDictionary(new { Action = "List", ID = product.UpToCategoriesByCategoryID.CategoryName }));
}
#endregion
...
}
Notice in my use of the Update method I have a SubmitChanges method on the service object, it's not part of Linq. I just created it to maintain a similar feel to Linq, but I had to pass in the Products object to the method as an argument in order for it to get updated correctly. Now bear in mind that there is most likely a better way of doing this, but this is what I did. You can change it to anything you feel works as good or better. Let me know what you did though, I'd be interested in hearing what you came up with.
But you'll notice that most of my service calls provide the same functionality as those from the original demo without having any querying in the controller itself.
Now as the application gets more complex, I don't know how far I would be able to take this, but at least I was able to do it this far.
GLOBAL ASAX
This is the original application:
protected void Application_Start(object sender, EventArgs e)
{
RegisterRoutes(RouteTable.Routes);
}
And this is my version:
protected void Application_Start()
{
RegisterRoutes(RouteTable.Routes);
EntitySpaces.Interfaces.esProviderFactory.Factory = new EntitySpaces.LoaderMT.esDataProviderFactory();
}
DATABASE CHANGES
There was a small change I had to make to the Northwind database itself in order for the routing to work. There are two categories, Grains/Cereals and Meat/Poultry. If you know about routing, the forward slashes will present a problem, so I had to change the fields in the database to use dashes instead, Grains-Cereals, etc. Then it works without a problem.
Other than that, no changes to the database.
SERVICE LAYER
The service layer is essentially a relay object to the data layer. It allows for more of a separation of concerns, meaning it is easier this way, to snap-in a different provider without affecting the Web layer.
The code for the service layer is pretty straight forward:
using System;
using System.Collections.Generic;
using System.Text;
using ESNorthwind.MVC.Data;
namespace ESNorthwind.MVC.Services
{
public class NorthwindService
{
INorthwindRepository _repository = null;
#region Constructors
public NorthwindService(INorthwindRepository repository)
{
this._repository = repository;
if (_repository == null)
throw new InvalidOperationException("Repository cannot be null");
}
public NorthwindService()
{
this._repository = new NorthwindRepository();
}
#endregion
#region Public Methods
/// <summary>
/// Get all the categories.
/// </summary>
/// <returns></returns>
public CategoriesCollection GetCategories()
{
CategoriesCollection catColl = _repository.GetCategories();
return catColl;
}
/// <summary>
/// Get all products.
/// </summary>
/// <returns></returns>
public ProductsCollection GetProducts()
{
ProductsCollection prodColl = _repository.GetProducts();
return prodColl;
}
/// <summary>
/// Return the ProductsCollection based on the Category name.
/// </summary>
/// <param name="categoryName"></param>
/// <returns></returns>
public ProductsCollection GetProductsByCategoryName(string categoryName)
{
ProductsCollection prodColl = _repository.GetProductsByCategoryName(categoryName);
return prodColl;
}
/// <summary>
/// Return the product based on the product id.
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public Products GetProductById(int id)
{
Products product = _repository.GetProductById(id);
return product;
}
/// <summary>
/// Get all suppliers.
/// </summary>
/// <returns></returns>
public SuppliersCollection GetSuppliers()
{
return _repository.GetSuppliers();
}
public void SubmitChanges(Products product)
{
_repository.SubmitChanges(product);
}
#endregion
}
}
Another thing I wanted to see if I could do with this application, is to avoid using IQueryable, which is a Linq interface. Knowing as little as I did (and still do) about ASP.NET MVC, I really didn't know whether I could build and MVC application without using IQueryable.
I really wanted to use the Collection classes created by Entity Spaces instead of IQueryable, so I wrote a few tests and saw that it worked. Needless to say I was jazzed.
DATA LAYER
The first thing I did was create an interface the repository was to implement, INorthwindRepository.
using System;
using System.Collections.Generic;
using System.Text;
namespace ESNorthwind.MVC.Data
{
public interface INorthwindRepository
{
CategoriesCollection GetCategories();
ProductsCollection GetProducts();
ProductsCollection GetProductsByCategoryName(string categoryName);
Products GetProductById(int id);
SuppliersCollection GetSuppliers();
void SubmitChanges(Products product);
}
}
A quick aside... I've been reading everything I can about OOP and Design Patterns, etc., to better my knowledge of making scalable and reusable applications. I've always known about interfaces, but I have never been really clear about why they are in existence and why I should use them.
But after reading books like the Head First Design Patterns, and Head First Object Oriented Analysis and Design, I feel I have a better understanding of what their purpose in life is.
Now back to our story...
By creating a repository that implements INorthwindRepository, the service layer will accept the repository without any complaints whether it's using MySql, SQL Server, Oracle, etc.
"Code to an Interface, not an implementation!"
So the NorthwindRepository class implements this interface and does all the heavy lifting.
using System;
using System.Collections.Generic;
using System.Text;
namespace ESNorthwind.MVC.Data
{
public class NorthwindRepository : INorthwindRepository
{
#region INorthwindRepository Members
/// <summary>
/// Get all categories.
/// </summary>
/// <returns></returns>
public CategoriesCollection GetCategories()
{
CategoriesCollection catColl = new CategoriesCollection();
catColl.LoadAll();
return catColl;
}
/// <summary>
/// Get all products.
/// </summary>
/// <returns></returns>
public ProductsCollection GetProducts()
{
ProductsCollection prodColl = new ProductsCollection();
prodColl.LoadAll();
return prodColl;
}
/// <summary>
/// Return the ProductsCollection by the Category name.
/// </summary>
/// <param name="categoryName"></param>
/// <returns></returns>
public ProductsCollection GetProductsByCategoryName(string categoryName)
{
int catId = 0;
Categories cat = new Categories();
cat.Query.Where(cat.Query.CategoryName.Equal(categoryName));
cat.Query.Load();
catId = (int)cat.CategoryID;
ProductsCollection prodColl = new ProductsCollection();
prodColl.Query.Where(prodColl.Query.CategoryID.Equal(catId));
prodColl.Query.Load();
return prodColl;
}
/// <summary>
/// Get the product details by productid
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public Products GetProductById(int id)
{
Products product = new Products();
product.LoadByPrimaryKey(id);
return product;
}
/// <summary>
/// Get all suppliers.
/// </summary>
/// <returns></returns>
public SuppliersCollection GetSuppliers()
{
SuppliersCollection suppColl = new SuppliersCollection();
suppColl.LoadAll();
return suppColl;
}
public void SubmitChanges(Products product)
{
product.Save();
}
#endregion
}
}
Again, it's not all that complex since this is a simple application, but you get the idea of how this works. This is the class that creates the concrete Entity Spaces classes that will return data to the service layer.
The only other class I have in this layer is a helper class for Views that need data from more than one entity.
using System;
using System.Collections.Generic;
using System.Text;
namespace ESNorthwind.MVC.Data
{
public class ProductsEditViewData
{
public Products Product { get; set; }
public SuppliersCollection Suppliers { get; set; }
public CategoriesCollection Categories { get; set; }
}
public class ProductsNewViewData
{
public SuppliersCollection Suppliers { get; set; }
public CategoriesCollection Categories { get; set; }
}
}
This is shoved into the code-behind of the Edit.aspx view to transport data to the view.
using System;
using System.Web;
using System.Web.Mvc;
using ESNorthwind.MVC.Data;
namespace ESNorthwind.MVC.Web.Views.Products
{
public partial class Edit : ViewPage<ProductsEditViewData>
{
}
}
In the Controller class, the Edit method uses this class that passes it along to the aspx page.
public object Edit(int id)
{
ProductsEditViewData viewData = new ProductsEditViewData();
Products product = service.GetProductById(id);
viewData.Product = product;
if (TempData.ContainsKey("ErrorMessage"))
{
foreach (var item in TempData)
{
ViewData[item.Key] = item.Value;
}
}
ViewData["CategoryID"] = new SelectList(service.GetCategories(), "CategoryID", "CategoryName", ViewData["CategoryID"] ?? product.CategoryID);
ViewData["SupplierID"] = new SelectList(service.GetSuppliers(), "SupplierID", "CompanyName", ViewData["SupplierID"] ?? product.SupplierID);
return View("Edit", viewData);
}
So that's about it. Feel free to comb through the code to see what I did and remember, I don't really know what I'm doing. So if you like this application, my name is King Wilder. If you don't, my name is John Smith.
I've been watching every video I can on the subject from Rob Conery, Scott Guthrie and Scott Hanselman, and I'm amazed at how lost I can get watching them sometimes. But other times I have a clarity that is overwhelming. I'd like to think this is one of those moments.
Thanks and if you have any comments, you can post them here, or in the Forums.
INSTALLATION
Click this link to download the application. ESNorthwind.MVC.zip (2.27 mb)
For Entity Spaces Users:
- simply unzip the application then add a reference to all the Entity Spaces DLL's to all four projects and generate the Custom and Generated classes to the Data project. Compile.
For non-Entity Spaces users:
- You must download the trial version of Entity Spaces and create the files necessary to run the application. You can find these instructions on the Entity Spaces web site. www.entityspaces.net
If you have any questions or comments you can post them here on the Blog or post your comment in the Forums, click on the Forums link at the top of this page.
Thanks.