Невозможно внедрить зависимости в контроллер веб-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, Type controllerType) в System.Web.Http.Dispatcher.DefaultHttpControllerFactory.CreateInstance (HttpControllerFactory.CreateInstance (HttpControllerFactory. (HttpControllerContext controllerContext, String controllerName) в System.Web.Http.Dispatcher.HttpControllerDispatcher.SendAsyncInternal (запрос HttpRequestMessage, CancellationToken cancellationToken) в System.Web.Http.Dispatcher.Http.DispatcherControllerDispatcher.HttpRequestMessage request, CancellationToken cancellationToken.

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

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

Зарегистрировал этот активатор контроллера как реализацию в самом контейнере 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.
       });
}
Овед Д
источник
Как вы реализовали ламду для 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 = "(нет)". Исключение произошло во время: при разрешении. Исключение: InvalidOperationException - тип IHostBufferPolicySelector не имеет доступного конструктора. --- Во время исключения контейнер был: Разрешение System.Web.Http.Hosting.IHostBufferPolicySelector, (
ATHER
У Microsoft есть пакет Nuget, который выполняет DI с веб-API. Смотрите мой ответ.
garethb 07
24

Возможно, вы захотите взглянуть на пакет NuGet Unity.WebApi, который рассортирует все это, а также обслуживает компоненты IDisposable.

видеть

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

или же

http://www.devtrends.co.uk/blog/introduction-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.
Иоаннис Карадимас
9

Microsoft создала для этого пакет.

Выполните следующую команду из консоли диспетчера пакетов.

установочный пакет Unity.AspNet.WebApi

Если у вас уже установлен Unity, он спросит, хотите ли вы перезаписать App_Start \ UnityConfig.cs. Ответьте нет и продолжайте.

Нет необходимости менять какой-либо другой код, и DI (с единицей) будет работать.

Гаретб
источник
не могли бы вы сообщить мне, почему мы отвечаем «нет», когда nuget предлагает перезаписать UnityConfig.cs? Вопрос 2: есть ли примеры кода для использования Unity.AspNet.WebApi?
Thomas.Benz
Извините, я перешел на Autofac, так как были некоторые вещи, которые мне были нужны, но Unity не справился легко, а Autofac сделал из коробки. Но из памяти вам нужно сказать нет, если у вас уже установлено единство, поскольку вы не хотите перезаписывать уже работающую конфигурацию единства, вы хотите только добавить конфигурацию единства webapi. Использование единства в webapi - это именно то, что вы обычно используете. Если он не вводит ваши зависимости, как для других ваших классов, я бы предложил разместить вопрос. Нет другого способа внедрения в webapi, чем другие классы (например, контроллеры)
garethb
@garethb: я использую unity.webapi, но мне нужно добавить несколько строк кода в модульные тесты для разрешения ControllerContext, который мне не нравится. Предположим, если я использую Unity.AspNet.WebApi, будет ли этот адрес автоматически разрешать ControllerContext из проекта UnitTests? Пожалуйста, предложите
sam
Я так не думаю. Уверен, что оба работают одинаково. Я создаю новый фиктивный контекст в своих модульных тестах.
garethb
@garethb: Спасибо за быстрый ответ. издевательство сработало. Я не могу внедрить UrlHelper с помощью Unity.WebApi или Unity.Aspnet.webapi. Вы помните, как у вас это получалось, когда вы использовали Unity? Заранее спасибо
sam
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. ;)

Noogen
источник
Не лучше ли использовать 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

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

Global.asax.cs должен выглядеть так:

public class WebApiApplication : System.Web.HttpApplication
{
    protected void Application_Start()
    {
        UnityConfig.RegisterComponents();
        ...
    }
}
Кит
источник