Simple Injector unable to inject dependencies in Web API controllers
Asked Answered
I

2

47

I am attempting to do some basic constructor DI with Simple Injector, and it seems that it is unable to resolve the dependencies for Web API controllers.

  • I have an API controller in an "API" folder, that is outside the "Controllers" folder.
  • I have also tried placing it within the "Controllers" folder, but that did not seem to make much of a difference. The stack trace that I receive is similar to the one presented in this question.
  • I am using a fresh install of the "Simple Injector MVC Integration Quick Start" NuGet Package (v. 2.1.0).
  • I have the base SimpleInjectorWebApiDependencyResolver from the documentation, which is also the same as found here.
  • I am using Entity Framework, and have looked at the discussion thread about changes to correctly load the context.

This does not seem to be a problem, but I still receive the following error:

Type 'MyProject.API.ArticleController' does not have a default constructor

System.ArgumentException at

System.Linq.Expressions.Expression.New(Type type) at System.Web.Http.Internal.TypeActivator.Create[TBase](Type instanceType) at System.Web.Http.Dispatcher.DefaultHttpControllerActivator.GetInstanceOrActivator(HttpRequestMessage request, Type controllerType, Func`1& activator) at System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)

It would be appreciated if someone could offer me some suggestions, on whether anything should be modified from its current state/call order.

ArticleController (basic structure):

public class ArticleController : ApiController
{
    private readonly IArticleRepository articleRepository;
    private readonly IUserRepository userRepository;
    private readonly IReleaseRepository releaseRepository;

    public ArticleController(IArticleRepository articleRepository, IUserRepository userRepository, IReleaseRepository releaseRepository)
    {
        this.articleRepository = articleRepository;
        this.userRepository = userRepository;
        this.releaseRepository = releaseRepository;
    }

    // GET api/Article
    public IEnumerable<Article> GetArticles(){ // code }

    // GET api/Article/5
    public Article GetArticle(int id){ // code }

    // PUT api/Article/5
    public HttpResponseMessage PutArticle(int id, Article article){ // code }

    // POST api/Article
    public HttpResponseMessage PostArticle(ArticleModel article){ // code }

    // DELETE api/Article/5
    public HttpResponseMessage DeleteArticle(int id){ // code }
}

SimpleInjectorInitializer:

public static class SimpleInjectorInitializer
{
    public static void Initialize()
    {
        var container = new Container();
        InitializeContainer(container);
        container.RegisterMvcControllers(Assembly.GetExecutingAssembly());
        container.RegisterMvcAttributeFilterProvider();
        container.Verify();

        DependencyResolver.SetResolver(new SimpleInjectorDependencyResolver(container));
    }

    private static void InitializeContainer(Container container)
    {
        container.Register<IArticleRepository, ArticleRepository>();
        container.Register<IUserRepository, UserRepository>();
        container.Register<IReleaseRepository, ReleaseRepository>();
    }
}

Global.asax.cs:

public class WebApiApplication : System.Web.HttpApplication
{
    private void ConfigureApi()
    {
        // Create the container as usual.
        var container = new Container();

        // Verify the container configuration
        // container.Verify();

        // Register the dependency resolver.
        GlobalConfiguration.Configuration.DependencyResolver =
                new SimpleInjectorWebApiDependencyResolver(container);
    }

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

        WebApiConfig.Register(GlobalConfiguration.Configuration);
        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
        RouteConfig.RegisterRoutes(RouteTable.Routes);
        BundleConfig.RegisterBundles(BundleTable.Bundles);
    }
}
Inseminate answered 9/4, 2013 at 17:0 Comment(3)
Are you sure that ConfigureApi method is actually run? Did you set a break point in it?Cinema
Any reason why you create a separate Container instance for your Web API configuration?Cinema
Yes, I am sure that it is run. I have a break point set on the line that sets the DependencyResolver on the GlobalConfiguration. If you mean within the Global.asax.cs, then I was following this guide (asp.net/web-api/overview/extensibility/…), to see if it would resolve my issue. However, it did not seem to.Inseminate
C
49

TLTR: the problem is caused by the implicit way Web API handles resolving controller types; register your Web API controllers explicitly and you'll see where the problem is.

Here is a step by step what is happening under the covers:

  1. The System.Web.Http.DefaultHttpControllerActivator calls into the SimpleInjectorWebApiDependencyResolver and requests the creation of an API controller.
  2. SimpleInjectorWebApiDependencyResolver forwards that call to the SimpleInjector.Container instance.
  3. That Container instance however, does not have any explicit registrations for that API Controller (since you supplied an empty container to the resolver).
  4. Since there is no explicit registration, the container tries to do a last minute registration for that type.
  5. That Controller type however depends on interfaces that can't be resolved because they are not registered in the container (remember, your container is empty).
  6. Although the container would normally throw an exception, null is returned in this case, because the type is requested through the IServiceProvider.GetService method and the type was not registered explictly.
  7. The SimpleInjectorWebApiDependencyResolver's GetService method will return null as well, since it's by definition that it should return null; It should return null when no registration exists (which currently is the case).
  8. Since the DependencyResolver returned null, DefaultHttpControllerActivator will fall back to its default behavior, which means creating that type itself, but this requires the controller to have a default constructor.

Long story short, the problem is caused by the implicit way Web API handles resolving controller types.

So the solution here is to:

  1. Have only one single Container in your web application. This prevents all sorts of trouble and complication of your configuration.
  2. Register all Web API Controllers explicitly in the container. Registering controllers explicitly will ensure that Simple Injector will throw an exception when a controller can't be resolved. Besides, this allows you to call container.Verify() which will make the application fail during startup when the configuration is invalid (a verifiable configuration is important). And this also allows you to diagnose the configuration which gives you even more confidence about the correctness of your configuration.

My advice is to place MVC and Web API in their own project. This will make things much easier.

Registering all Web API controllers can be done with the following code:

container.RegisterWebApiControllers(GlobalConfiguration.Configuration);

UPDATE:

Because this error is so common, newer versions of the SimpleInjectorWebApiDependencyResolver class will simply never return null when a controller type is requested. Instead it will throw a descriptive error. Because of this you should never see error anymore, as long as you use the official SimpleInjectorWebApiDependencyResolver.

Cinema answered 9/4, 2013 at 19:24 Comment(7)
Thanks for the detailed answer! Where exactly should I place this method? Is it implied that I should make a new class, and implement it as an extension method of the Container?Inseminate
It's an extension method. You can place it in any static class so you can do container.RegisterApiControllers() from within your Composition Root.Cinema
Would it be appropriate to set the GlobalConfiguration DependencyResolver within the SimpleInitializer? Or should that be kept within the Global.asax.cs? The documentation (simpleinjector.codeplex.com/…) suggests that container creation and service/resource registration should be done within Application_Start() in Global.asax.cs, but comments in InitializeContainer() also seem to suggest that registration should happen there.Inseminate
There is no harm in placing this in the SimpleInjectorInitiazer.Cinema
Is it the same to use GlobalConfiguration.Configuration.Services.GetHttpControllerTypeResolver().GetControllerTypes() ? (instead of searching the assembly with the LINQ code you used?Doze
@diegohb: Good catch. Using the HttpControllerTypeResolve would be better. I updated my answer.Cinema
@Cinema can i get your attention on this question please? #41500168Algo
S
1

Following setups work for me:

1) include Unity.WebAPI from https://www.nuget.org/packages/Unity.WebAPI/ 2) in UnityConfig

public static class UnityConfig
    {
        public static void RegisterComponents()
        {
            var container = new UnityContainer();
            // **** Important note -----
            // register all your components with the container here
            // e.g. container.RegisterType<ITestService, TestService>();


            DependencyResolver.SetResolver(new Unity.Mvc5.UnityDependencyResolver(container));

            GlobalConfiguration.Configuration.DependencyResolver = new Unity.WebApi.UnityDependencyResolver(container);
        }
    }

3) in Global.asax file

 public class MvcApplication : System.Web.HttpApplication
        {
            protected void Application_Start()
            {
                AreaRegistration.RegisterAllAreas();
                UnityConfig.RegisterComponents();
                GlobalConfiguration.Configure(WebApiConfig.Register);
                FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
                RouteConfig.RegisterRoutes(RouteTable.Routes);
                BundleConfig.RegisterBundles(BundleTable.Bundles);
            }
        }

Getting started with Unity.WebAPI

To get started, just add a call to UnityConfig.RegisterComponents() in the Application_Start method of Global.asax.cs and the Web API framework will then use the Unity.WebAPI DependencyResolver to resolve your components.

e.g.

public class WebApiApplication : System.Web.HttpApplication
{
  protected void Application_Start()
  {
    AreaRegistration.RegisterAllAreas();
    UnityConfig.RegisterComponents();                           // <----- Add this line
    GlobalConfiguration.Configure(WebApiConfig.Register);
    FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
    RouteConfig.RegisterRoutes(RouteTable.Routes);
    BundleConfig.RegisterBundles(BundleTable.Bundles);
  }           
}  

Add your Unity registrations in the RegisterComponents method of the UnityConfig class. All components that implement IDisposable should be registered with the HierarchicalLifetimeManager to ensure that they are properly disposed at the end of the request.

Swafford answered 18/10, 2019 at 11:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.