Refactoring to SOLID - Part 7: Dependency Injection in ASP.NET MVC (Poor Man's Version)

by Larry Spencer Sunday, May 27, 2012 12:43 PM

Our series on refactoring to SOLID development practices has progressed from an ASP.NET WebForms application to an MVC 3 version. Where we left off in Part 6, we had begun to consider the 'D of SOLID, which stands for dependency inversion. Specifically, we were injecting an ISearcher<Document> into the Model portion of our Model-View-Controller app. The question was how to accomplish this injection of a concrete class into an MVC Model.

In ASP.NET MVC, it is the Controller's job to create the Model. In our case, this happens in the Search action method (the last one in the listing below).

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using Solid.WebApp.Models;
using Solid.Interfaces;
using Solid.MyDatabase;

namespace Solid.WebApp.Controllers
{
    public class DocumentsController : Controller
    {
        #region Fields
        readonly ISearcher<Document> _searcher = null;
        #endregion

        #region Constructors
        /// <summary>
        /// Default constructor.
        /// </summary>
        public DocumentsController()
        {         
        }

        /// <summary>
        /// Constructor that lets you inject a searcher.
        /// </summary>
        /// <param name="searcher">An object that can search for Documents.</param>
        public DocumentsController(ISearcher<Document> searcher)
        {
            _searcher = searcher;
        }
        #endregion

        #region Action Methods
        /// <summary>
        /// The default action. Can be used to display the initial View, or to search and redisplay.
        /// </summary>
        /// <returns>A ViewResult.</returns>
        public ViewResult Search(FormCollection formCollection)
        {
            var model = new DocumentsModel(_searcher);

            // If this is not the initial display, do a search.
            if (formCollection.Count > 0)
            {
                model.Search(formCollection["Account"], formCollection["LastName"], formCollection["FirstName"]);
            }
            return View(model);
        }
        #endregion
    }
}

 

The Search method passes _searcher to the Model. But as you can see, that merely begs the question: Where does the Controller get it? Well, it's passed to the Controller's constructor (dependency injection again). But who calls the constructor?

ASP.NET MVC conveniently provides a Controller Factory for this task, and lets us hook in our own factory. First, here is our custom factory. Notice that is has a static property in which the desired ISearcher<Document> can be stored, and then the CreateController override can use that ISearcher<Document> when it creates the Controller.

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Text.RegularExpressions;
using System.Web.Mvc;
using System.Web.Routing;
using Solid.MyDatabase;
using Solid.Interfaces;
using System.Diagnostics.Contracts;

namespace Solid.WebApp
{
    /// <summary>
    /// An IControllerFactory that can help with dependency injection.
    /// </summary>
    public class WebAppControllerFactory : DefaultControllerFactory
    {
        #region Properties
        /// <summary>
        /// Dependency-injected objects. Must be set before this factory produces controllers.
        /// </summary>
        static public ISearcher<Document> DocumentSearcher { get; set; }
        #endregion

        #region Overrides
        public override IController CreateController(RequestContext requestContext, string controllerName)
        {
            Contract.Requires(DocumentSearcher != null, "Before any controllers are created, the DocumentSearcher property must be set.");

            if (controllerName == Regex.Replace(typeof(Controllers.DocumentsController).Name, "Controller$", ""))
            {
                return new Controllers.DocumentsController(DocumentSearcher);
            }
            return base.CreateController(requestContext, controllerName);
        }
        #endregion
    }
}

 

We wire up our controller factory in the Application_Start() method method of global.asax.cs. That is also a logical place to set the DocumentSearcher property. Our first version uses "poor man's dependency injection," meaning that we hard-code the choice of the concrete class.

 

protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();

    RegisterGlobalFilters(GlobalFilters.Filters);
    RegisterRoutes(RouteTable.Routes);
    WebAppControllerFactory.DocumentSearcher = new DocumentSearcher();
    ControllerBuilder.Current.SetControllerFactory(typeof(WebAppControllerFactory));
}

 

Although this is poor man's DI, it demonstrates an important principle: Your dependency injection should take place as close to the root of your application as possible. Notice that none of our downstream code relies on any one means for dependency injection. When we switch from poor man's DI to something better, we will only have to change global.asax.cs, and will not have to change any of the other classes. The choice is not such a big deal for a toy app like this one, but if you had dozens of dependency injections, you would be glad you only had to make changes in one place. 

Furthermore, this follows the Single Responsibility Principle. It is global.asax.cs's single responsibility to establish the infrastructure for our application. This is NOT the responsibilty of the Models or Controllers. When we use a dependency-injection framework, this will become even more clear. It is NOT the responsibility of the business classes to choose that framework!

OK, let's summarize where we are with dependency injection in our ASP.NET MVC application.

  1. Global.asax.cs hooks in our custom controller factory, WebAppControllerFactory, and tells it which concrete version of ISearcher<Document> to use.
  2. When WebAppControllerFactory creates our DocumentsController, it injects that ISearcher<Document> into the constructor.
  3. DocumentsController can then pass the ISearcher<Document> to the DocumentsModel -- dependency injection again.
  4. Finally, DocumentsModel uses the injected searcher.

This is great, but in the next post we will do better by moving the hard-coded choice of DocumentSearcher to the web.config file. To accomplish this, we need a dependency-injection framework.

Tags: , ,

All | Dependency Injection | Talks

About the Author

Larry Spencer

Larry Spencer develops software with the Microsoft .NET Framework for ScerIS, a document-management company in Sudbury, MA.