Asp.net-mvc – Using Entity Framework with Castle Windsor

asp.net-mvccastle-windsorentity-frameworkinversion-of-control

I use the Entity Framework database-first approach to generate a DbContext / POCO model for an MVC application. I want to avoid having dependencies on DbContext in my controllers to enable me to switch to another persistence provider as I need to (for example for unit testing purposes).

To do this I want to use the Castle Windsor IoC container. I plan to register DbContext as an IUnitOfWork service and to register a generic IRepository service, implementations of which I will use to access and work with aggregate roots in the model.

I'm new to Windsor and haven't been able to find much info about using it with EF, and I have a couple of questions:

  • is this a reasonable approach if I want to decouple EF from the application?
  • how do I install / register the IUnitOfWork and generic IRepository services?

Best Answer

So, some conclusions. I thought I'd write this up in case it is of use to someone else trying to use / unit test EF, Windsor and MVC together.

First of all, as DbContext implements both the Repository and Unit of Work patterns, you need to take view on whether these implementations will serve or whether you need to create your own.

I chose to create my own Repository, following the DDD pattern: one per aggregate root. The reasons: to encapsulate query code, to prevent it from leaking into the application layer, and to be able to mock more easily when testing the application controllers. I created a generic repository based on IRepository<TEntity>. There are plenty of examples out there. I found this a good one: http://architects.dzone.com/articles/implementing-repository

On the other hand I decided to drop the IUnitOfWork service, opting for the default implementation instead. However, I created an IDbContext abstraction (don't know why Microsoft didn't do this themselves), so that I could mock the DbContext when testing the Repository services.

I gave IDbContext only the members of DbContext that I wanted to use in the repository. So:

public interface IDbContext: IDisposable
{
    Database Database { get; }
    DbEntityEntry Entry(object entity);
    IDbSet<TEntity> Set<TEntity>() where TEntity : class;
    int SaveChanges();
}

I then created a Windsor facility and installer for my IDbContext and IRepository services:

public class EntityFrameworkFacility: AbstractFacility
{
    protected override void Init()
    {
        Kernel.Register(Component.For<IDbContext>()
                                 .ImplementedBy<MyEntities>()
                                 .LifestylePerWebRequest(),
                        Component.For(typeof(IRepository<>))
                                 .ImplementedBy(typeof(Repository<>))
                                 .LifestylePerWebRequest());
    }
}

public class PersistenceInstaller : IWindsorInstaller
{
    public void Install(IWindsorContainer container, IConfigurationStore store)
    {
        container.AddFacility<EntityFrameworkFacility>();
    }
}

The final piece was to extend the Entity Framework context class to implement IDbContext, and to shadow the Set() method to return IDbSet rather than DbSet:

public partial class MyEntities : IDbContext
{
    public new IDbSet<TEntity> Set<TEntity>() where TEntity : class
    {
        return base.Set<TEntity>();
    }
}

With all this in place (and the ControllerFactory registration illustrated in the Windsor docs), it becomes trivial to get Windsor to inject IRepository objects (or IDbContext) into controller constructors, as required:

public ControllerBase(IRepository<Contact> repo)
{
    _repo = repo;
}

In the Repository unit tests, a real repository instance can be backed with a mock IDbContext:

mocks = new MockRepository();
context = mocks.StrictMock<IDbContext>();
repo = new Repository<Contact>(context);

In Controller unit tests, a mock repository can be used:

mocks = new MockRepository();
repo = mocks.StrictMock<IRepository<Contact>>();
ContactController controller = new ContactController(repo);
Related Topic