ASP MVC 4 : WEBAPI / UNITOFWORK / RESPOSITORY AND IOC

A while back I published an article which discussed how to use the then WCF web API with UnitOfWork/Respository, and one user asked me how to do this with ASP MVC4, where the WCF Web API has now become the WebAPI, which you can use by creating a new ApiController. Its pretty simple. To do this lets consider a simple ApiController which is as follows:

public class ProductsController : ApiController
{
    private readonly IUnitOfWork unitOfWork;
    private readonly IRepository productRepository;

    public ProductsController(IUnitOfWork unitOfWork, IRepository productRepository)
    {
        if (unitOfWork == null) { throw new ArgumentNullException("unitOfWork"); }
        if (productRepository == null) { throw new ArgumentNullException("productRepository"); }

        this.unitOfWork = unitOfWork;
        this.productRepository = productRepository;
    }

    public IEnumerable GetAllProducts()
    {
        productRepository.EnrolInUnitOfWork(unitOfWork);
        return productRepository.FindAll().AsEnumerable();
    }

    public Product GetProduct(int id)
    {
        productRepository.EnrolInUnitOfWork(unitOfWork);
        Product item = productRepository.FindBy(x => x.Id == id).SingleOrDefault();
        if (item == null)
        {
            throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound));
        }
        return item;
    }

    public IEnumerable GetProductsByCategory(string category)
    {
        productRepository.EnrolInUnitOfWork(unitOfWork);
        return productRepository.FindBy(x=> string.Equals(x.Category, category, StringComparison.OrdinalIgnoreCase));
    }

    public HttpResponseMessage PostProduct(Product item)
    {
        productRepository.EnrolInUnitOfWork(unitOfWork);
        Product newItem = productRepository.Add(item);
        unitOfWork.Commit();
        var response = Request.CreateResponse(HttpStatusCode.Created, newItem);
        string uri = Url.Link("DefaultApi", new { id = item.Id });
        response.Headers.Location = new Uri(uri);
        return response;
    }

    public void PutProduct(int id, Product product)
    {
        productRepository.EnrolInUnitOfWork(unitOfWork);
        Product item = productRepository.FindBy(x => x.Id == id).SingleOrDefault();
        if (item != null)
        {
            item.Name = product.Name;
            item.Category = product.Category;
            item.Price = product.Price;
            unitOfWork.Commit();
        }
    }

    public HttpResponseMessage DeleteProduct(int id)
    {
        productRepository.EnrolInUnitOfWork(unitOfWork);
        Product item = productRepository.FindBy(x => x.Id == id).SingleOrDefault();
        if (item != null)
        {
            productRepository.Remove(item);
            unitOfWork.Commit();
        }
        return new HttpResponseMessage(HttpStatusCode.NoContent);
    }

}

Where you can clearly see this guy takes some dependenices which look like this

UnitOfWork
I am using a Entity Framework code first Unit of work abstraction. The general idea I was going for is that my repositories can involve in a UnitOfWork, and the UnitOfWork is committed when the work is well um done.

public interface IUnitOfWork : IDisposable
{
    void Commit();
    void Attach(T obj) where T : class;
    void Add(T obj) where T : class;
    IQueryable Get() where T : class;
    bool Remove(T item) where T : class;
}
public abstract class EfDataContextBase : DbContext, IUnitOfWork
{
    public IQueryable Get() where T : class
    {
        return Set();
    }

    public bool Remove(T item) where T : class
    {
        try
        {
            Set().Remove(item);
        }
        catch (Exception)
        {
            return false;
        }
        return true;
    }

    public void Commit()
    {
        base.SaveChanges();
    }

    public void Attach(T obj) where T : class
    {
        Set().Attach(obj);
    }

    public void Add(T obj) where T : class
    {
        Set().Add(obj);
    }
}
public class ProductContext : EfDataContextBase, IUnitOfWork
{
    public DbSet Products { get; set; }
}

Repository
I went for a generic repository, but you may find that you do need a specific repository if these simple methods do not satisfy your needs.

public interface IRepository where T : class
{
    void EnrolInUnitOfWork(IUnitOfWork unitOfWork);
    int Count { get; }
    T Add(T item);
    bool Contains(T item);
    void Remove(T item);
    IQueryable FindAll();
    IQueryable FindBy(Func<T, bool> predicate);
}
public class Repository : IRepository where T : class
{
    protected IUnitOfWork context;

    public void EnrolInUnitOfWork(IUnitOfWork unitOfWork)
    {
        this.context = unitOfWork;
    }

    public int Count
    {
        get { return context.Get().Count(); }
    }

    public T Add(T item)
    {
        context.Add(item);
        return item;
    }

    public bool Contains(T item)
    {
        return context.Get().FirstOrDefault(t => t == item) != null;
    }

    public void Remove(T item)
    {
        context.Remove(item);
    }

    public IQueryable FindAll()
    {
        return context.Get();
    }

    public IQueryable FindBy(Func<T, bool> predicate)
    {
        return context.Get().Where(predicate).AsQueryable();
    }
}

 

So how do these get satisfied?

Well that is pretty simple, we just need to have a MVC 4 IDependencyScope/IDependencyResolver inheriting class which looks like this. The important thing to note here is that we are creating a new container to service the request for the scope that we get told about by inheriting from IDependencyScope. I went with this approach rather than child containers, which are deprecated in Castle now. I also went for Castle due to its support for open generics, which I needed to have a single generic repository.

class CastleScopeContainer : IDependencyScope, IDependencyResolver
{
    protected IWindsorContainer container;

    public IDependencyScope BeginScope()
    {
        container = new WindsorContainer().Install(new[] { new ApiInstaller() }); ;
        return this;
    }


    public object GetService(Type serviceType)
    {
        try
        {
            return container.Resolve(serviceType);
        }
        catch(Exception ex)
        {
            return null;
        }
    }

    public IEnumerable<object> GetServices(Type serviceType)
    {
        try
        {
            var results = container.ResolveAll(serviceType);
            if (results == null)
                return new List<object>();

            return results.Cast<object>();
        }
        catch(Exception ex)
        {
            return new List<object>();
        }

    }

    public void Dispose()
    {
        container.Dispose();
    }
}

Where we configure a standard Castle Windsor IOC container using the following installer(s)

public class ApiInstaller : IWindsorInstaller
{
    public void Install(IWindsorContainer container, Castle.MicroKernel.SubSystems.Configuration.IConfigurationStore store)
    {
        container.Register
        (
            Component.For(typeof(IRepository<>)).ImplementedBy(typeof(Repository<>)).LifestyleTransient(),
            Component.For().ImplementedBy().LifestyleTransient(),
            Component.For().LifestyleTransient()
        );
    }
}
public class DDDInstaller : IWindsorInstaller
{
    public void Install(IWindsorContainer container, Castle.MicroKernel.SubSystems.Configuration.IConfigurationStore store)
    {
        container.Register
        (
            Component.For(typeof(IRepository<>)).ImplementedBy(typeof(Repository<>)).LifestyleTransient(),
            Component.For().ImplementedBy().LifestyleTransient()
        );
    }
}

And the following Global.asax.cs which is used to setup all the IOC requirements for both the WebApi and the ControllerFactory

public class WebApiApplication : System.Web.HttpApplication
{
    protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();

        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
        RouteConfig.RegisterRoutes(RouteTable.Routes);
        BundleConfig.RegisterBundles(BundleTable.Bundles);

        #region IOC
        //IOC : I like Castle because it has open generics

        //Web Api
        GlobalConfiguration.Configuration.DependencyResolver = new CastleScopeContainer();

        //Controllers
        IWindsorContainer container = new ContainerFactory().Install(
                        new DDDInstaller(),
                        new ControllerInstaller()
                    );
        ControllerBuilder.Current.SetControllerFactory(new CastleControllerFactory(container));
        #endregion

        //Seed database
        Database.SetInitializer(new ProductInitializer());

    }
}

We can also run a standard controller using IOC, which is easily achievable using a ControllerFactory something like the following:

public class CastleControllerFactory : DefaultControllerFactory
{
    private readonly IWindsorContainer container;

    public CastleControllerFactory(IWindsorContainer container)
    {
        this.container = container;
    }

    protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
    {
        if (controllerType == null)
        {
            return base.GetControllerInstance(requestContext, null);
        }

        object controller= container.Resolve(controllerType);
        if (controller != null)
        {
            return (IController)controller;
        }

        return base.GetControllerInstance(requestContext, null);
    }

    public override void ReleaseController(IController controller)
    {
        var disposable = controller as IDisposable;

        if (disposable != null)
        {
            disposable.Dispose();
        }
        container.Release(controller);
    }
}

Where the ControllerInstaller we previously used in global.asax.cs looks like this

public class ControllerInstaller : IWindsorInstaller
{
    public void Install(IWindsorContainer container, IConfigurationStore store)
    {
        container.Register(
            AllTypes.FromAssembly(Assembly.GetExecutingAssembly())
                .BasedOn().LifestylePerWebRequest());
    }
}

Where we may have a controller that looks like this, where we also have the goodness of IOC resolves dependencies in our controllers.

public class HomeController : Controller
{
    private readonly IUnitOfWork unitOfWork;
    private readonly IRepository productRepository;

    public HomeController(IUnitOfWork unitOfWork, IRepository productRepository)
    {
        if (unitOfWork == null) { throw new ArgumentNullException("unitOfWork"); }
        if (productRepository == null) { throw new ArgumentNullException("productRepository"); }

        this.unitOfWork = unitOfWork;
        this.productRepository = productRepository;
    }

    private IEnumerable GetAllProducts()
    {
        productRepository.EnrolInUnitOfWork(unitOfWork);
        return productRepository.FindAll().AsEnumerable();
    }

    public ActionResult Index()
    {
        return View();
    }

    public ActionResult ShowDetails()
    {
        ViewData["products"] = string.Format("There are currently {0} Products", GetAllProducts().Count());
        return View();
    }
}

I havea small demo project which shows a work demo of all of this available at : http://dl.dropbox.com/u/2600965/Blogposts/2012/07/ProductStoreMVC4Demo.zip

About these ads

5 thoughts on “ASP MVC 4 : WEBAPI / UNITOFWORK / RESPOSITORY AND IOC

  1. Rory says:

    I know it’s just an example you’ve posted but……

    Not sure about bringing the repository into the controller, you’ll start to get a “Fat Controller” before you know it. Don’t bring models into the Controller. Better to inject a service that uses a ViewModel.
    Even better use JSON going up and coming down, so to speak. Especially with the number of fantastic 3rd party frontend libs available these days.

    I’d be concerned that you cannot unit test controllers, services, repos in isolation with your design, once again very busy controllers you have there. Rule of thumb, “controllers are just routers”

    I’d also use Async controllers / TPL for I/O bound stuff e.g. services calls to databases and WCF/Web Services. Get off the ASP.NET thread pronto.

    Also, much neater with NInject rather than Castle, but again personal pref. Castle has a lot of code you have to implement, NInject all done for you.

    You might want to check this guys UofW/Repo

    http://huyrua.wordpress.com/2010/08/25/specification-pattern-in-entity-framework-4-revisited/

    We took his code and added to it, it’s a very good base.

    • sachabarber says:

      Rory

      I hear what you are saying about the service idea, its something Paul Stovell mentioned too a while back : http://www.paulstovell.com/clean-aspnet-mvc-controllers, that said I don’t feel my controller is that busy but hey. I agree it would be even better with a service to deal with the data.

      Ill have to have a look at NInject (personal choice though I know Castle)

      Thanks for link on UofW/Repo ill check it out.

    • sachabarber says:

      Rory

      I looked at that link you shared, excellent article actually. I think his and mine are not that different. He has obviously done more than me, but the fundamental ideas are the same.

      Still thanks for that link

  2. Steve says:

    Great job and appreciate you covering this topic. I tried to extend your example by adding an Entity Model and generating the required classes using a slightly modified T4 template. For some reason, no matter what I have tried the server continually returns an internal error when I try to the return value from ‘/api/Projects’.

    I greatly appreciate help on resolving my issue.

    I modified EfDataContextBase to be accomidate both your model and my model:
    namespace ProductStore
    {
    public abstract class EfDataContextBase : DbContext, IUnitOfWork
    {

    public EfDataContextBase(string nameOrConnectionString)
    : base(nameOrConnectionString)
    {
    }

    public EfDataContextBase()
    {
    }
    ….

    I got the following class for the DbContext:
    namespace ProductStore.Models
    {
    public partial class MyEntities : EfDataContextBase, IUnitOfWork //DbContext
    {
    public MyEntities ()
    : base(“name= MyEntities”)
    {
    }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
    throw new UnintentionalCodeFirstException();
    }

    public DbSet Customers { get; set; }
    public DbSet Networks { get; set; }
    public DbSet Projects { get; set; }

    ………………….

    I decided to start with just one class and controller named Projects:
    namespace ProductStore.Models
    {
    public partial class Project
    {
    public Project()
    {
    this.ProjectNetworks = new HashSet();
    }

    public int ProjectID { get; set; }
    public int CustomerID { get; set; }
    public string Definition { get; set; }
    public string DefinitionKey { get; set; }
    …..

    I simplified the ApiController to try to eliminate any erros from Castle:
    public class ProjectsController : ApiController
    {
    private IUnitOfWork unitOfWork;
    private IRepository projectRepository;

    public ProjectsController() {
    unitOfWork = new Models.MyEntities();
    projectRepository = new Repository();

    if (unitOfWork == null) { throw new ArgumentNullException(“unitOfWork”); }
    if (projectRepository == null)
    { throw new ArgumentNullException(“projectRepository”); }
    }

    public IEnumerable GetAllProjects()
    {
    projectRepository.EnrolInUnitOfWork(unitOfWork);
    return projectRepository.FindAll().AsEnumerable();
    }
    ….

    • sachabarber says:

      To be honest I would not know what is up just from looking at a comment here and am up to my eye balls busy.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s