Rebuilding imar.spaanjaars.com in ASP.NET MVC 2 - Part 4 - Repositories, Testability and Inversion of Control
UPDATE: Links to the MVC site on the URL mvc.spaanjaars.com are no longer active, now that the site is live at imar.spaanjaars.com.
I just uploaded a new version of my web site in MVC to mvc.spaanjaars.com. This new version has the following features:
- All data access is now done with repositories (using Entity Framework at run-time).
- My Controllers and repositories are fully testable.
- I am using Castle Windsor as my Dependency Injection tool to inject concrete repositories into my controllers at run-time or during testing.
Some of the main USPs of the MVC framework are separation of concerns and testability. So when rebuilding my web site in MVC, I kept these principles in mind. One area where that is really useful is in the data access part of the application. Rather than tying my controllers to the database or data access layer directly, I am injecting interfaces for data access into my controllers at run time. These interfaces in turn provide access to repository interfaces. I then have a bunch of concrete classes that provide real data at run-time, and test data during testing. I'll show you some of the code for the repositories first, followed by a discussion on using Castle Windsor for the dependency injection. The second half of the article shows you how to use extension methods to create behavior that is shared between real and fake implementations of your repository classes.
Using Repositories
NOTE: at the end of the article you can download a full, working example of the concepts presented in this article. You should realize that the sample application is just that: a sample. In a real-world application you need a lot more than what is presented in this article. Additionally, the code does not necessarily show best practices for MVC applications as it's focus is on an implementation of the Repository pattern using extension methods. The database for the sample application contains a bunch of fake categories and articles. If they don't look like true categories and content articles you may find on a web site like mine, but think they look more like products: that's because I generated the records automatically using Red Gate's Data Generator as explained here. Also note: the application is using MVC 2 and Entity Framework 4; as such you need Visual Studio 2010 to compile and run it. You probably could downgrade the code and recreate the model in VS 2008, but I haven't tried that yet.
One of the ideas behind the Repository pattern is to have a repository for each aggregate root in your model. You'll find a great explanation of the pattern and concrete examples in this article in the NHibernate FAQ. One of the ideas presented in that article is to have a generic IRepository interface that could look like this:
public interface IRepository<T> { T GetById(int id); void Add(T entity); void Remove(T entity); }
You can then use this interface for a concrete implementation on, say, a Product like this:
public class ProductRepository : IRepository<Product> { public Product GetById(int id) { // Implementation here } public void Add(Product entity) { // Implementation here } public void Remove(Product entity) { // Implementation here } }
This works excellent, and enables you to do something like this in your code:
ProductRepository myProductRepository = new ProductRepository(); Product myProduct = myProductRepository.GetById(23);
What I personally find difficult about this "repository per aggregate root" concept is the fact that in your application, things are not always as disconnected as each aggregate root seems to indicate. Sometimes you do need a Customer from the CustomerRepository and associate it with an Order from an OrderRepository. This isn't a big deal when dealing with, say, test data in fake repositories or when working with your own data access classes, but it does become problematic with Entity Framework or other ORM frameworks that need to keep track of these entities from the same underlying mechanism. If you try to associate an entity from one EF context with another entity from another context you get an error such as the following:
The relationship between the two objects cannot be defined because they are attached to different ObjectContext objects.
So you need to find a way to share the ObjectContext from EF and still separate your aggregate roots. To solve this problem for the MVC version of my web site, I am using an IRepositoryWrapper interface whose concrete implementations can hold an instance of a single ObjectContext and provides access to a number of Repositories that get a reference to this ObjectContext. To show you how this works, I created a simple web application using ASP.NET MVC. The application is an extremely simple web site that shows articles in specific categories. Figure 1 shows the database model for the application.
Figure 1
The application has an MVC View that displays a list of categories. Once you click a category you get a new View that displays all articles for that category. In the remainder of this article I'll show you the underlying model and design.
First, take a look at a part of the (simplified) class diagram of my application, shown in Figure 2.
Figure 2
I'll get to the IRepositoryWrapper later, but for now focus on the two interfaces on the left. IGenericRepository<T> is a variation of the interface you saw earlier and defines the base services a concrete implementation needs to supply: All, Add and Remove. For simple aggregate roots, like Category, this is more than enough. As an example, I also added IArticleRepository that inherits IGenericRepository<Article> and adds one extra method: PurgeDeletedItems that deletes items marked for deletion from the database. This just serves as an example. You could, and probably should, implement PurgeDeletedItems as an extension method as explained later in this article.
Inside my Model class library, there are two concrete repositories: ArticleRepository and CategoryRepository:
Figure 3
You can see that each of these concrete classes implements the members defined by the interfaces they inherit. Additionally, you can see they have a field called _entities which is of type MvcDemoEntities, the Entity Framework ObjectContext instance used in my application. The concrete implementation uses these entities for data access. For example, Remove in the ArticleRepository looks like this:
public void Remove(Article item) { Article article = _entities.Articles.Where(c => c.Id == item.Id).FirstOrDefault();
if (article != null)
{
article.Deleted = true;
}
}
The concrete repositories don't create this instance themselves; instead, they get a reference to the ObjectContext passed to them in their constructor like this:
public ArticleRepository(MvcDemoEntities entities) { _entities = entities; }
So who instantiates the ObjectContext? The concrete implementation of IRepositoryWrapper does. Take another look at Figure 2 and notice how the IRepositoryWrapper has two properties: Articles and Categories, of type IArticleRepository and IGenericRepository<Category> respectively. The concrete implementation of this class looks like this:
public class RepositoryWrapper : IRepositoryWrapper { MvcDemoEntities _entities = new MvcDemoEntities(); private CategoryRepository _categories; private ArticleRepository _articles; public IGenericRepository<Category> Categories { get { if (_categories == null) { _categories = new CategoryRepository(_entities); } return _categories; } } public IArticleRepository Articles { get { if (_articles == null) { _articles = new ArticleRepository(_entities); } return _articles; } } public int SaveChanges() { return _entities.SaveChanges(); } }
The two properties are lazy loaded and instantiate a repository when requested and pass it the _entities ObjectContext. This in turn means the repositories all share the same ObjectContext enabling you to share entities across repositories. With this setup, you can now do stuff like this in a Controller method:
RepositoryWrapper _repositoryWrapper = new RepositoryWrapper(); IEnumerable<Article> allArticles = _repositoryWrapper.Articles.All; return View(activeArticles);
Likewise, you can access _repositoryWrapper.Categories to get access to the categories in the system.
This is all fine, but doesn't really promote testability of your repositories. Also, since the Controller is instantiating the RepositoryWrapper directly, it's not easy to swap the RepositoryWrapper for another implementation. Finally, it's now pretty difficult to test your Controllers as they are tied to the concrete RepositoryWrapper that in turn uses the Entity Framework and a live database for all data access. Fortunately, the code I've shown you lays out a nice foundation to improve on, using Castle Windsor, a few Fake classes and a whole bunch of extensions methods.
Testing Your Application
The first step in making your Controllers testable is by removing the dependency on the repositories; in particular the dependency on the RepositoryWrapper. This can easily be fixed in a few steps:
- Download, unzip and reference Castle Windsor in your web and test project.
- Add configuration information to the config files to tell Windsor which class to instantiate for which interface. For example, in Web.config for my MVC application I have this:
<castle> <components> <component id="RepositoryWrapper" service="MvcDemo.Model.IRepositoryWrapper, MvcDemo.Model" type="MvcDemo.Model.RepositoryWrapper, MvcDemo.Model" lifestyle="transient" /> </components> </castle>
Likewise, in app.config of my Test project I have this:
<castle> <components> <component id="RepositoryWrapper" service="MvcDemo.Model.IRepositoryWrapper, MvcDemo.Model" type="MvcDemo.Model.FakeRepositoryWrapper, MvcDemo.Tests" lifestyle="transient" /> </components> </castle>
In other words, I want Castle Windsor to instantiate a concrete RepositoryWrapper in my MVC application, and an instance of FakeRepositoryWrapper in my test project whenever an instance of IRepositoryWrapper is required.
- To tell your MVC application to instantiate controllers through Windsor you need to add the following class to your project:
public class WindsorControllerFactory : DefaultControllerFactory
{
WindsorContainer container; public WindsorControllerFactory() { container = new WindsorContainer(new XmlInterpreter( new ConfigResource("castle"))); var controlerTypes = from t in Assembly.GetExecutingAssembly().GetTypes() where typeof(IController).IsAssignableFrom(t) select t; foreach (Type t in controlerTypes) { container.AddComponentLifeStyle(t.FullName, t, LifestyleType.Transient); } } protected override IController GetControllerInstance( RequestContext requestContext, Type controllerType) { if (controllerType == null) { return null; } else { return (IController)container.Resolve(controllerType); } } }
- To tell the MVC framework you want to use a different ControllerFactory, you need to add the following code to Application_Start in the Global.asax file:
ControllerBuilder.Current.SetControllerFactory(new WindsorControllerFactory());
IRepositoryWrapper _repositoryWrapper; public ArticlesController(IRepositoryWrapper repositoryWrapper) { _repositoryWrapper = repositoryWrapper; }
This effectively removes the dependency between the controller and the concrete RepositoryWrapper you saw in an earlier code example. Instead, the controller now accepts anything that implements IRepositoryWrapper. The code in the Controller method can now be simplified to:
IEnumerable<Article> allArticles = _repositoryWrapper.Articles.All; return View(activeArticles);
While this seems like a lot of work, it opens up a number of interesting possibilities. For example, you can now test your Controllers by doing the following:
- Create concrete fake classes of IRepositoryWrapper, IGenericWrapper<Category> and IArticleRepository that are specific to testing.
- Implement the relevant members in these classes. For example, the All property in FakeArticleRepository looks like this:
public IQueryable<Article> All
{
get
{
return _tempList.AsQueryable();
}
}
public FakeArticleRepository()
{
_tempList = new List<Article>();
for (int i = 0; i < 100; i++)
{
Random myRandom = new Random();
_tempList.Add(new Article()
{
Id = i,
CategoryId = myRandom.Next(4),
Summary = "Summary " + i.ToString(),
Deleted = i % 2 == 0,
Body = "Body " + i.ToString(),
CreateDateTime = DateTime.Now,
UpdateDateTime = DateTime.Now
});
}
}
using (ArticlesController articlesController = new ArticlesController(RepositoryWrapper)) { var result = articlesController.List(1); // Get articles in category 1 Assert.IsNotNull(result); var returnedItems = (result.ViewData.Model as IEnumerable<Article>).ToList(); returnedItems.ForEach(a => Assert.IsFalse(a.Deleted)); returnedItems.ForEach(a => Assert.IsTrue(a.CategoryId == 1)); }
public class BaseTestClass
{
protected IRepositoryWrapper RepositoryWrapper;
public BaseTestClass()
{
using (IWindsorContainer container = new WindsorContainer(new XmlInterpreter(new ConfigResource("castle"))))
{
RepositoryWrapper = container.Resolve<IRepositoryWrapper>();
}
}
}
Making Your Tests More Reliable
At this point you may be thinking: "Now wait a minute; isn't he just testing his fakes, and not his real repositories?" Yes, to some extent you would be right. For example, imagine my IGenericRepository<T> had a member called GetById(int id) that was implemented in the real and fake repositories as follows (note the faulty implementation of GetById in the real implementation):
// Fake implementation public Article GetById(int id) { return _tempList.Find(a => a.Id == id); } // Real implementation public Article GetById(int id) { return _entities.Articles.Where(a => a.Id > id).First(); }
If this was real code, and I was testing against my FakeArticleRepository, I would never catch this bug. The Fake would return the correct article, but at run-time I would receive the first article with an ID higher than the one requested. To prevent these kind of issues where there is a mismatch between your fake and real implementations, I haven't added members like GetById or GetByCategoryId to the interfaces and real implementations. Instead, I have created a bunch of extension methods on the respective repository interfaces that do all the repository logic such as querying for me. Because both the real and the fake repositories implement this interface, they get access to these extension methods which in turn means they exhibit the exact same behavior when run. I pretty much dumbed down the repository interfaces to "a bunch of objects of type T" with the All property and let the extension methods do the hard work. I can trust a property such as All to be reliable across both implementations as it requires little to no code. But when it comes to querying or other logic, an error is easily made in one of the two concrete providers, so it's good to move te logic to a shared location that multiple concrete implementations can use. To see what I mean, consider these two methods in an Extensions class:
public static IQueryable<Article> GetActive(this IArticleRepository repository)
{
return repository.All.Where(c => !c.Deleted);
}
public static Article GetById(this IArticleRepository repository, int id)
{
return (from c in repository.GetActive()
where c.Id == id
select c).FirstOrDefault();
}
GetActive makes sure I am not returning items that have been marked as deleted. GetById in turn uses GetActive to get all non-deleted items and then queries the one with the ID requested. With the real implementation using EF, I get a composable query that executes a simple and clean SQL statement against the database. With the Fake implementation, I get the same, but instead of targeting a (slow) database, I query the in-memory representation of my fake list of articles. Either way, I am testing the implementation of GetById, without needing a real database at test time. This makes it pretty easy to test your repositories and your Controllers using fake objects and fake data, while still being able to true test runtime code.
Wrapping Up
Pfffeew, I covered a lot in a relatively short article. Let's briefly recap the main topics:
- Using repositories is a good thing to create maintainable and testable applications.
- While having a repository per aggregate root is a good thing too, when using an ORM such as EF you need a mechanism to share the ObjectContext between repositories.
- By using a RepositoryWrapper you can achieve two main goals:
- You can let multiple repositories share a single instance of the ObjectContext
- You provide easy access to your various repositories. This means you can do stuff like MyRepositoryWrapper.Articles.GetById(23) and MyRepositoryWrapper.Categories.GetById(3) to get articles and categories. IntelliSense will help you find the right repository and help you find the right methods.
- By embedding all your querying and other repository logic in the concrete implementation of your repository, you run the risk of introducing bugs you cannot test for. Since you're really testing the Fake implementation of your repository, bugs in the real repository may go unnoticed.
- By moving much of your repository logic to extension methods you can easily reuse that behavior in both real and fake implementations, making tests more real and reliable.
I haven't covered each and every piece of code necessary to make these concepts work. However, the download that comes with this article has a fully working, and testable application that shows all major concepts. I haven't written a test for each possible scenario, but instead wrote just a few tests to demonstrate the main concepts.
Since this is a work in progress, I am looking forward to a discussion on this topic. Let me know what you think of this using the Comment feature at the end of this article.
References
Downloads
Where to Next?
Wonder where to go next? You can post a comment on this article.
Links in this Document
Doc ID | 525 |
Full URL | https://imar.spaanjaars.com/525/rebuilding-imarspaanjaarscom-in-aspnet-mvc-2-part-4-repositories-testability-and-inversion-of-control |
Short cut | https://imar.spaanjaars.com/525/ |
Written by | Imar Spaanjaars |
Date Posted | 03/28/2010 17:58 |
Comments
Talk Back! Comment on Imar.Spaanjaars.Com
I am interested in what you have to say about this article. Feel free to post any comments, remarks or questions you may have about this article. The Talk Back feature is not meant for technical questions that are not directly related to this article. So, a post like "Hey, can you tell me how I can upload files to a MySQL database in PHP?" is likely to be removed. Also spam and unrealistic job offers will be deleted immediately.
When you post a comment, you have to provide your name and the comment. Your e-mail address is optional and you only need to provide it if you want me to contact you. It will not be displayed along with your comment. I got sick and tired of the comment spam I was receiving, so I have protected this page with a simple calculation exercise. This means that if you want to leave a comment, you'll need to complete the calculation before you hit the Post Comment button.
If you want to object to a comment made by another visitor, be sure to contact me and I'll look into it ASAP. Don't forget to mention the page link, or the Doc ID of the document.