Abhängigkeiten können mit Unity nicht in ASP.NET Web API Controller injiziert werden

82

Hat jemand erfolgreich mit einem IoC-Container ausgeführt, um Abhängigkeiten in ASP.NET-WebAPI-Controller einzufügen? Ich kann es nicht zum Laufen bringen.

Das mache ich jetzt.

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

Meine Controller-Fabrik:

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

Es scheint nie in meiner Unity-Datei zu suchen, um Abhängigkeiten aufzulösen, und ich erhalte die folgende Fehlermeldung:

Beim Versuch, einen Controller vom Typ 'PersonalShopper.Services.WebApi.Controllers.ShoppingListController' zu erstellen, ist ein Fehler aufgetreten. Stellen Sie sicher, dass der Controller über einen parameterlosen öffentlichen Konstruktor verfügt.

bei System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create (HttpControllerContext Controller, Typ controller) bei System.Web.Http.Dispatcher.DefaultHttpControllerFactory.CreateInstance (HttpControllerContext Controller, HttpControllerDescriptor controllerDescriptor) bei System.Web.Http.Dispatcher.DefaultHttpControllerFactory.CreateController (HttpControllerContext controllerContext, String controllerName) unter System.Web.Http.Dispatcher.HttpControllerDispatcher.SendAsyncInternal (HttpRequestMessage-Anforderung, CancellationToken CancellationToken) unter System.Web.Http.Dispatcher.Http.

Controller sieht aus wie:

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


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

Meine Unity-Datei sieht folgendermaßen aus:

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

Beachten Sie, dass ich keine Registrierung für den Controller selbst habe, da in früheren Versionen von mvc die Controller-Factory herausgefunden hat, dass die Abhängigkeiten aufgelöst werden müssen.

Es scheint, als würde meine Controller-Fabrik niemals aufgerufen.

Oved D.
quelle

Antworten:

42

Herausgefunden.

Für ApiController verwendet MVC 4 einen System.Web.Http.Dispatcher.IHttpControllerFactory und System.Web.Http.Dispatcher.IHttpControllerActivator , um die Controller zu erstellen. Wenn es keine statische Methode gibt, um zu registrieren, um welche Implementierung es sich handelt; Wenn sie aufgelöst werden, sucht das MVC-Framework nach den Implementierungen im Abhängigkeitsauflöser. Wenn sie nicht gefunden werden, werden die Standardimplementierungen verwendet.

Ich habe eine Einheitsauflösung von Controller-Abhängigkeiten erhalten, die wie folgt funktioniert:

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

Registrierte diesen Controller-Aktivator als Implementierung im Unity-Container selbst:

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.
quelle
Wie haben Sie die Lamda implementiert GlobalConfiguration.Configuration.ServiceResolver.SetResolver()?
Jrummell
7
Dieser Beitrag ist veraltet. In der Antwort von CodeKata finden Sie eine sauberere Lösung mit MVC RC.
Matt Randle
Das ist veraltet. Microsoft hat ein Nuget-Paket, das DI mit der Web-API ausführt. Siehe meine Antwort.
Garethb
37

Es gibt eine bessere Lösung, die hier richtig funktioniert

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

CodeKata
quelle
1
Dies ist eine viel bessere Lösung mit MVC RC. Sie müssen keine Implementierung von IHttpControllerActivator bereitstellen. Sie implementieren stattdessen System.Web.Http.Dependencies.IDependencyResolver.
Matt Randle
22
Kein großer Fan von Links zu Blogs - könnten Sie hier zusammenfassen und den Link setzen? :)
Nate-Wilkins
3
Es sieht nach einem guten Ansatz aus, aber ich erhalte einige Fehler wie die folgenden. Der neue Resolver kann anscheinend nicht mehrere Typen auflösen. Die Auflösung der Abhängigkeit ist fehlgeschlagen. Geben Sie = "System.Web.Http.Hosting.IHostBufferPolicySelector", name = "(none)" ein. Ausnahme aufgetreten während: beim Auflösen. Ausnahme ist: InvalidOperationException - Der Typ IHostBufferPolicySelector verfügt nicht über einen zugänglichen Konstruktor. --- Zum Zeitpunkt der Ausnahme war der Container: Auflösen von System.Web.Http.Hosting.IHostBufferPolicySelector, (
ATHER
Microsoft hat ein Nuget-Paket, das DI mit der Web-API ausführt. Siehe meine Antwort.
Garethb
24

Vielleicht möchten Sie sich das Unity.WebApi NuGet-Paket ansehen, das all dies regelt und auch IDisposable-Komponenten berücksichtigt.

sehen

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

oder

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

Paul Hiles
quelle
2
Hier gibt es einen guten Blog-Beitrag ( netmvc.blogspot.com/2012/04/… ), in dem eine Kombination aus Unity.mvc3 (funktioniert auch mit MVC4) und Unit.WebApi verwendet wird, um DI ziemlich sauber zu implementieren.
Phred Menyhert
1
Der im Kommentar erwähnte Link spiegelt die aktualisierte API für alle wider, die jetzt darauf stoßen: GlobalConfiguration.Configuration.ServiceResolver.SetResolver (neuer Unity.WebApi.UnityDependencyResolver (Container)); ist jetzt GlobalConfiguration.Configuration.DependencyResolver = new Unity.WebApi.UnityDependencyResolver (Container);
Robert Zahm
Beachten Sie, dass es auch bessere Debugging-Meldungen als die benutzerdefinierten bietet UnityResolver.
Ioannis Karadimas
9

Microsoft hat dafür ein Paket erstellt.

Führen Sie den folgenden Befehl über die Paketmanagerkonsole aus.

Installationspaket Unity.AspNet.WebApi

Wenn Sie Unity bereits installiert haben, werden Sie gefragt, ob Sie App_Start \ UnityConfig.cs überschreiben möchten. Antworte nein und fahre fort.

Sie müssen keinen anderen Code ändern und DI (mit Einheit) funktioniert.

garethb
quelle
Können Sie mich wissen lassen, warum wir mit "Nein" antworten, wenn das Nuget zum Überschreiben von UnityConfig.cs auffordert? Frage 2: Gibt es einige Beispielcodes für Unity.AspNet.WebApi?
Thomas.Benz
Entschuldigung, ich habe zu Autofac gewechselt, da ich einige Dinge brauchte, die Unity nicht einfach und Autofac sofort erledigte. Aber aus dem Speicher müssen Sie Nein sagen, wenn Sie bereits Unity installiert haben, da Sie Ihre bereits funktionierende Unity-Konfiguration nicht überschreiben möchten. Sie möchten nur die Unity-Webapi-Konfiguration hinzufügen. Die Verwendung von Unity in Webapi ist genau so, wie Sie es normalerweise verwenden würden. Wenn es Ihre Abhängigkeiten nicht wie für Ihre anderen Klassen einfügt, würde ich vorschlagen, eine Frage zu stellen. Es gibt keine andere Art, in Webapi zu injizieren als andere Klassen (wie Controller)
Garethb
@garethb: Ich verwende entity.webapi, muss aber in Unit-Tests einige Codezeilen hinzufügen, um ControllerContext aufzulösen, den ich nicht mag. Angenommen, wenn ich Unity.AspNet.WebApi verwende, würde diese Adresse ControllerContext automatisch aus dem UnitTests-Projekt heraus auflösen? Bitte vorschlagen
Sam
Das glaube ich nicht. Ziemlich sicher, dass beide auf ähnliche Weise funktionieren. Ich erstelle in meinen Unit-Tests einen neuen Scheinkontext.
Garethb
@garethb: Danke für deine schnelle Antwort. Spott hat funktioniert. Ich kann UrlHelper weder mit Unity.WebApi noch mit Unity.Aspnet.webapi injizieren. Erinnerst du dich, wie du es gemacht hast, als du Unity benutzt hast? Vielen Dank im Voraus
Sam
3

Ich hatte den gleichen Fehler und suchte einige Stunden im Internet nach Lösungen. Schließlich schien es, dass ich Unity registrieren musste, bevor ich WebApiConfig.Register aufrief. Mein global.asax sieht jetzt so aus

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

Für mich löste dies das Problem, dass Unity die Abhängigkeiten in meinen Controllern nicht auflösen konnte

Vincent
quelle
2

In einem kürzlich erschienenen RC stelle ich fest, dass es keine SetResolver-Methode mehr gibt. Um sowohl IoC für Controller als auch Webapi zu aktivieren, verwende ich Unity.WebApi (NuGet) und den folgenden Code:

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

Ich benutze auch UnityConfiguration (ebenfalls von NuGet) für IoC-Magie. ;)

Noogen
quelle
Wäre es nicht besser, requestContext.Configuration.DependencyResolver.GetService () zu verwenden?
Alwyn
2

Nachdem ich die Antworten gelesen hatte, musste ich noch viel herumgraben, um in diesem Fall zu landen. Hier ist es also für Peers von Vorteil: Dies ist alles, was Sie in ASP.NET 4 Web API RC tun müssen (Stand: 8. August) '13):

  1. Fügen Sie einen Verweis auf "Microsoft.Practices.Unity.dll" hinzu [Ich bin auf Version 3.0.0.0, hinzugefügt über NuGet]
  2. Fügen Sie einen Verweis auf "Unity.WebApi.dll" hinzu [Ich bin auf Version 0.10.0.0, hinzugefügt über NuGet]
  3. Registrieren Sie Ihre Typzuordnungen im Container - ähnlich dem Code in Bootstrapper.cs, der vom Unity.WebApi-Projekt zu Ihrem Projekt hinzugefügt wird.
  4. Erstellen Sie in den Controllern, die von der ApiController-Klasse erben, parametrisierte Konstruktoren, deren Parametertypen die zugeordneten Typen sind

Und siehe da, Sie erhalten die Abhängigkeiten ohne eine weitere Codezeile in Ihren Konstruktor eingefügt!

HINWEIS: Ich habe diese Informationen aus einem der Kommentare in DIESEM Blog des Autors erhalten.

Sudhanshu Mishra
quelle
2

Kurze Zusammenfassung für ASP.NET Web API 2.

UnityVon NuGet installieren .

Erstellen Sie eine neue Klasse mit dem Namen 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();
    }
}

Erstellen Sie eine neue Klasse mit dem Namen UnityConfig:

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

Bearbeiten Sie App_Start -> WebApiConfig.cs

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

Jetzt wird es funktionieren.

Ursprüngliche Quelle, jedoch etwas geändert: https://docs.microsoft.com/en-us/aspnet/web-api/overview/advanced/dependency-injection

Ogglas
quelle
1

Ich hatte die gleiche Ausnahme, die ausgelöst wurde, und in meinem Fall hatte ich einen Konflikt zwischen MVC3- und MVC4-Binärdateien. Dies verhinderte, dass meine Controller ordnungsgemäß in meinem IOC-Container registriert wurden. Überprüfen Sie Ihre web.config und stellen Sie sicher, dass sie auf die richtigen Versionen von MVC verweist.


quelle
Dies hat meine Razor-Probleme behoben, aber das Problem der Lösung von API-Controllern nicht behoben.
Oved D
1

Dieses Problem tritt aufgrund der Registrierung von Controllern bei Unity auf. Ich habe dies gelöst, indem ich die Registrierung gemäß Konvention wie unten gezeigt verwendet habe. Bitte filtern Sie nach Bedarf weitere Typen heraus.

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

Auch das obige Beispiel verwendet AllClasses.FromLoadedAssemblies(). Wenn Sie Assemblys aus dem Basispfad laden möchten, funktioniert dies möglicherweise nicht wie erwartet in einem Web-API-Projekt mit Unity. Bitte werfen Sie einen Blick auf meine Antwort auf eine andere diesbezügliche Frage. https://stackoverflow.com/a/26624602/1350747

Srihari Sridharan
quelle
0

Ich hatte das gleiche Problem bei der Verwendung des Unity.WebAPI NuGet-Pakets. Das Problem war, dass das Paket UnityConfig.RegisterComponents()in meiner Global.asax nie einen Aufruf hinzugefügt hat.

Global.asax.cs sollte folgendermaßen aussehen:

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