Search code examples
c#asp.net-mvc-4simple-injector

SimpleInjector and custom WebViewPage


I have a custom WebViewPage, and I would like SimpleInjector to resolve some dependencies in it. It seems like I can't use constructor injection due to the way MVC instantiates this class.

I've enabled explicity property injection as described in the SimpleInjector documentation. But the properties are not getting injected - they're always null.

custom WebViewPage class:

public abstract class CustomWebViewPage<T> : WebViewPage<T>
{
   [Import]
   public IMyDependency Dependency { get; set; }

   public void UseDependency()
   {
      Dependency.DoStuff(); // Dependency is null
   }
}

IOC setup:

var container = new Container();
container.Options.DefaultScopedLifestyle = new WebRequestLifestyle();
container.Options.PropertySelectionBehavior = new ImportPropertySelectionBehavior();

container.RegisterMvcControllers(Assembly.GetExecutingAssembly());

container.Register<IMyDependency, MyDependency>();

Do I need to wire anything else up to get this working? I've tried registering the custom WebViewPage with SimpleInjector (mapping WebViewPage<> to CustomWebViewPage<> and making my class non-abstract), but that didn't work either.

Just to be clear, all other property and constructor dependency injection is working in the application - just not for this particular class.


Solution

  • I agree with Remo Gloor's answer and the statement from Stefan:

    But as the others said you shouldn't do view injection anyway. Your view should be dumb and just render the view model to HTML. Anything requiring a dependency should be done in the controller or a service.

    That said, in case you really, really, really need this (which you shouldn't), I think it's a matter of creating a custom IViewEngine.

    WARNING: The code below is NOT!!! tested AT ALL*

    internal class PropertyInjectionViewEngineDecorator : IViewEngine
    {
        private readonly ConcurrentDictionary<Type, Registration> registrations = new ConcurrentDictionary<Type, Registration>();
    
        private readonly IViewEngine decoratee;
        private readonly Func<Type, Registration> createRegistration;
    
        public PropertyInjectionViewEngineDecorator(IViewEngine decoratee, Container container)
        {
            this.decoratee = decoratee;
            this.createRegistration = type => Lifestyle.Transient.CreateRegistration(type, container);
        }
    
        public ViewEngineResult FindPartialView(ControllerContext context, string partialViewName, bool useCache) =>
            this.InitializeView(this.decoratee.FindPartialView(context, partialViewName, useCache));
    
        public ViewEngineResult FindView(ControllerContext context, string viewName, string masterName, bool useCache) =>
            this.InitializeView(this.decoratee.FindView(context, viewName, masterName, useCache));
    
        public void ReleaseView(ControllerContext controllerContext, IView view) =>
            this.decoratee.ReleaseView(controllerContext, view);
    
        private ViewEngineResult InitializeView(ViewEngineResult result)
        {
            if (result.View != null)
            {
                Registration registration = this.registrations.GetOrAdd(result.View.GetType(), this.createRegistration);
                registration.InitializeInstance(result.View);
            }
    
            return result;
        }
    }
    

    You can hook this custom PropertyInjectionViewEngineDecorator into the MVC pipeline as follows:

    var razor = ViewEngines.Engines.Single(e => e.GetType() == typeof(RazorViewEngine));
    ViewEngines.Engines.Remove(razor);
    ViewEngines.Engines.Add(new PropertyInjectionViewEngineDecorator(razor, container));