Click here to Skip to main content
15,884,298 members
Articles / Web Development / ASP.NET

Reducing Controller dependencies with generic factories

Rate me:
Please Sign up or sign in to vote.
5.00/5 (3 votes)
5 Jan 2016CPOL5 min read 10.2K   8  
Article examining one approach to reducing controller dependencies in an ASP.NET MVC application by using generic factories.

For those of us new to Dependency Injection, a common question to ask is:

"How many dependencies is too many?"

Coupled with, and soon following this question, comes:

"How can I reduce this number?"

With Controllers, we tend to focus on dependencies that pass data to and fro. We first take in, or "inject", interfaces into our Controller. We can then call the methods we need within our Controller logic. In this post I’ll discuss the use of generic factories for reducing Controller dependencies.

In my previous post, Simplify Controller logic using factories, I discussed using factories to create ViewModels. To recap, we moved ViewModel creation from the Controller to a factory. One specific factory handles creating and populating one specific ViewModel. By doing this, the factory becomes a dependency for the Controller. Each factory derives from an interface. This means our controller knows nothing of the concrete implementation that the factory uses. We inject the interface into our Controller via the constructor. When we need to pass a ViewModel to a view, we ask the relevant factory to create it for us. If we're creating lots of ViewModels we can end up with lots of dependencies.

Let's look at an example Controller which is starting to get a bit busy. In this example we're creating a product catalogue. Our main Controller is a ProductController. It allows us to view a full list of products and details of individual products. It also has some admin functions, meaning that we can manage, add and edit products. Here's the code:

public class ProductController : Controller
{
        public ActionResult Add()
        {
            // TODO: create ProductAddViewModel somehow
            return View();
        }

        public ActionResult Display(int productId)
        {
            // TODO: create ProductDisplayViewModel somehow
            return View();
        }

        public ActionResult Edit(int productId)
        {
            // TODO: create ProductEditViewModel somehow
            return View();
        }

        public ActionResult List()
        {
            // TODO: create ProductListViewModel somehow
            return View();
        }

        public ActionResult Manage()
        {
            // TODO: create ProductManageViewModel somehow
            return View();
        }
}

Each of these 5 Controller actions passes a different ViewModel to a different view. Each ViewModel requires its own factory. If we inject 5 factories into our Controller, it might look something like this:

public class ProductController : Controller
{
        private readonly IProductAddViewModelFactory productAddViewModelFactory;
        private readonly IProductDisplayViewModelFactory productDisplayViewModelFactory;
        private readonly IProductEditViewModelFactory productEditViewModelFactory;
        private readonly IProductListViewModelFactory productListViewModelFactory;
        private readonly IProductManageViewModelFactory productManageViewModelFactory;

        public ProductController(IProductAddViewModelFactory productAddViewModelFactory, 
            IProductDisplayViewModelFactory productDisplayViewModelFactory, IProductEditViewModelFactory productEditViewModelFactory, 
            IProductListViewModelFactory productListViewModelFactory, IProductManageViewModelFactory productManageViewModelFactory)
        {
            this.productAddViewModelFactory = productAddViewModelFactory;
            this.productDisplayViewModelFactory = productDisplayViewModelFactory;
            this.productEditViewModelFactory = productEditViewModelFactory;
            this.productListViewModelFactory = productListViewModelFactory;
            this.productManageViewModelFactory = productManageViewModelFactory;
        }
}

It would be much better if we could replace these 5 dependencies with one. We still need a factory per ViewModel. We just defer the decision about which factory to use until ViewModel creation time. How do we do this? Through the magic of generics.

It's time to constrain our ViewModels

For this to work, we need a base type for all our ViewModels. This allows us to constrain the type of thing that our factories can create.

public abstract class ViewModel 
{
}

Now that we have a base type for our ViewModels we can create the factory interfaces. We need both a generic factory and a non-generic factory for this to work. We'll see why in a moment. Here's the generic factory interfaces:

public interface IViewModelFactory<out TViewModel>
{
    TViewModel Create();
}

public interface IViewModelFactory<in TInput, out TViewModel>
{
    TViewModel Create(TInput input);
}

We use the first of these when we need a ViewModel that isn’t pre-populated. We use the second when we're populating the ViewModel from a database (for example). In our example, the first interface is for our Add, List and Manage ViewModels. The second is for our Edit and Display ViewModels.

Now for the special, non-generic factory interface. This is the interface that we inject wherever we need to create ViewModels.

public interface IViewModelFactory
{
    TViewModel Create<TViewModel> () where TViewModel : ViewModel, new();

    TViewModel Create<TInputModel, TViewModel> (TInputModel input) where TViewModel : ViewModel, new();
}

The first thing to notice is that it has 2 Create signatures. These correlate exactly to the 2 generic interfaces above. As we'll see in a moment, we can use some DI Container wizardry to call the correct factory.

Time to meet the DI Container

Let’s see how this would work. In our examples we’ll be using the popular DI Container, Autofac. If you’re not familiar with Autofac, they have great documentation. Here's some links to help you get started:

The first thing we’ll look at is the implementation of our non-generic ViewModelFactory.

using Autofac;

public class ViewModelFactory : IViewModelFactory
{
    private readonly IContainer container;

    public ViewModelFactory(IContainer container)
    {
        this.container = container;
    }

    public TViewModel Create<TViewModel> () where TViewModel : ViewModel, new()
    {
        var factory = container.Resolve<IViewModelFactory<TViewModel>>();
        var model = factory.Create();
        return model;
    }

    public TViewModel Create<TInputModel, TViewModel> (TInputModel input) where TViewModel : ViewModel, new()
    {
        var factory = container.Resolve<IViewModelFactory<TInputModel, TViewModel>>();
        var model = factory.Create(input);
        return model;
    }
}

So what’s going on here? We use the Autofac container to resolve our factory and call its Create method. We’re letting the container decide which factory implementation to use when we call Create. Bear in mind that this happens at runtime. We need to provide a factory with matching generic arguments. If we don’t provide one, the solution will still compile. We will get a runtime error when we fire it up in a browser though.

Now that we have our factory we need to register it with the container. Autofac can only wire up our dependencies for us if we tell it where to find them. Registration happens once, during application start. I'm going to assume we have set Autofac up in the correct fashion. We need to register our factories within Global.asax.

using System.Web;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;
using Autofac;
using CommandQuery.Factories;

public class MvcApplication : HttpApplication
{
    protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();
        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
        RouteConfig.RegisterRoutes(RouteTable.Routes);
        BundleConfig.RegisterBundles(BundleTable.Bundles);

        var builder = RegisterDependencies();
        var container = builder.Build();
        // perform other container registration work here
    }

    private static ContainerBuilder RegisterDependencies()
    {
        var builder = new ContainerBuilder();
        builder.RegisterType<ViewModelFactory>().As<IViewModelFactory>().InstancePerLifetimeScope();
        builder.RegisterAssemblyTypes(typeof(MvcApplication).Assembly)
            .Where(t => t.IsClosedTypeOf(typeof(IViewModelFactory<,>)) || t.IsClosedTypeOf(typeof(IViewModelFactory<>)))
            .AsImplementedInterfaces().InstancePerLifetimeScope();

        return builder;            
    }
}

The interesting stuff happens within RegisterDependencies. First up we register our base ViewModelFactory. The line after is where the magic happens. Here we’re performing assembly scanning of our web assembly. We’re looking for closed types matching our generic interfaces. Whenever we find one, we register it with its implemented interface.

Time to look at an implementation of this

Let's put some of this in place in our ProductController example. First up, we can simplify the constructor:

private readonly IViewModelFactory viewModelFactory;

public ProductController(IViewModelFactory viewModelFactory)
{
    this.viewModelFactory = viewModelFactory;
}

Our add and edit factories would have the following structure:

public class ProductAddViewModelFactory : IViewModelFactory<ProductAddViewModel>
{
    public ProductAddViewModel Create()
    {
        // TODO: initialise ProductAddViewModel here
        return null;
    }
}

public class ProductEditViewModelFactory : IViewModelFactory<int, ProductEditViewModel>
{
    public ProductEditViewModel Create(int input)
    {
        // TODO: retrieve Product from database and then initialise ProductEditViewModel here
        return null;
    }
}

Make sure that the view models inherit from our new base ViewModel type. All this then leaves is to call them within the controller actions:

public ActionResult Add()
{
    var model = viewModelFactory.Create<ProductAddViewModel>();
    return View(model);
}

public ActionResult Edit(int productId)
{
    var model = viewModelFactory.Create<int, ProductEditViewModel>(productId);
    return View(model);
}

There’s a lot going on here. What did we just do?

Let’s recap on what we’ve done here.

  • We created a base ViewModel for our factories
  • We ensured our view models inherit from it
  • We added a generic and non-generic factory interface
  • We registered the factories with Autofac
  • We reduced our controller dependencies to 1
  • We called Create on our new non-generic factory wherever we needed a ViewModel

This technique isn’t just limited to ViewModels and factories. We can use it in any situation where we need to perform the same action over and over. Similar examples include mapping from one type to another or executing a command.

View original article

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Technical Lead Levelnis Ltd
United Kingdom United Kingdom
Follow along my journey as I create a newsletter about launching websites. Just message me with "I'm in" and I'll add you

Comments and Discussions

 
-- There are no messages in this forum --