Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Simple Injector unable to inject dependencies in Web API controllers

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);
    }
}
like image 349
user1417835 Avatar asked Apr 09 '13 17:04

user1417835


2 Answers

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.

like image 78
Steven Avatar answered Oct 18 '22 20:10

Steven


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.

like image 1
Muhammad Awais Avatar answered Oct 18 '22 20:10

Muhammad Awais