4

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.

3 Answers 3

8

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;
        }
Sign up to request clarification or add additional context in comments.

7 Comments

This is how I have it set up too and it works for me. But I'm not sure how this is different from the previous versions where we used a subclass of the DefaultControllerFactory and that was set in a call to ControllerBuilder's Current property
Are you working with the RTM version? Do you have any relevant set-up code in your registries? If I run code like the above I get a StructureMap exception saying that no default instance of IControllerFactory is defined.
Hakeem - Subclassing the DefaultControllerFactory only gave us dependency injection support for controllers. In MVC3, using the DependencyResolver.SetResolver gives us dependency injection support for many areas in the MVC framework including controllers, views, action filters and model binders.
Rob - I am using the RTM version. I do not have any registries included in the project. Which version of StructureMap are you using? I am using version 2.6.1
I'm using 2.6.1 also. You must have registries doing your configuration otherwise why are you scanning for them?
|
4

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!

Comments

0

This works for me for both MVC and Web API..

namespace Web.Utilities.DependencyResolvers
{
    public class StructureMapResolver : IServiceLocator, IDependencyResolver
    {
        private readonly IContainer _container;

        public StructureMapResolver(IContainer container)
        {
            if (container == null)
                throw new ArgumentNullException("container");

            this._container = container;
        }

        public IDependencyScope BeginScope()
        {
            return new StructureMapResolver(this._container.GetNestedContainer());
        }

        public object GetInstance(Type serviceType, string instanceKey)
        {
            if (string.IsNullOrEmpty(instanceKey))
            {
                return GetInstance(serviceType);
            }

            return this._container.GetInstance(serviceType, instanceKey);
        }

        public T GetInstance<T>()
        {
            return this._container.GetInstance<T>();
        }

        public object GetService(Type serviceType)
        {
            return GetInstance(serviceType);
        }

        public IEnumerable<object> GetServices(Type serviceType)
        {
            return this._container.GetAllInstances(serviceType).Cast<object>();
        }

        public T GetInstance<T>(string instanceKey)
        {
            return this._container.GetInstance<T>(instanceKey);
        }

        public object GetInstance(Type serviceType)
        {
            return serviceType.IsAbstract || serviceType.IsInterface ?
                this._container.TryGetInstance(serviceType) : this._container.GetInstance(serviceType);
        }

        public IEnumerable<T> GetAllInstances<T>()
        {
            return this._container.GetAllInstances<T>();
        }

        public IEnumerable<object> GetAllInstances(Type serviceType)
        {
            return this._container.GetAllInstances(serviceType).Cast<object>();
        }

        public void Dispose()
        {
            this._container.Dispose();
        }
    }
}

1 Comment

Hey Alvin, welcome to SO. Normally it helps a lot for you to leave some more detail in your answer, especially if you're reviving an old thread. Please give an explanation as to why your answer solves the problem.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.