No se pueden inyectar dependencias en ASP.NET Web API Controller usando Unity

82

¿Alguien ha tenido éxito al ejecutar un contenedor IoC para inyectar dependencias en los controladores ASP.NET WebAPI? Parece que no puedo hacer que funcione.

Esto es lo que estoy haciendo ahora.

En mi global.ascx.cs:

    public static void RegisterRoutes(RouteCollection routes)
    {
            // code intentionally omitted 
    }

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

        RegisterGlobalFilters(GlobalFilters.Filters);
        RegisterRoutes(RouteTable.Routes);

        IUnityContainer container = BuildUnityContainer();

        System.Web.Http.GlobalConfiguration.Configuration.ServiceResolver.SetResolver(
            t =>
            {
                try
                {
                    return container.Resolve(t);
                }
                catch (ResolutionFailedException)
                {
                    return null;
                }
            },
            t =>
            {
                try
                {
                    return container.ResolveAll(t);
                }
                catch (ResolutionFailedException)
                {
                    return new System.Collections.Generic.List<object>();
                }
            });

        System.Web.Mvc.ControllerBuilder.Current.SetControllerFactory(new UnityControllerFactory(container)); 

        BundleTable.Bundles.RegisterTemplateBundles();
    }

    private static IUnityContainer BuildUnityContainer()
    {
        var container = new UnityContainer().LoadConfiguration();

        return container;
    }

Mi fábrica de controladores:

public class UnityControllerFactory : DefaultControllerFactory
            {
                private IUnityContainer _container;

                public UnityControllerFactory(IUnityContainer container)
                {
                    _container = container;
                }

                public override IController CreateController(System.Web.Routing.RequestContext requestContext,
                                                    string controllerName)
                {
                    Type controllerType = base.GetControllerType(requestContext, controllerName);

                    return (IController)_container.Resolve(controllerType);
                }
            }

Nunca parece buscar en mi archivo de unidad para resolver dependencias, y obtengo un error como:

Se produjo un error al intentar crear un controlador de tipo 'PersonalShopper.Services.WebApi.Controllers.ShoppingListController'. Asegúrese de que el controlador tenga un constructor público sin parámetros.

en System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create (HttpControllerContext controllerContext, Type controllerType) en System.Web.Http.Dispatcher.DefaultHttpControllerFactory.CreateInstance (HttpControllerContext controllerContext, HttpControllerHttpController.Controller, HttpController. (HttpControllerContext controllerContext, String controllerName) en System.Web.Http.Dispatcher.HttpControllerDispatcher.SendAsyncInternal (solicitud HttpRequestMessage, CancellationToken cancellationToken) en System.Web.Http.Dispatcher.HttpControllerDispatlation request, CancellationTokenlation

El controlador se parece a:

public class ShoppingListController : System.Web.Http.ApiController
    {
        private Repositories.IProductListRepository _ProductListRepository;


        public ShoppingListController(Repositories.IUserRepository userRepository,
            Repositories.IProductListRepository productListRepository)
        {
            _ProductListRepository = productListRepository;
        }
}

Mi archivo de unidad se ve así:

<unity xmlns="http://schemas.microsoft.com/practices/2010/unity">
  <container>
    <register type="PersonalShopper.Repositories.IProductListRepository, PersonalShopper.Repositories" mapTo="PersonalShopper.Implementations.MongoRepositories.ProductListRepository, PersonalShopper.Implementations" />
  </container>
</unity>

Tenga en cuenta que no tengo un registro para el controlador en sí porque en versiones anteriores de mvc, la fábrica de controladores se daría cuenta de que las dependencias debían resolverse.

Parece que nunca se llama a la fábrica de mi controlador.

Oved D
fuente

Respuestas:

42

Lo averigué.

Para ApiControllers , MVC 4 usa System.Web.Http.Dispatcher.IHttpControllerFactory y System.Web.Http.Dispatcher.IHttpControllerActivator para crear los controladores. Si no existe un método estático para registrar cuál es la implementación de estos; cuando se resuelven, el marco mvc busca las implementaciones en el solucionador de dependencias y, si no se encuentran, usa las implementaciones predeterminadas.

Obtuve la resolución unitaria de las dependencias del controlador trabajando haciendo lo siguiente:

Creó un UnityHttpControllerActivator:

public class UnityHttpControllerActivator : IHttpControllerActivator
{
    private IUnityContainer _container;

    public UnityHttpControllerActivator(IUnityContainer container)
    {
        _container = container;
    }

    public IHttpController Create(HttpControllerContext controllerContext, Type controllerType)
    {
        return (IHttpController)_container.Resolve(controllerType);
    }
}

Registró ese activador del controlador como la implementación en el contenedor de la unidad en sí:

protected void Application_Start()
{
    // code intentionally omitted

    IUnityContainer container = BuildUnityContainer();
    container.RegisterInstance<IHttpControllerActivator>(new UnityHttpControllerActivator(container));

    ServiceResolver.SetResolver(t =>
       {
         // rest of code is the same as in question above, and is omitted.
       });
}
Oved D
fuente
¿Cómo implementaste el lamda GlobalConfiguration.Configuration.ServiceResolver.SetResolver()?
jrummell
7
Esta publicación está desactualizada. Vea la respuesta de CodeKata para una solución más limpia con MVC RC.
Matt Randle
Esto está desactualizado. Microsoft tiene un paquete Nuget que hace DI con Web API. Mira mi respuesta.
garethb
37

Hay una mejor solución que funciona correctamente aquí.

http://www.asp.net/web-api/overview/extensibility/using-the-web-api-dependency-resolver

CodeKata
fuente
1
Esta es una solución mucho mejor con MVC RC. No es necesario que proporcione una implementación de IHttpControllerActivator. En su lugar, implementa System.Web.Http.Dependencies.IDependencyResolver.
Matt Randle
22
No soy un gran fanático de los enlaces a blogs, ¿podría resumir aquí y poner el enlace? :)
Nate-Wilkins
3
Parece un buen enfoque, pero recibo varios errores como el siguiente. Parece que el nuevo solucionador no puede resolver varios tipos. La resolución de la dependencia falló, escriba = "System.Web.Http.Hosting.IHostBufferPolicySelector", name = "(none)". La excepción ocurrió mientras: mientras se resolvía. La excepción es: InvalidOperationException: el tipo IHostBufferPolicySelector no tiene un constructor accesible. --- En el momento de la excepción, el contenedor era: Resolving System.Web.Http.Hosting.IHostBufferPolicySelector, (
ATHER
Microsoft tiene un paquete Nuget que hace DI con Web API. Mira mi respuesta.
garethb
24

Es posible que desee echar un vistazo al paquete Unity.WebApi NuGet que resolverá todo esto y también atenderá los componentes IDisposable.

ver

http://nuget.org/packages/Unity.WebAPI

o

http://www.devtrends.co.uk/blog/introducing-the-unity.webapi-nuget-package

Paul Hiles
fuente
2
Aquí hay una buena publicación de blog ( netmvc.blogspot.com/2012/04/… ) que tiene un tutorial sobre el uso de una combinación de Unity.mvc3 (también funciona con MVC4) y Unit.WebApi para implementar DI de manera bastante limpia.
Phred Menyhert
1
El enlace mencionado en el comentario refleja la API actualizada, para cualquiera que se encuentre con esto ahora: GlobalConfiguration.Configuration.ServiceResolver.SetResolver (nuevo Unity.WebApi.UnityDependencyResolver (contenedor)); ahora es GlobalConfiguration.Configuration.DependencyResolver = new Unity.WebApi.UnityDependencyResolver (contenedor);
Robert Zahm
Tome nota, también proporciona mejores mensajes de depuración que los personalizados UnityResolver.
Ioannis Karadimas
9

Microsoft ha creado un paquete para esto.

Ejecute el siguiente comando desde la consola del administrador de paquetes.

paquete de instalación Unity.AspNet.WebApi

Si ya tiene Unity instalado, le preguntará si desea sobrescribir App_Start \ UnityConfig.cs. Responda no y continúe.

No es necesario cambiar ningún otro código y DI (con unidad) funcionará.

garethb
fuente
¿puede decirme por qué respondemos "no" cuando el nuget solicita sobrescribir UnityConfig.cs? Pregunta 2: ¿hay algunos códigos de muestra para usar Unity.AspNet.WebApi?
Thomas.Benz
Lo siento, me cambié a Autofac porque había algunas cosas que necesitaba que Unity no hacía fácilmente y Autofac hizo de inmediato. Pero desde la memoria, debe decir que no si ya tiene la unidad instalada, ya que no desea sobrescribir su configuración de unidad que ya funciona, solo desea agregar la configuración de unidad de webapi. Usar unity en webapi es exactamente como lo usaría normalmente. Si no está inyectando sus dependencias como lo hace con sus otras clases, le sugiero que publique una pregunta. No hay forma diferente de inyectar en webapi que otras clases (como controladores)
garethb
@garethb: Estoy usando unity.webapi pero tengo que agregar algunas líneas de código en las pruebas unitarias para resolver ControllerContext que no me gusta. Supongamos que, si uso Unity.AspNet.WebApi, ¿esa dirección resolvería ControllerContext automáticamente desde el proyecto UnitTests? Por favor sugiera
sam
No lo creo. Estoy bastante seguro de que ambos funcionan de manera similar. Creo un nuevo contexto simulado en mis pruebas unitarias.
garethb
@garethb: Gracias por su rápida respuesta. burlarse funcionó. No puedo inyectar UrlHelper usando Unity.WebApi o Unity.Aspnet.webapi. ¿Recuerdas cómo lo hacías cuando usabas Unity? Gracias de antemano
sam
3

Tuve el mismo error y estuve buscando soluciones en Internet un par de horas. Finalmente, pareció que tenía que registrar Unity ANTES de llamar a WebApiConfig.Register. Mi global.asax ahora parece

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

Para mí, esto resolvió el problema de que Unity no podía resolver las dependencias en mis controladores

Vincent
fuente
2

En un RC reciente, descubrí que ya no hay un método SetResolver. Para habilitar IoC para el controlador y webapi, uso Unity.WebApi (NuGet) y el siguiente código:

public static class Bootstrapper
{
    public static void Initialise()
    {
        var container = BuildUnityContainer();

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

        ControllerBuilder.Current.SetControllerFactory(new DefaultControllerFactory(new ControllerActivator()));
    }

    private static IUnityContainer BuildUnityContainer()
    {
        var container = new UnityContainer();

        container.Configure(c => c.Scan(scan =>
        {
            scan.AssembliesInBaseDirectory();
            scan.With<UnityConfiguration.FirstInterfaceConvention>().IgnoreInterfacesOnBaseTypes();
        }));

        return container;
    }
}

public class ControllerActivator : IControllerActivator
{
    IController IControllerActivator.Create(RequestContext requestContext, Type controllerType)
    {
        return GlobalConfiguration.Configuration.DependencyResolver.GetService(controllerType) as IController;
    }
}

También uso UnityConfiguration (también de NuGet) para la magia de IoC. ;)

Noogen
fuente
¿No sería mejor usar requestContext.Configuration.DependencyResolver.GetService ()?
Alwyn
2

Después de leer las respuestas, todavía tuve que investigar mucho para aterrizar en este problema, así que aquí está para el beneficio de los compañeros: esto es todo lo que necesita hacer en ASP.NET 4 Web API RC (como el 8 de agosto '13):

  1. Agregue una referencia a "Microsoft.Practices.Unity.dll" [Estoy en la versión 3.0.0.0, agregada a través de NuGet]
  2. Agregue una referencia a "Unity.WebApi.dll" [Estoy en la versión 0.10.0.0, agregada a través de NuGet]
  3. Registre sus asignaciones de tipos con el contenedor, similar al código en Bootstrapper.cs que el proyecto Unity.WebApi agrega a su proyecto.
  4. En los controladores que heredan de la clase ApiController, cree constructores parametrizados que tengan sus tipos de parámetros como tipos asignados

¡Y he aquí, obtienes las dependencias inyectadas en tu constructor sin otra línea de código!

NOTA: Obtuve esta información de uno de los comentarios en ESTE blog por su autor.

Sudhanshu Mishra
fuente
2

Breve resumen de ASP.NET Web API 2.

Instalar Unitydesde NuGet.

Crea una nueva clase llamada UnityResolver:

using Microsoft.Practices.Unity;
using System;
using System.Collections.Generic;
using System.Web.Http.Dependencies;

public class UnityResolver : IDependencyResolver
{
    protected IUnityContainer container;

    public UnityResolver(IUnityContainer container)
    {
        if (container == null)
        {
            throw new ArgumentNullException("container");
        }
        this.container = container;
    }

    public object GetService(Type serviceType)
    {
        try
        {
            return container.Resolve(serviceType);
        }
        catch (ResolutionFailedException)
        {
            return null;
        }
    }

    public IEnumerable<object> GetServices(Type serviceType)
    {
        try
        {
            return container.ResolveAll(serviceType);
        }
        catch (ResolutionFailedException)
        {
            return new List<object>();
        }
    }

    public IDependencyScope BeginScope()
    {
        var child = container.CreateChildContainer();
        return new UnityResolver(child);
    }

    public void Dispose()
    {
        Dispose(true);
    }

    protected virtual void Dispose(bool disposing)
    {
        container.Dispose();
    }
}

Crea una nueva clase llamada UnityConfig:

public static class UnityConfig
{
    public static void ConfigureUnity(HttpConfiguration config)
    {
        var container = new UnityContainer();
        container.RegisterType<ISomethingRepository, SomethingRepository>();
        config.DependencyResolver = new UnityResolver(container);
    }
}

Editar App_Start -> WebApiConfig.cs

public static void Register(HttpConfiguration config)
{
    UnityConfig.ConfigureUnity(config);
    ...

Ahora funcionará.

Fuente original pero un poco modificada: https://docs.microsoft.com/en-us/aspnet/web-api/overview/advanced/dependency-injection

Ogglas
fuente
1

Tuve la misma excepción y, en mi caso, tuve un conflicto entre los binarios MVC3 y MVC4. Esto impedía que mis controladores se registraran correctamente con mi contenedor IOC. Verifique su web.config y asegúrese de que apunte a las versiones correctas de MVC.


fuente
Esto solucionó mis problemas de Razor, pero no solucionó el problema de resolver los controladores de API.
Oved D
1

Este problema ocurre debido al registro de controladores con Unity. Resolví esto usando el registro por convención como se muestra a continuación. Filtre los tipos adicionales según sea necesario.

    IUnityContainer container = new UnityContainer();

    // Do not register ApiControllers with Unity.
    List<Type> typesOtherThanApiControllers
        = AllClasses.FromLoadedAssemblies()
            .Where(type => (null != type.BaseType)
                           && (type.BaseType != typeof (ApiController))).ToList();

    container.RegisterTypes(
        typesOtherThanApiControllers,
        WithMappings.FromMatchingInterface,
        WithName.Default,
        WithLifetime.ContainerControlled);

También el ejemplo anterior usa AllClasses.FromLoadedAssemblies(). Si está buscando cargar ensamblados desde la ruta base, es posible que no funcione como se esperaba en un proyecto de API web con Unity. Por favor, eche un vistazo a mi respuesta a otra pregunta relacionada con esto. https://stackoverflow.com/a/26624602/1350747

Srihari Sridharan
fuente
0

Tuve el mismo problema al usar el paquete Unity.WebAPI NuGet. El problema fue que el paquete nunca agregó una llamada aUnityConfig.RegisterComponents() en mi Global.asax.

Global.asax.cs debería verse así:

public class WebApiApplication : System.Web.HttpApplication
{
    protected void Application_Start()
    {
        UnityConfig.RegisterComponents();
        ...
    }
}
Keith
fuente