Wednesday, March 9, 2016

How do we set up the modern StructureMap wireups for MVC which do not use ObjectFactory?

Oh boy, it's not trivial at all. Let's start by creating a solution with the Onion Architecture in Visual Studio 2015. The UI will be an MVC project and there will also be a Core and an Infrastructure project which will be class libraries. The UI will inherit both the Core and the Infrastructure and the Infrastructure will inherit the Core so that the Core inherits no other projects and is not contaminated by external concerns (making it easy to unit test). Our interfaces will live in the Core and the classes that implement them and hydrate them will live in the Infrastructure.

  • using System.Collections.Generic;
    namespace Airport.Core.ExternalDependencies
    {
       public interface IFlightRepository
       {
          KeyValuePair<string, string> HappyPass();
       }
    }

     
  • using System.Collections.Generic;
    using Airport.Core.ExternalDependencies;
    namespace Airport.Infrastructure.ExternalDependencies
    {
       public class FlightRepository : IFlightRepository
       {
          public KeyValuePair<string, string> HappyPass()
          {
             return new KeyValuePair<string, string>("Happy?", "Yes");
          }
       }
    }

     
  • using System.Web.Mvc;
    using Airport.Core.ExternalDependencies;
    namespace Airport.UserInterface.Controllers
    {
       public class HomeController : Controller
       {
          private IFlightRepository _flightRepository;
          
          public HomeController(IFlightRepository flightRepository)
          {
             _flightRepository = flightRepository;
          }
          
          public ActionResult Index()
          {
             var model = _flightRepository.HappyPass();
             return View(model);
          }
       }
    }

 
 

Obviously our three example files in our three projects above could be a lot slicker, but something simple like this will suit our needs. The goal here is to hand "Happy? Yes" off to our view as a model. In order for that to happen something is going to have to hand in an instance of FlightRepository at the constructor of HomeController. How is that possible? Well, we will need some help. Running Install-Package StructureMap in NuGet's console gives us gives us version 4.1.0.361 of StructureMap and furthermore running Install-Package CommonServiceLocator gives us version 1.3.0 of CommonServiceLocator. We need these specifically at the UI project. Moving forward from here the next step is to make a new class in the App_Start folder like so:

using System.Web;
using System.Web.Mvc;
using Airport.UserInterface.App_Start;
using Airport.UserInterface.DependencyResolution;
using Microsoft.Web.Infrastructure.DynamicModuleHelper;
using StructureMap;
[assembly: PreApplicationStartMethod(typeof(StructuremapMvc), "Start")]
namespace Airport.UserInterface.App_Start
{
   public static class StructuremapMvc
   {
      public static StructureMapDependencyScope StructureMapDependencyScope { get;
            set; }
      
      public static void Start()
      {
         IContainer container = IoC.Initialize();
         StructureMapDependencyScope = new
               StructureMapDependencyScope(container);
         DependencyResolver.SetResolver(StructureMapDependencyScope);
         DynamicModuleUtility.RegisterModule(typeof(StructureMapScopeModule));
      }
   }
}

 
 

The PreApplicationStartMethod attribute in the class is going to force the Start method to run when the application is spun up. However, we need a lot more than just this class to hydrate our interface at our controller's constructor. Create a folder in the UI called DependencyResolution and put these five classes in it. (no kidding)

  1. using System;
    using StructureMap;
    using StructureMap.Graph;
    using StructureMap.Graph.Scanning;
    using StructureMap.Pipeline;
    namespace Airport.UserInterface.DependencyResolution
    {
       public class ControllerConvention : IRegistrationConvention
       {
          public void ScanTypes(TypeSet types, Registry registry)
          {
             foreach (Type type in types.AllTypes())
             {
                registry.For(type).LifecycleIs(new UniquePerRequestLifecycle());
             }
          }
       }
    }

     
  2. using StructureMap;
    namespace Airport.UserInterface.DependencyResolution
    {
       public static class IoC
       {
          public static IContainer Initialize()
          {
             var container = new Container(c => c.AddRegistry<DefaultRegistry>());
             return container;
          }
       }
    }

     
  3. using System.Web;
    using Airport.UserInterface.App_Start;
    namespace Airport.UserInterface.DependencyResolution
    {
       public class StructureMapScopeModule : IHttpModule
       {
          public void Dispose()
          {
          }
          
          public void Init(HttpApplication context)
          {
             context.BeginRequest += (sender, e) =>
                   StructuremapMvc.StructureMapDependencyScope
                   .CreateNestedContainer();
             context.EndRequest += (sender, e) => {
                StructuremapMvc.StructureMapDependencyScope
                      .DisposeNestedContainer();
             };
          }
       }
    }

     
  4. using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using Microsoft.Practices.ServiceLocation;
    using StructureMap;
    namespace Airport.UserInterface.DependencyResolution
    {
       public class StructureMapDependencyScope : ServiceLocatorImplBase
       {
          private const string NestedContainerKey = "Nested.Container.Key";
          
          public StructureMapDependencyScope(IContainer container)
          {
             if (container == null)
             {
                throw new ArgumentNullException("container");
             }
             Container = container;
          }
          
          public IContainer Container { get; set; }
          
          public IContainer CurrentNestedContainer
          {
             get
             {
                return (IContainer)HttpContext.Items[NestedContainerKey];
             }
             set
             {
                HttpContext.Items[NestedContainerKey] = value;
             }
          }
          
          private HttpContextBase HttpContext
          {
             get
             {
                var ctx = Container.TryGetInstance<HttpContextBase>();
                return ctx ?? new
                      HttpContextWrapper(System.Web.HttpContext.Current);
             }
          }
          
          public void CreateNestedContainer()
          {
             if (CurrentNestedContainer != null)
             {
                return;
             }
             CurrentNestedContainer = Container.GetNestedContainer();
          }
          
          public void Dispose()
          {
             DisposeNestedContainer();
             Container.Dispose();
          }
          
          public void DisposeNestedContainer()
          {
             if (CurrentNestedContainer != null)
             {
                CurrentNestedContainer.Dispose();
                CurrentNestedContainer = null;
             }
          }
          
          public IEnumerable<object> GetServices(Type serviceType)
          {
             return DoGetAllInstances(serviceType);
          }
          
          protected override IEnumerable<object> DoGetAllInstances(Type
                serviceType)
          {
             return (CurrentNestedContainer ??
                   Container).GetAllInstances(serviceType).Cast<object>();
          }
          
          protected override object DoGetInstance(Type serviceType, string key)
          {
             IContainer container = (CurrentNestedContainer ?? Container);
             if (string.IsNullOrEmpty(key))
             {
                return serviceType.IsAbstract || serviceType.IsInterface
                   ? container.TryGetInstance(serviceType)
                   : container.GetInstance(serviceType);
             }
             return container.GetInstance(serviceType, key);
          }
       }
    }

     
  5. using Airport.Core.ExternalDependencies;
    using Airport.Infrastructure.ExternalDependencies;
    using StructureMap;
    using StructureMap.Graph;
    namespace Airport.UserInterface.DependencyResolution
    {
       public class DefaultRegistry : Registry
       {
          public DefaultRegistry()
          {
             Scan(
                scan =>
                {
                   scan.TheCallingAssembly();
                   scan.WithDefaultConventions();
                   scan.With(new ControllerConvention());
                });
             For<IFlightRepository>().Singleton().Use<FlightRepository>();
          }
       }
    }

 
 

Notice that the very last of these actually has the magic to associate FlightRepository with IFlightRepository. What a journey! This alone will not empower Web API 2 controllers also. After I figured all of this out today my superior stopped by my desk and pointed out to me that the stuff I had stolen out of a Visual Studio 2013 app and recreated at my app could have been made for me if I had run Install-Package StructureMap.MVC5 to do all of the work I hand-rolled and also Install-Package StructureMap.WebAPI2 would add in the Web API stuff. #sadtrombone

No comments:

Post a Comment