Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to configure dependency injection with ASP.NET MVC 3 RTM

I am upgrading a web app from ASP.NET 3 Preview 1 to the RTM and I am confused by the updated approach to dependency injection. I am using StructureMap for this but that's not really relevant to my question. Previously all I needed to do was as follows:

x.For<IControllerFactory>().Use<DefaultControllerFactory>();
x.For<IServiceLocator>().Use(MvcServiceLocator.Current);

Now it seems like I need to provide implementations of IControllerActivator, IViewPageActivator and ModelMetadataProvider because otherwise I get an error from StructureMap because MVC tries to locate them using the dependency resolver. From a look at the MVC source there do not seem to be public default implementations. Am I missing something in setting these up? Surely these should be configured by convention?

Examples of what needs configuring and how with StructureMap would be appreciated. For reference I am currently using the following ugly kludge which forces MVC to use its internal defaults:

x.For<IControllerFactory>().Use<DefaultControllerFactory>();
x.For<IDependencyResolver>().Use(() => DependencyResolver.Current);                
x.For<IControllerActivator>().Use(() => null);
x.For<IViewPageActivator>().Use(() => null);
x.For<ModelMetadataProvider>().Use(ModelMetadataProviders.Current);

EDIT: Just to be clear I have a working StructureMap implementation of the Dependency Resolver - the issue is why MVC is complaining about all these interfaces not being configured in the container.

like image 559
Rob West Avatar asked Dec 17 '22 18:12

Rob West


2 Answers

I was able to get StructureMap to work with ASP.NET MVC3 by creating a Dependency Resolver(IDependencyResolver) class, then registering that class in the global.asax. I have not fully tested this code. But, it has been working without any issues in two applications.

StructureMapDependencyResolver.cs

using System.Linq;
using System.Web.Mvc;
using StructureMap;

namespace SomeNameSpace
{
    public class StructureMapDependencyResolver : IDependencyResolver
    {
        private readonly IContainer container;

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

        public object GetService(System.Type serviceType)
        {
            try
            {
                return this.container.GetInstance(serviceType);
            }
            catch
            {
                return null;
            }
        }

        public System.Collections.Generic.IEnumerable<object> GetServices(System.Type serviceType)
        {
            return this.container.GetAllInstances<object>()
                .Where(s => s.GetType() == serviceType);
        }
    }
}

Global.asax.cs

        public static void RegisterGlobalFilters(GlobalFilterCollection filters)
        {
            filters.Add(new HandleErrorAttribute());
        }

        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

            routes.MapRoute(
                "Default", // Route name
                "{controller}/{action}/{id}", // URL with parameters
                new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
            );

        }

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

            DependencyResolver.SetResolver(new StructureMapDependencyResolver(InitContainer()));
            RegisterGlobalFilters(GlobalFilters.Filters);            
            RegisterRoutes(RouteTable.Routes);
        }

        private static IContainer InitContainer()
        {
            ObjectFactory.Initialize(x =>
            {
                x.Scan(y =>
                {
                    y.WithDefaultConventions();
                    y.AssembliesFromApplicationBaseDirectory();
                    y.LookForRegistries();
                });
            });

            return ObjectFactory.Container;
        }
like image 141
Michael Carman Avatar answered Feb 23 '23 18:02

Michael Carman


I've figured this out thanks to the link @Michael Carman posted in a comment on his answer. I'm not sure of the etiquette here as to whether that warrants accepting his actual answer as it wasn't quite right (I've given him +1 vote) but I thought I'd post my own answer to explain exactly what the issue was.

The problem was down to a combination of my implementation of IDependencyResolver and my container configuration. Originally I had:

public class StructureMapDependencyResolver : IDependencyResolver
{
    public object GetService(Type serviceType)
    {
        return ObjectFactory.GetInstance(serviceType);
    }

    public IEnumerable<object> GetServices(Type serviceType)
    {
        foreach (object obj in ObjectFactory.GetAllInstances(serviceType))
        {
            yield return obj;
        }
    }
}

but I have now changed to this based on Steve Smith's blog post linked to in Jeremy Miller's blog post:

public class StructureMapDependencyResolver : IDependencyResolver
{
    public object GetService(Type serviceType)
    {
        if (serviceType.IsAbstract || serviceType.IsInterface)
        {
            return ObjectFactory.TryGetInstance(serviceType);
        }
        else
        {
            return ObjectFactory.GetInstance(serviceType);
        }
    }

    public IEnumerable<object> GetServices(Type serviceType)
    {
        foreach (object obj in ObjectFactory.GetAllInstances(serviceType))
        {
            yield return obj;
        }
    }
}

on its own this still doesn't resolve the issue until I remove this configuration expression:

x.For<IControllerFactory>().Use<DefaultControllerFactory>();

According to the documentation TryGetInstance only returns types registered with the container and will return null if none exist. I presume the MVC 3 code relies on this behaviour to indicate that it should use its defaults, hence in my original case I had to register these defaults with my container. Tricky one!

like image 20
Rob West Avatar answered Feb 23 '23 17:02

Rob West