Nie można wstrzyknąć zależności do kontrolera interfejsu API sieci Web ASP.NET przy użyciu aparatu Unity


82

Czy ktoś odniósł jakikolwiek sukces, używając kontenera IoC do wstrzykiwania zależności do kontrolerów ASP.NET WebAPI? Nie wydaje mi się, żeby to działało.

To właśnie teraz robię.

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

Moja fabryka kontrolerów:

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

Wydaje się, że nigdy nie zagląda do mojego pliku unity, aby rozwiązać zależności, i otrzymuję błąd taki jak:

Wystąpił błąd podczas próby utworzenia kontrolera typu „PersonalShopper.Services.WebApi.Controllers.ShoppingListController”. Upewnij się, że kontroler ma konstruktora publicznego bez parametrów.

at System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create (HttpControllerContext controllerContext, Type controllerType) w System.Web.Http.Dispatcher.DefaultHttpControllerFactory.CreateInstance (HttpControllerContext controllerContext, Type controllerType) w System.Web.Http.Dispatcher.DefaultHttpControllerFactory.CreateInstance (HttpControllerContext controllerContext, HttpContrcripateController) (HttpControllerContext controllerContext, String controllerName) w System.Web.Http.Dispatcher.HttpControllerDispatcher.SendAsyncInternal (HttpRequestMessage request, CancellationToken cancellationToken) w System.Web.Http.Dispatcell.HttpContrendollerAsyncInternal (HttpRequestMessage request, CancellationToken cancellationToken) w System.Web.Http.Dispatcell.HttpContrendól

Kontroler wygląda następująco:

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


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

Mój plik Unity wygląda następująco:

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

Zauważ, że nie mam rejestracji samego kontrolera, ponieważ w poprzednich wersjach MVC fabryka kontrolerów zorientowała się, że zależności wymagają rozwiązania.

Wygląda na to, że nigdy nie dzwoniono do mojej fabryki kontrolerów.

Odpowiedzi:


42

Domyśliłam się.

W przypadku ApiControllers MVC 4 używa System.Web.Http.Dispatcher.IHttpControllerFactory i System.Web.Http.Dispatcher.IHttpControllerActivator do tworzenia kontrolerów. Jeśli nie ma statycznej metody rejestracji, jaka jest ich implementacja; po ich rozwiązaniu struktura mvc szuka implementacji w programie rozpoznawania zależności, a jeśli nie zostaną znalezione, używa domyślnych implementacji.

Otrzymałem rozwiązanie zależności kontrolera jedności działające, wykonując następujące czynności:

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

Zarejestrowałem ten aktywator kontrolera jako implementację w samym kontenerze Unity:

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

Jak zaimplementowałeś lamdę GlobalConfiguration.Configuration.ServiceResolver.SetResolver()?
jrummell

7
Ten post jest nieaktualny. Zobacz odpowiedź CodeKata, aby uzyskać czystsze rozwiązanie z MVC RC.
Matt Randle

To jest nieaktualne. Firma Microsoft ma pakiet Nuget, który obsługuje DI z interfejsem API sieci Web. Zobacz moją odpowiedź.
garethb

37

1
To znacznie lepsze rozwiązanie z MVC RC. Nie musisz zapewniać implementacji IHttpControllerActivator. Zamiast tego implementujesz System.Web.Http.Dependencies.IDependencyResolver.
Matt Randle

22
Nie jesteś wielkim fanem linków do blogów - czy mógłbyś podsumować tutaj i umieścić link? :)
Nate-Wilkins

3
Wygląda na dobre podejście, ale pojawia się kilka błędów, takich jak śledzenie. Wygląda na to, że nowy resolver nie może rozwiązać kilku typów. Rozwiązanie zależności nie powiodło się, typ = "System.Web.Http.Hosting.IHostBufferPolicySelector", name = "(none)". Wyjątek wystąpił podczas: podczas rozpatrywania. Wyjątkiem jest: InvalidOperationException - typ IHostBufferPolicySelector nie ma dostępnego konstruktora. --- W momencie wystąpienia wyjątku kontener był:
Resolving

Firma Microsoft ma pakiet Nuget, który obsługuje DI z interfejsem API sieci Web. Zobacz moją odpowiedź.
garethb

24

Możesz rzucić okiem na pakiet Unity.WebApi NuGet, który rozwiąże to wszystko, a także zapewni składniki IDisposable.

widzieć

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

lub

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


2
Jest tutaj dobry post na blogu ( netmvc.blogspot.com/2012/04/… ), który zawiera przewodnik po użyciu kombinacji Unity.mvc3 (działa również z MVC4) i Unit.WebApi w celu całkiem przejrzystego zaimplementowania DI.
Phred Menyhert,

1
Odnośnik wymieniony w komentarzu odzwierciedla zaktualizowane API, dla każdego, kto teraz zetknie się z tym: GlobalConfiguration.Configuration.ServiceResolver.SetResolver (nowy Unity.WebApi.UnityDependencyResolver (container)); jest teraz GlobalConfiguration.Configuration.DependencyResolver = new Unity.WebApi.UnityDependencyResolver (container);
Robert Zahm

Zwróć uwagę, że zapewnia również lepsze komunikaty debugowania niż niestandardowe UnityResolver.
Ioannis Karadimas,

9

Microsoft stworzył w tym celu pakiet.

Uruchom następujące polecenie z konsoli menedżera pakietów.

zainstaluj pakiet Unity.AspNet.WebApi

Jeśli masz już zainstalowany Unity, zapyta, czy chcesz nadpisać App_Start \ UnityConfig.cs. Odpowiedz nie i kontynuuj.

Nie trzeba zmieniać żadnego innego kodu, a DI (z jednością) będzie działać.


czy możesz dać mi znać, dlaczego odpowiadamy „nie”, gdy nuget monituje o nadpisanie UnityConfig.cs? Pytanie 2: czy istnieje kilka przykładowych kodów do korzystania z Unity.AspNet.WebApi?
Thomas.Benz

Przepraszam, zmieniłem na Autofac, ponieważ potrzebowałem pewnych rzeczy, których Unity nie robił łatwo, a Autofac zrobił to po wyjęciu z pudełka. Ale z pamięci musisz powiedzieć nie, jeśli masz już zainstalowany Unity, ponieważ nie chcesz nadpisywać już działającej konfiguracji Unity, chcesz tylko dodać konfigurację unity webapi. Używanie jedności w webapi jest dokładnie takie, jak zwykle. Jeśli nie jest to wstrzykiwanie twoich zależności, tak jak w przypadku innych klas, sugerowałbym zadać pytanie. Nie ma innego sposobu wstrzykiwania do webapi niż inne klasy (takie jak kontrolery)
garethb

@garethb: Używam unity.webapi, ale muszę dodać kilka wierszy kodu w testach jednostkowych, aby rozwiązać ControllerContext, co mi się nie podoba. Załóżmy, że jeśli korzystam z Unity.AspNet.WebApi, czy będzie to adresowane do automatycznego rozwiązywania ControllerContext z poziomu projektu UnitTests? Proszę o sugestię
sam

Nie sądzę. Na pewno oba działają w podobny sposób. Tworzę nowy kontekst próbny w moich testach jednostkowych.
garethb

@garethb: Dziękuję za szybką odpowiedź. kpiny zadziałały. Nie mogę wstrzyknąć UrlHelpera przy użyciu Unity.WebApi lub Unity.Aspnet.webapi. Czy pamiętasz, jak to robiłeś, używając Unity? Z góry dziękuję
sam

3

Miałem ten sam błąd i szukałem rozwiązań w Internecie przez kilka godzin. Ostatecznie okazało się, że muszę zarejestrować Unity PRZED wywołaniem WebApiConfig.Register. Mój global.asax wygląda teraz tak

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

Dla mnie to rozwiązało problem, że Unity nie może rozwiązać zależności w moich kontrolerach


2

W niedawnym RC stwierdziłem, że nie ma już metody SetResolver. Aby włączyć IoC dla kontrolera i webapi, używam Unity.WebApi (NuGet) i następującego kodu:

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

Używam również UnityConfiguration (również z NuGet) do magii IoC. ;)


Czy nie byłoby lepiej użyć requestContext.Configuration.DependencyResolver.GetService ()?
Alwyn

2

Po przeczytaniu odpowiedzi nadal musiałem dużo kopać, aby wylądować w tej pułapce, więc tutaj jest to z korzyścią dla rówieśników: To wszystko, co musisz zrobić w ASP.NET 4 Web API RC (stan na 8 sierpnia 13):

  1. Dodaj odwołanie do „Microsoft.Practices.Unity.dll” [korzystam z wersji 3.0.0.0, dodanej przez NuGet]
  2. Dodaj odwołanie do „Unity.WebApi.dll” [korzystam z wersji 0.10.0.0, dodanej przez NuGet]
  3. Zarejestruj mapowania typów w kontenerze - podobnie do kodu w Bootstrapper.cs, który jest dodawany do projektu przez projekt Unity.WebApi.
  4. W kontrolerach dziedziczących po klasie ApiController utwórz sparametryzowane konstruktory, których typy parametrów będą odpowiadały typom mapowanym

I oto zależności wstawione do konstruktora bez kolejnej linii kodu!

UWAGA: otrzymałem tę informację z jednego z komentarzy na TYM blogu jego autora.


2

Krótkie podsumowanie dotyczące ASP.NET Web API 2.

Zainstaluj Unityz NuGet.

Utwórz nową klasę o nazwie 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();
    }
}

Utwórz nową klasę o nazwie UnityConfig:

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

Edytuj App_Start -> WebApiConfig.cs

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

Teraz to zadziała.

Oryginalne źródło, ale nieco zmodyfikowane: https://docs.microsoft.com/en-us/aspnet/web-api/overview/advanced/dependency-injection


1

Miałem ten sam wyjątek, aw moim przypadku wystąpił konflikt między plikami binarnymi MVC3 i MVC4. To uniemożliwiało prawidłową rejestrację moich kontrolerów w moim kontenerze IOC. Sprawdź swój plik web.config i upewnij się, że wskazuje poprawną wersję MVC.


To rozwiązało moje problemy z Razor, ale nie rozwiązało problemu z rozwiązywaniem kontrolerów API.
Oved D

1

Ten problem występuje z powodu rejestracji kontrolerów w Unity. Rozwiązałem to, używając rejestracji według konwencji, jak pokazano poniżej. W razie potrzeby odfiltruj wszelkie dodatkowe typy.

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

Również powyższy przykład używa AllClasses.FromLoadedAssemblies(). Jeśli patrzysz na ładowanie zestawów ze ścieżki podstawowej, może to nie działać zgodnie z oczekiwaniami w projekcie interfejsu API sieci Web przy użyciu aparatu Unity. Proszę spojrzeć na moją odpowiedź na inne związane z tym pytanie. https://stackoverflow.com/a/26624602/1350747


0

Miałem ten sam problem podczas korzystania z pakietu NuGet Unity.WebAPI. Problem polegał na tym, że pakiet nigdy nie dodał wywołania do UnityConfig.RegisterComponents()w moim Global.asax.

Global.asax.cs powinien wyglądać tak:

public class WebApiApplication : System.Web.HttpApplication
{
    protected void Application_Start()
    {
        UnityConfig.RegisterComponents();
        ...
    }
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.