web-dev-qa-db-fra.com

Impossible d'injecter des dépendances dans le contrôleur API Web ASP.NET à l'aide de Unity

Quelqu'un at-il déjà réussi à utiliser un conteneur IoC pour injecter des dépendances dans les contrôleurs ASP.NET WebAPI? Je n'arrive pas à le faire fonctionner.

C'est ce que je fais maintenant.

Dans mon 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;
    }

Mon usine de contrôleur:

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);
                }
            }

Il semble ne jamais regarder dans mon fichier d'unité pour résoudre les dépendances, et j'obtiens une erreur comme:

Une erreur s'est produite lors de la tentative de création d'un contrôleur de type 'PersonalShopper.Services.WebApi.Controllers.ShoppingListController'. Assurez-vous que le contrôleur a un constructeur public sans paramètre.

à la hauteur de System.Web.Http. (HttpControllerContext controllerContext, String controllerName) at System.Web.Http.Dispatcher.HttpControllerDispatcher.SendAsyncInternal (HttpRequestS'enregistrez-vous, enregistrez-vous!

Le contrôleur ressemble à:

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


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

Mon fichier d'unité ressemble à:

<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>

Notez que je n’ai pas d’enregistrement pour le contrôleur lui-même car dans les versions précédentes de mvc, l’usine du contrôleur déterminait que les dépendances devaient être résolues.

Il semble que ma fabrique de contrôleurs ne soit jamais appelée.

81
Oved D

Deviner.

Pour ApiControllers, MVC 4 utilise les System.Web.Http.Dispatcher.IHttpControllerFactory et System.Web.Http.Dispatcher.IHttpControllerActivator pour créer le contrôleurs. S'il n'y a pas de méthode statique pour enregistrer quelle est la mise en œuvre de ceux-ci; lorsqu'ils sont résolus, l'infrastructure mvc recherche les implémentations dans le résolveur de dépendances et, si elles ne sont pas trouvées, utilise les implémentations par défaut.

J'ai obtenu la résolution unitaire des dépendances de contrôleur en procédant comme suit:

Création d'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);
    }
}

Enregistré cet activateur de contrôleur en tant qu'implémentation dans le conteneur d'unité lui-même:

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.
       });
}
42
Oved D

Il y a une meilleure solution qui fonctionne correctement ici

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

37
CodeKata

Vous voudrez peut-être jeter un coup d'œil au paquet Unity.WebApi NuGet qui va régler tout cela et prendre en charge les composants IDisposable.

voir

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

ou

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

22
Paul Hiles

Microsoft a créé un package pour cela.

Exécutez la commande suivante à partir de la console du gestionnaire de packages.

package d'installation Unity.AspNet.WebApi

Si Unity est déjà installé, il vous sera demandé si vous souhaitez écraser App_Start\UnityConfig.cs. Répondez non et continuez.

Nul besoin de changer d’autres codes et DI (avec unité) fonctionnera.

9
garethb

J'ai eu la même erreur et cherchais sur Internet des solutions quelques heures plus tard. Finalement, il est apparu que je devais enregistrer Unity AVANT que j'appelais WebApiConfig.Register. Mon global.asax ressemble maintenant à

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);
   }
}

Pour moi, cela a résolu le problème que Unity ne pouvait pas résoudre les dépendances dans mes contrôleurs

3
Vincent

Après avoir lu les réponses, il me restait encore beaucoup à faire pour aller atterrir dans ce piège, donc voici pour le bénéfice des pairs: C’est tout ce que vous devez faire dans ASP.NET 4 Web API RC (à partir du 8 août). '13):

  1. Ajouter une référence à "Microsoft.Practices.Unity.dll" [Je suis sur la version 3.0.0.0, ajoutée par NuGet]
  2. Ajouter une référence à "Unity.WebApi.dll" [Je suis sur la version 0.10.0.0, ajoutée par NuGet]
  3. Enregistrez vos mappages de types avec le conteneur - similaire au code de Bootstrapper.cs ajouté à votre projet par le projet Unity.WebApi.
  4. Dans les contrôleurs héritant de la classe ApiController, créez des constructeurs paramétrés dont les types de paramètre sont les types mappés.

Et voilà, vous obtenez les dépendances injectées dans votre constructeur sans une autre ligne de code!

NOTE: J'ai obtenu cette information d'un des commentaires dans THIS blog par son auteur.

2
Sudhanshu Mishra

Dans une récente RC, je trouve qu'il n'y a plus de méthode SetResolver. Pour activer IoC pour le contrôleur et Webapi, j'utilise Unity.WebApi (NuGet) et le code suivant:

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;
    }
}

J'utilise également UnityConfiguration (également de NuGet) pour la magie IoC. ;)

2
Noogen

Résumé succinct pour l’API Web ASP.NET 2.

Installez Unity à partir de NuGet.

Créez une nouvelle classe appelée 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();
    }
}

Créez une nouvelle classe appelée UnityConfig:

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

Modifier App_Start -> WebApiConfig.cs

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

Maintenant ça va marcher.

Source originale mais un peu modifiée: https://docs.Microsoft.com/en-us/aspnet/web-api/overview/advanced/dependency-injection

1
Ogglas

J'ai eu la même exception et, dans mon cas, j'avais un conflit entre les binaires MVC3 et MVC4. Cela empêchait mes contrôleurs d'être correctement enregistrés avec mon conteneur IOC. Vérifiez votre fichier web.config et assurez-vous qu'il pointe vers les versions correctes de MVC.

1
user1233012

Ce problème est dû à l'enregistrement de contrôleurs avec Unity. J'ai résolu ce problème en utilisant l'enregistrement par convention comme indiqué ci-dessous. Veuillez filtrer tous types supplémentaires si nécessaire.

    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);

De plus, l'exemple ci-dessus utilise AllClasses.FromLoadedAssemblies(). Si vous envisagez de charger des assemblys à partir du chemin de base, il se peut que cela ne fonctionne pas correctement dans un projet d'API Web utilisant Unity. S'il vous plaît jeter un oeil à ma réponse à une autre question liée à cela. https://stackoverflow.com/a/26624602/1350747

1

J'ai eu le même problème lors de l'utilisation du package Unity.WebAPI NuGet. Le problème était que le paquet n'a jamais ajouté un appel à UnityConfig.RegisterComponents() dans mon Global.asax.

Global.asax.cs devrait ressembler à ceci:

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