Не вдається внести залежності в контролер веб-API ASP.NET за допомогою Unity


82

Хтось мав успіх, використовуючи контейнер IoC для введення залежностей у контролери ASP.NET WebAPI? Здається, я не можу змусити це працювати.

Це те, що я роблю зараз.

У моєму 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;
    }

Моя фабрика контролерів:

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

Здається, це ніколи не шукає у моєму файлі єдності для вирішення залежностей, і я отримую повідомлення про помилку:

Під час спроби створити контролер типу „PersonalShopper.Services.WebApi.Controllers.ShoppingListController” сталася помилка. Переконайтеся, що контролер має безпараметричний публічний конструктор.

на System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create (HttpControllerContext controllerContext, тип controllerType) при System.Web.Http.Dispatcher.DefaultHttpControllerFactory.CreateInstance (HttpControllerContext controllerContext, HttpControllerDescriptor controllerDescriptor) в System.Web.Http.Dispatcher.DefaultHttpControllerFactory.CreateController (HttpControllerContext controllerContext, String controllerName) на System.Web.Http.Dispatcher.HttpControllerDispatcher.SendAsyncInternal (HttpRequestMessage request, CancellationToken cancellationToken) в System.Web.Http.Dispatcher.TokenTracker.Dispatcher.

Контролер виглядає так:

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


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

Мій файл єдності виглядає так:

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

Зауважте, що у мене немає реєстрації самого контролера, оскільки в попередніх версіях mvc фабрика контролерів зрозуміла, що залежності потрібно було вирішити.

Здається, мою фабрику контролерів ніколи не називають.

Відповіді:


42

Розібрався.

Для ApiControllers MVC 4 використовує System.Web.Http.Dispatcher.IHttpControllerFactory та System.Web.Http.Dispatcher.IHttpControllerActivator для створення контролерів. Якщо немає статичного методу, щоб зареєструвати, якою реалізацією вони є; коли вони вирішені, фреймворк mvc шукає реалізації в засобі вирішення залежностей, і якщо їх не знайти, використовує реалізації за замовчуванням.

Я отримав роздільну здатність залежностей контролера, працюючи, виконавши наступне:

Створено 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);
    }
}

Зареєстрував цей активатор контролера як реалізацію в самому контейнері єдності:

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

Як ви реалізували lamda GlobalConfiguration.Configuration.ServiceResolver.SetResolver()?
jrummell

7
Ця публікація застаріла. Див. Відповідь CodeKata щодо більш чистого рішення з MVC RC.
Метт Рендл,

Це застаріло. Microsoft має пакет Nuget, який виконує DI з веб-API. Дивіться мою відповідь.
garethb

37

Є краще рішення, яке тут працює коректно

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


1
Це набагато краще рішення з MVC RC. Вам не потрібно надавати реалізацію IHttpControllerActivator. Замість цього ви реалізуєте System.Web.Http.Dependencies.IDependencyResolver.
Метт Рендл,

22
Не великий шанувальник посилань на блоги - не могли б ви підсумувати тут і розмістити посилання? :)
Nate-Wilkins

3
Це схоже на хороший підхід, але я отримую кілька помилок, таких як наступні. Схоже, новий вирішувач не може вирішити кілька типів. Не вдалося вирішити залежність, введіть = "System.Web.Http.Hosting.IHostBufferPolicySelector", name = "(none)". Виняток стався під час: під час вирішення. Виняток: InvalidOperationException - Тип IHostBufferPolicySelector не має доступного конструктора. --- На момент виключення контейнером було:
Resolving

Microsoft має пакет Nuget, який виконує DI з веб-API. Дивіться мою відповідь.
garethb

24

Можливо, ви захочете поглянути на пакет Unity.WebApi NuGet, який все це розбере, а також забезпечить наявність одноразових компонентів.

подивитися

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

або

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


2
Тут є хороший допис у блозі ( netmvc.blogspot.com/2012/04/… ), у якому є покроковий посібник із використання комбінації Unity.mvc3 (також працює з MVC4) та Unit.WebApi для реалізації DI досить чисто.
Фред Менігерт,

1
Посилання, згадане в коментарі, відображає оновлений API для всіх, хто стикається з цим зараз: GlobalConfiguration.Configuration.ServiceResolver.SetResolver (новий Unity.WebApi.UnityDependencyResolver (контейнер)); тепер GlobalConfiguration.Configuration.DependencyResolver = new Unity.WebApi.UnityDependencyResolver (контейнер);
Роберт Захм,

Зверніть увагу, він також забезпечує кращі повідомлення про налагодження, ніж користувальницькі UnityResolver.
Ioannis Karadimas

9

Корпорація Майкрософт створила для цього пакет.

Запустіть наступну команду з консолі менеджера пакетів.

інсталяційний пакет Unity.AspNet.WebApi

Якщо ви вже встановили Unity, він запитає, чи хочете ви перезаписати App_Start \ UnityConfig.cs. Відповідь ні і продовжуй.

Не потрібно міняти будь-який інший код, і DI (з єдністю) спрацює.


Ви можете повідомити мене, чому ми відповідаємо "ні", коли nuget пропонує перезаписати UnityConfig.cs? Питання 2: чи є кілька зразків кодів для використання Unity.AspNet.WebApi?
Томас Бенц,

На жаль, я перейшов на Autofac, оскільки деякі речі, які мені потрібні, Unity зробив нелегко, і Autofac зробив нестандартно. Але з пам’яті вам потрібно сказати «ні», якщо у вас вже встановлено єдність, оскільки ви не хочете перезаписувати свою вже працюючу конфігурацію єдності, юо бажаєте лише додати конфігурацію єдності webapi. Використовувати єдність у webapi - це саме те, як ви використовували б її зазвичай. Якщо це не вводить ваші залежності, як це робиться для інших ваших класів, я пропоную опублікувати запитання. Існує не інший спосіб ін’єкційного введення в webapi, ніж інші класи (наприклад, контролери)
garethb

@garethb: Я використовую union.webapi, але маю додати кілька рядків коду в модульних тестах, щоб вирішити ControllerContext, який мені не подобається. Припустимо, якщо я використовую Unity.AspNet.WebApi, чи буде ця адреса автоматично вирішувати ControllerContext із проекту UnitTests? Будь ласка, запропонуйте
сам

Я не думаю. Впевнений, що обидва вони працюють однаково. Я створюю новий фіктивний контекст у своїх модульних тестах.
garethb

@garethb: Дякуємо за швидку відповідь. знущання спрацювало. Я не можу ввести UrlHelper, використовуючи Unity.WebApi або Unity.Aspnet.webapi. Ви пам’ятаєте, як ви це робили, коли користувались Unity? Дякую заздалегідь
сам

3

У мене була та ж помилка і я шукав рішення в Інтернеті пару годин. Зрештою виявилося, що мені довелося зареєструвати Unity ДО того, як я зателефонував WebApiConfig.Register. Тепер мій global.asax виглядає так

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

Для мене це вирішило проблему, через яку Unity не міг вирішити залежності в моїх контролерах


2

У недавньому RC я виявив, що метод SetResolver більше не існує. Щоб увімкнути IoC для контролера та webapi, я використовую Unity.WebApi (NuGet) та такий код:

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

Я також використовую UnityConfiguration (також від NuGet) для магії IoC. ;)


Чи не було б краще використовувати requestContext.Configuration.DependencyResolver.GetService ()?
Alwyn,

2

Прочитавши відповіді, мені все одно довелося багато копатись, щоб приземлитися за цією проблемою, тож це на користь однолітків: Це все, що вам потрібно зробити в ASP.NET 4 Web API RC (як 8 серпня '13):

  1. Додайте посилання на "Microsoft.Practices.Unity.dll" [Я на версії 3.0.0.0, доданий через NuGet]
  2. Додайте посилання на "Unity.WebApi.dll" [я на версії 0.10.0.0, доданий через NuGet]
  3. Зареєструйте зіставлення типів у контейнері - подібно до коду в Bootstrapper.cs, який додає до вашого проекту проект Unity.WebApi.
  4. У контролерах, які успадковуються від класу ApiController, створіть параметризовані конструктори, які мають типи параметрів як відображені типи

Ось ось, ви отримуєте залежності, введені у ваш конструктор без іншого рядка коду!

ПРИМІТКА: Цю інформацію я отримав з одного з коментарів ЦЬОГО блогу від його автора.


2

Короткий підсумок веб-API ASP.NET 2.

Встановити Unity з NuGet.

Створіть новий клас під назвою 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();
    }
}

Створіть новий клас під назвою UnityConfig:

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

Редагувати App_Start -> WebApiConfig.cs

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

Тепер це спрацює.

Оригінальне джерело, але трохи змінене: https://docs.microsoft.com/en-us/aspnet/web-api/overview/advanced/dependency-injection


1

У мене був такий самий виняток, і в моєму випадку у мене виник конфлікт між бінарними файлами MVC3 та MVC4. Це заважало моїм контролерам реєструватися належним чином у моєму контейнері IOC. Перевірте свою web.config і переконайтеся, що вона вказує на правильні версії MVC.


Це вирішило мої проблеми з Razor, але не вирішило проблему вирішення контролерів api.
Oved D

1

Ця проблема виникає внаслідок реєстрації контролерів у Unity. Я вирішив це, використовуючи реєстрацію за домовленістю, як показано нижче. Будь ласка, відфільтруйте додаткові типи за необхідності.

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

Також наведений вище приклад використовує AllClasses.FromLoadedAssemblies(). Якщо ви розглядаєте завантаження збірок з базового шляху, це може не працювати належним чином у проекті веб-API за допомогою Unity. Погляньте, будь ласка, на мою відповідь на інше питання, пов’язане з цим. https://stackoverflow.com/a/26624602/1350747


0

У мене була та ж проблема під час використання пакета Unity.WebAPI NuGet. Проблема полягала в тому, що пакунок ніколи не додавав дзвінкаUnityConfig.RegisterComponents() мого Global.asax.

Global.asax.cs повинен виглядати так:

public class WebApiApplication : System.Web.HttpApplication
{
    protected void Application_Start()
    {
        UnityConfig.RegisterComponents();
        ...
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.