I can say that this code is good enough for the first time try but it does have some places to improve.
Let's go through some of them.
1. Dependency injection (DI) and usage of IoC.
You use the simplest version of Service Locator pattern - container
instance itself.
I suggest you use 'constructor injection'. You can find more information here (ASP.NET MVC 4 Dependency Injection).
public class CustomerController : Controller
{
private readonly IUnitOfWork unitOfWork;
private readonly ICustomerRepository customerRepository;
public CustomerController(
IUnitOfWork unitOfWork,
ICustomerRepository customerRepository)
{
this.unitOfWork = unitOfWork;
this.customerRepository = customerRepository;
}
public ActionResult List()
{
return View(customerRepository.Query());
}
[HttpPost]
public ActionResult Create(Customer customer)
{
customerRepository.Add(customer);
unitOfWork.SaveChanges();
return RedirectToAction("List");
}
}
2. Unit of Work (UoW) scope.
I can't find lifestyle of IUnitOfWork
and ICustomerRepository
. I am not familiar with Unity but msdn says that TransientLifetimeManager is used by default. It means that you'll get a new instance every time when you resolve type.
So, the following test fails:
[Test]
public void MyTest()
{
var target = new UnityContainer();
target.RegisterType<IUnitOfWork, EntityFrameworkUnitOfWork>();
target.RegisterType<ICustomerRepository, CustomerRepository>();
//act
var unitOfWork1 = target.Resolve<IUnitOfWork>();
var unitOfWork2 = target.Resolve<IUnitOfWork>();
// assert
// This Assert fails!
unitOfWork1.Should().Be(unitOfWork2);
}
And I expect that instance of UnitOfWork
in your controller differs from the instance of UnitOfWork
in your repository. Sometimes it may be resulted in bugs. But it is not highlighted in the ASP.NET MVC 4 Dependency Injection as an issue for Unity.
In Castle Windsor PerWebRequest lifestyle is used to share the same instance of type within single http request.
It is common approach when UnitOfWork
is a PerWebRequest component. Custom ActionFilter
can be used in order to invoke Commit()
during invocation of OnActionExecuted()
method.
I would also rename the SaveChanges()
method and call it simply Commit
as it is called in the example and in the PoEAA.
public interface IUnitOfWork : IDisposable
{
void Commit();
}
3.1. Dependencies on repositories.
If your repositories are going to be 'empty' it is not needed to create specific interfaces for them. It is possible to resolve IRepository<Customer>
and have the following code in your controller
public CustomerController(
IUnitOfWork unitOfWork,
IRepository<Customer> customerRepository)
{
this.unitOfWork = unitOfWork;
this.customerRepository = customerRepository;
}
There is a test that tests it.
[Test]
public void MyTest()
{
var target = new UnityContainer();
target.RegisterType<IRepository<Customer>, CustomerRepository>();
//act
var repository = target.Resolve<IRepository<Customer>>();
// assert
repository.Should().NotBeNull();
repository.Should().BeOfType<CustomerRepository>();
}
But if you would like to have repositories that are 'layer of abstraction over the mapping layer where query construction code is concentrated.' (PoEAA, Repository)
A Repository mediates between the domain and data mapping layers,
acting like an in-memory domain object collection. Client objects
construct query specifications declaratively and submit them to
Repository for satisfaction.
3.2. Inheritance on EntityFrameworkRepository.
In this case I would create a simple IRepository
public interface IRepository
{
void Add(object item);
void Remove(object item);
IQueryable<T> Query<T>() where T : class;
}
and its implementation that knows how to work with EntityFramework infrastructure and can be easily replaced by another one (e.g. AzureTableStorageRepository
).
public class EntityFrameworkRepository : IRepository
{
public readonly EntityFrameworkUnitOfWork unitOfWork;
public EntityFrameworkRepository(IUnitOfWork unitOfWork)
{
var entityFrameworkUnitOfWork = unitOfWork as EntityFrameworkUnitOfWork;
if (entityFrameworkUnitOfWork == null)
{
throw new ArgumentOutOfRangeException("Must be of type EntityFrameworkUnitOfWork");
}
this.unitOfWork = entityFrameworkUnitOfWork;
}
public void Add(object item)
{
unitOfWork.GetDbSet(item.GetType()).Add(item);
}
public void Remove(object item)
{
unitOfWork.GetDbSet(item.GetType()).Remove(item);
}
public IQueryable<T> Query<T>() where T : class
{
return unitOfWork.GetDbSet<T>();
}
}
public interface IUnitOfWork : IDisposable
{
void Commit();
}
public class EntityFrameworkUnitOfWork : IUnitOfWork
{
private readonly DbContext context;
public EntityFrameworkUnitOfWork()
{
this.context = new CustomerContext();
}
internal DbSet<T> GetDbSet<T>()
where T : class
{
return context.Set<T>();
}
internal DbSet GetDbSet(Type type)
{
return context.Set(type);
}
public void Commit()
{
context.SaveChanges();
}
public void Dispose()
{
context.Dispose();
}
}
And now CustomerRepository
can be a proxy and refer to it.
public interface IRepository<T> where T : class
{
void Add(T item);
void Remove(T item);
}
public abstract class RepositoryBase<T> : IRepository<T> where T : class
{
protected readonly IRepository Repository;
protected RepositoryBase(IRepository repository)
{
Repository = repository;
}
public void Add(T item)
{
Repository.Add(item);
}
public void Remove(T item)
{
Repository.Remove(item);
}
}
public interface ICustomerRepository : IRepository<Customer>
{
IList<Customer> All();
IList<Customer> FindByCriteria(Func<Customer, bool> criteria);
}
public class CustomerRepository : RepositoryBase<Customer>, ICustomerRepository
{
public CustomerRepository(IRepository repository)
: base(repository)
{ }
public IList<Customer> All()
{
return Repository.Query<Customer>().ToList();
}
public IList<Customer> FindByCriteria(Func<Customer, bool> criteria)
{
return Repository.Query<Customer>().Where(criteria).ToList();
}
}