无法使用Unity将依赖项注入到ASP.NET Web API控制器中

84
有人成功地使用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);
                }
            }

它似乎从未查找我的Unity文件以解决依赖项,我会收到如下错误消息:
“尝试创建类型为'PersonalShopper.Services.WebApi.Controllers.ShoppingListController'的控制器时发生错误。请确保控制器具有无参数的公共构造函数。”
控制器如下所示:
public class ShoppingListController : System.Web.Http.ApiController
    {
        private Repositories.IProductListRepository _ProductListRepository;


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

我的Unity文件看起来像这样:

<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版本中,控制器工厂会确定需要解析的依赖项。
看起来我的控制器工厂从未被调用过。
11个回答

44

搞定了。

对于 ApiControllers,MVC 4 使用一个 System.Web.Http.Dispatcher.IHttpControllerFactorySystem.Web.Http.Dispatcher.IHttpControllerActivator 来创建控制器。如果没有静态方法来注册这些实现,当它们被解析时,mvc 框架会在依赖项解析器中查找这些实现,如果找不到,则使用默认的实现。

我通过以下方式使 Unity 解决控制器依赖关系:

创建了一个 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() 的 lambda 函数的? - jrummell
7
此帖子已过时。请查看CodeKata的回复,使用MVC RC更清晰的解决方案。 - Matt Randle
这已经过时了。微软有一个Nuget包,可以在Web API中进行DI。请看我的回答。 - garethb

38

1
这是一个更好的MVC RC解决方案。您不需要提供IHttpControllerActivator的实现。相反,您需要实现System.Web.Http.Dependencies.IDependencyResolver。 - Matt Randle
22
不太喜欢链接到博客,您能在这里概括一下并附上链接吗? :) - Nate-Wilkins
3

这似乎是一种不错的方法,但我收到了几个错误,如下所示。看起来新的解析器无法解析多个类型。依赖项的解析失败,类型为“System.Web.Http.Hosting.IHostBufferPolicySelector”,名称为“(none)”。 异常发生于:解析时。 异常为:InvalidOperationException-类型IHostBufferPolicySelector没有可访问的构造函数。

在发生异常时,容器为: 解析 System.Web.Http.Hosting.IHostBufferPolicySelector, (
- ATHER
微软有一个 Nuget 包,可以在 Web API 中进行 DI。请查看我的回答。 - garethb

24

2
这里有一篇不错的博客文章(http://netmvc.blogspot.com/2012/04/dependency-injection-in-aspnet-mvc-4.html),其中详细介绍了如何使用Unity.mvc3(也适用于MVC4)和Unit.WebApi的组合来实现相当干净的依赖注入。 - Phred Menyhert
1
在评论中提到的链接反映了更新的 API,对于现在遇到这个问题的任何人:GlobalConfiguration.Configuration.ServiceResolver.SetResolver( new Unity.WebApi.UnityDependencyResolver(container));现在改为GlobalConfiguration.Configuration.DependencyResolver = new Unity.WebApi.UnityDependencyResolver(container); - Robert Zahm
请注意,它提供了比自定义的UnityResolver更好的调试信息。 - Ioannis Karadimas

11

微软已经为此创建了一个包。

在程序包管理器控制台中运行以下命令。

install-package Unity.AspNet.WebApi

如果您已安装Unity,它将询问是否要覆盖App_Start\UnityConfig.cs。 选择否并继续。

不需要更改任何其他代码,依赖注入(使用Unity)将会生效。


你能告诉我为什么当NuGet提示覆盖UnityConfig.cs时我们会回答“否”吗?问题2:有没有使用Unity.AspNet.WebApi的示例代码? - Thomas.Benz
抱歉,由于Unity无法轻松实现某些我需要的功能,而Autofac可以直接实现,所以我已经更换为Autofac。但是从记忆中,如果您已经安装了Unity,则需要拒绝安装,因为您不希望覆盖已经工作的Unity配置,而只想添加Unity WebAPI配置。在WebAPI中使用Unity的方法与通常使用它的方法完全相同。如果它没有像其他类(如控制器)一样注入您的依赖项,我建议您发布一个问题。在WebAPI中注入的方式与其他类别没有区别。 - garethb
@garethb:我正在使用unity.webapi,但必须在单元测试中添加几行代码以解决ControllerContext的问题,这并不是我想要的。假设我使用Unity.AspNet.WebApi,那么是否可以自动从UnitTests项目中解析ControllerContext?请建议。 - sam
我不这么认为。相当确定两者的工作方式类似。我在单元测试中创建一个新的模拟上下文。 - garethb
@garethb:感谢您的快速反应。Mocking 生效了。我无法使用 Unity.WebApi 或 Unity.AspNet.WebApi 注入 UrlHelper。您还记得在使用 Unity 时是如何完成的吗?提前谢谢您。 - sam
@Nattrass 这将安装微软的Unity实现,用于Web API。虽然我没有查看它的实际代码是如何工作的,但我相信你可以在GitHub上找到它? - garethb

3

我曾经遇到过同样的错误,花了几个小时在互联网上寻找解决方案。最终发现,在调用WebApiConfig.Register之前必须先注册Unity。现在我的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

ASP.NET Web API 2简介:

从NuGet安装Unity

创建一个名为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);
    }
}

Edit App_Start -> WebApiConfig.cs

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

现在它将起作用。

原始来源,但稍加修改: https://learn.microsoft.com/en-us/aspnet/web-api/overview/advanced/dependency-injection


2
在最近的RC版本中,我发现不再有SetResolver方法。为了启用控制器和WebAPI的IoC,我使用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(截至2013年8月8日)所需的全部操作:
  1. 添加对“Microsoft.Practices.Unity.dll”的引用[我使用的是版本3.0.0.0,通过NuGet添加]
  2. 添加对“Unity.WebApi.dll”的引用[我使用的是版本0.10.0.0,通过NuGet添加]
  3. 向容器注册类型映射 - 类似于由Unity.WebApi项目添加到项目中的Bootstrapper.cs中的代码。
  4. 在继承自ApiController类的控制器中,创建参数化构造函数,其参数类型为映射类型
惊奇地发现,您可以在不编写其他代码的情况下将依赖项注入到构造函数中!
注意:我从作者的博客的评论中获取了此信息。

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()。如果您想从基本路径加载程序集,在使用Unity的Web API项目中可能无法按预期工作。请查看我对另一个相关问题的回答。https://dev59.com/1H7aa4cB1Zd3GeqPolFm#26624602

1

我遇到了同样的异常,我的情况是MVC3和MVC4二进制文件之间发生了冲突。这导致我的控制器无法正确地注册到我的IOC容器中。请检查您的web.config文件,并确保它指向正确的MVC版本。


这解决了我的 Razor 问题,但没有解决解析 API 控制器的问题。 - Oved D

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接