Autofac MVC 集成
Autofac 总是会紧跟最新的 ASP.NET MVC 版本更新,因此文档也会同步更新到最新版本。通常情况下,各个版本之间的集成方式保持相对一致。
要将 ASP.NET MVC 与 Autofac 集成,需要引用 Autofac.Mvc5 NuGet 包 。
MVC 集成提供了控制器、模型绑定器、动作过滤器和视图的依赖注入支持,并且添加了 生命周期支持 。
此页面解释了 ASP.NET 经典 MVC 的集成。 如果您使用的是 ASP.NET Core,请查看 ASP.NET Core 集成页面 。
快速入门
为了将 Autofac 与 MVC 集成,您需要引用 MVC 集成的 NuGet 包,注册控制器并设置依赖解析器。还可以选择启用其他功能。
protected void Application_Start()
{
var builder = new ContainerBuilder();
// 注册您的MVC控制器。MvcApplication是Global.asax中的类名。
builder.RegisterControllers(typeof(MvcApplication).Assembly);
// 可选:注册需要DI的模型绑定器。
builder.RegisterModelBinders(typeof(MvcApplication).Assembly);
builder.RegisterModelBinderProvider();
// 可选:注册web抽象,如HttpContextBase。
builder.RegisterModule<AutofacWebTypesModule>();
// 可选:在视图页中启用属性注入。
builder.RegisterSource(new ViewRegistrationSource());
// 可选:启用动作过滤器的属性注入。
builder.RegisterFilterProvider();
// 可选:启用动作方法参数注入(很少用到)。
builder.InjectActionInvoker();
// 将依赖解析器设置为Autofac。
var container = builder.Build();
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
}
下面的部分将详细介绍这些功能以及如何使用它们。
注册控制器
在应用程序启动时,在构建 Autofac 容器时,应注册您的 MVC 控制器及其依赖项。这通常发生在 OWIN 启动类或 Global.asax
的 Application_Start
方法中。
var builder = new ContainerBuilder();
// 使用Assembly扫描一次性注册所有控制器...
builder.RegisterControllers(typeof(MvcApplication).Assembly);
// ...或者手动注册单个控制器。
builder.RegisterType<HomeController>().InstancePerRequest();
注意,ASP.NET MVC 通过具体类型请求控制器,所以使用
As<IController>()
注册是不正确的。如果手动注册控制器并选择指定寿命,必须使用InstancePerDependency()
或InstancePerRequest()
进行注册——尝试为多个请求重用控制器实例时,ASP.NET MVC 会抛出异常。
设置依赖解析器
构建容器后,将容器传递给 AutofacDependencyResolver
的新实例。使用静态 DependencyResolver.SetResolver
方法,让 ASP.NET MVC 知道应该使用 AutofacDependencyResolver
来查找服务。这是 Autofac 实现的 IDependencyResolver
接口的实现。
var container = builder.Build();
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
注册模型绑定器
可选步骤之一是为模型绑定器启用依赖注入。与控制器类似,可以在应用程序启动时在容器中注册模型绑定器。可以使用 RegisterModelBinders()
方法完成此操作。同时别忘了使用 RegisterModelBinderProvider()
扩展方法注册 AutofacModelBinderProvider
。这是 Autofac 实现的 IModelBinderProvider
接口的实现。
builder.RegisterModelBinders(Assembly.GetExecutingAssembly());
builder.RegisterModelBinderProvider();
由于 RegisterModelBinders()
扩展方法使用 Assembly 扫描添加所需的模型绑定器,因此需要使用 ModelBinderTypeAttribute
指定模型绑定器(实现了 IModelBinder
的类)应注册的类型。
这样操作:
[ModelBinderType(typeof(string))]
public class StringBinder : IModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
// 实现在这里
}
}
如果一个类需要为多个类型注册,可以在类上添加多个 ModelBinderTypeAttribute
。
注册 Web 抽象
MVC 集成包含一个 Autofac 模块,它将为 web 抽象类添加 HTTP 请求生命周期 注册,允许您在类中将 web 抽象作为依赖项,并在运行时获取正确的值。
以下包括的抽象类:
HttpContextBase
HttpRequestBase
HttpResponseBase
HttpServerUtilityBase
HttpSessionStateBase
HttpApplicationStateBase
HttpBrowserCapabilitiesBase
HttpFileCollectionBase
RequestContext
HttpCachePolicyBase
VirtualPathProvider
UrlHelper
要使用这些抽象类,只需将 AutofacWebTypesModule
添加到容器中,使用标准 RegisterModule()
方法。
builder.RegisterModule<AutofacWebTypesModule>();
启用视图页属性注入
要使您的 MVC 视图支持属性注入,可以在构建应用程序容器之前将 ViewRegistrationSource
添加到 ContainerBuilder
。
builder.RegisterSource(new ViewRegistrationSource());
您的视图页必须继承 MVC 支持创建视图的基类。使用 Razor 视图引擎时,这将是 WebViewPage
类。
public abstract class CustomViewPage : WebViewPage
{
public IDependency Dependency { get; set; }
}
当使用 Web Forms 视图引擎时,ViewPage
、ViewMasterPage
和 ViewUserControl
类是支持的。
public abstract class CustomViewPage : ViewPage
{
public IDependency Dependency { get; set; }
}
确保实际的视图页从自定义基类继承。对于 Razor 视图引擎,可以在 .cshtml
文件中的 @inherits
指令中实现:
@inherits Example.Views.Shared.CustomViewPage
对于 Web Forms 视图引擎,您需要在 .aspx
文件的 @ Page
指令的 Inherits
属性中指定。
<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="Example.Views.Shared.CustomViewPage" %>
由于 ASP.NET MVC 内部的一个问题,属性注入不可用于 Razor 布局页。 视图工作正常,但布局页不会。有关更多详细信息,请参阅问题#349。
启用动作过滤器属性注入
要使用动作过滤器的属性注入,只需在构建容器之前调用 RegisterFilterProvider()
方法,并将容器提供给 AutofacDependencyResolver
。
builder.RegisterFilterProvider();
这允许您在过滤器属性上添加属性,并将容器中注册的任何匹配依赖项注入到属性中。
例如,下面的动作过滤器将在容器中注入 ILogger
实例(假设已注册 ILogger
)。请注意,过滤器本身不需要在容器中注册。
public class CustomActionFilter : ActionFilterAttribute
{
public ILogger Logger { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
Logger.Log("OnActionExecuting");
}
}
其他过滤器属性类型(如授权属性)也可以使用相同简单的方法。
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
public ILogger Logger { get; set; }
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
Logger.Log("AuthorizeCore");
return true;
}
}
按照常规将这些属性应用到动作上后,就完成了工作。
[CustomActionFilter]
[CustomAuthorizeAttribute]
public ActionResult Index()
{
}
启用操作参数的注入
虽然不常见,但有些人希望在调用动作方法时由 Autofac 填充参数。建议您在控制器上使用构造函数注入而不是动作方法注入,但如果愿意,可以启用动作方法注入:
// Autofac ExtensibleActionInvoker尝试从请求生命周期范围中解决参数,如果模型绑定器无法绑定到参数。
builder.RegisterType<ExtensibleActionInvoker>().As<IActionInvoker>();
builder.InjectActionInvoker();
您也可以使用 InjectActionInvoker()
机制与自定义调用者一起使用。
builder.RegisterType<MyCustomActionInvoker>().As<IActionInvoker>();
builder.InjectActionInvoker();
OWIN 集成
如果您使用 MVC 作为 OWIN 应用程序的一部分,则需要执行以下操作:
- 执行标准 MVC 集成的所有内容——注册控制器,设置依赖解析器等。
- 使用 Autofac OWIN 集成 设置应用程序。
- 引用 Autofac.Mvc5.Owin NuGet 包。
- 在应用程序启动类中,先注册基础 Autofac 中间件,然后注册 Autofac MVC 中间件。
public class Startup
{
public void Configuration(IAppBuilder app)
{
var builder = new ContainerBuilder();
// STANDARD MVC SETUP:
// Register your MVC controllers.
builder.RegisterControllers(typeof(MvcApplication).Assembly);
// Run other optional steps, like registering model binders,
// web abstractions, etc., then set the dependency resolver
// to be Autofac.
var container = builder.Build();
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
// OWIN MVC SETUP:
// Register the Autofac middleware FIRST, then the Autofac MVC middleware.
app.UseAutofacMiddleware(container);
app.UseAutofacMvc();
}
}
小问题:MVC 并不能完全运行在 OWIN 管道中。 它仍然需要 HttpContext.Current
和一些非 OWIN 的东西。在应用程序启动时,当 MVC 注册路由时,它会实例化一个 IControllerFactory
,最终创建两个请求生命周期范围。这只会在应用启动时的路由注册期间发生,而不是在开始处理请求时,但这是需要了解的事情。这是两个管道被搅和在一起的遗留物。我们曾尝试过寻找解决方法,但未能以一种干净的方式做到这一点。
使用“插件”程序集
如果你在一个未被主应用程序引用的“插件程序集”中拥有控制器,你需要将控制器插件程序集与 ASP.NET BuildManager 注册。
你可以通过配置或编程方式来实现。
如果你选择配置,你需要将插件程序集添加到 /configuration/system.web/compilation/assemblies
列表中。如果您的插件程序集不在 bin
文件夹中,还需要更新 /configuration/runtime/assemblyBinding/probing
路径。
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<!--
如果你的插件放在一个不是bin的文件夹中,请将其添加到探查路径
-->
<probing privatePath="bin;bin\plugins" />
</assemblyBinding>
</runtime>
<system.web>
<compilation>
<assemblies>
<add assembly="The.Name.Of.Your.Plugin.Assembly.Here" />
</assemblies>
</compilation>
</system.web>
</configuration>
如果你选择程序化注册,你需要在 ASP.NET BuildManager
启动之前,在应用程序启动前进行操作。
创建一个初始化类,用于扫描加载程序集并使用 BuildManager
进行注册:
using System.IO;
using System.Reflection;
using System.Web.Compilation;
namespace MyNamespace
{
public static class Initializer
{
public static void Initialize()
{
var pluginFolder = new DirectoryInfo(HostingEnvironment.MapPath("~/plugins"));
var pluginAssemblies = pluginFolder.GetFiles("*.dll", SearchOption.AllDirectories);
foreach (var pluginAssemblyFile in pluginAssemblyFiles)
{
var asm = Assembly.LoadFrom(pluginAssemblyFile.FullName);
BuildManager.AddReferencedAssembly(asm);
}
}
}
}
然后确保使用一个特性来注册你的应用程序启动前代码:
[assembly: PreApplicationStartMethod(typeof(Initializer), "Initialize")]
使用当前 Autofac 依赖解析器
一旦你将 MVC 的 DependencyResolver
设置为 AutofacDependencyResolver
,你可以使用 AutofacDependencyResolver.Current
作为获取当前依赖解析器并将其转换为 AutofacDependencyResolver
的快捷方式。
不幸的是,关于使用 AutofacDependencyResolver.Current
有一些问题,可能导致某些功能无法正常工作。通常这些问题是由使用像 Glimpse 或 Castle DynamicProxy 这样的产品引起的,它们“包装”或“装饰”依赖解析器以添加功能。如果当前的依赖解析器被装饰或以其他方式包装/代理,你就不能将其转换为 AutofacDependencyResolver
,也没有单一的方法来“解包”它或获取实际的解析器。
在 Autofac MVC 集成的 3.3.3 版本之前,我们通过动态添加它到请求生命周期范围来跟踪当前的依赖解析器。这让我们绕过了无法从代理中解包 AutofacDependencyResolver
的问题……但这意味着 AutofacDependencyResolver.Current
只能在请求生命周期内工作——你不能在后台任务或应用程序启动时使用它。
从 3.3.3 版本开始,查找 AutofacDependencyResolver.Current
逻辑的变化首先尝试将当前依赖解析器转换为类型;然后特别查找使用 Castle DynamicProxy 包装的迹象,并通过反射解包它。如果失败……我们找不到当前的 AutofacDependencyResolver
,因此会抛出一个 InvalidOperationException
,消息类似于:
依赖解析器的类型是 'Some.Other.DependencyResolver',但预期是类型
Autofac.Integration.Mvc.AutofacDependencyResolver
。它看起来也不像是由 Castle Project 的 DynamicProxy 包裹的。这个问题可能是由 DynamicProxy 实现的更改或使用不同的代理库包裹依赖解析器导致的。
这种问题最常出现在使用 ContainerBuilder.RegisterFilterProvider()
的 action 过滤器提供程序时。过滤器提供程序需要访问 Autofac 依赖解析器,并使用 AutofacDependencyResolver.Current
来执行它。
如果你看到这个,这意味着你在以无法解包的方式装饰解析器,依赖于 AutofacDependencyResolver.Current
的功能将失败。目前的解决方案是不要装饰依赖解析器。
Glimpse 集成
使用 Autofac 的 MVC 应用程序与 Glimpse 的集成与其他集成基本相同。但是,如果你使用动作方法参数注入(例如,通过 builder.InjectActionInvoker()
),则 Glimpse 的执行检查会失败。
你可以通过在 Glimpse 配置中添加以下内容来解决这个问题:
<glimpse defaultRuntimePolicy="On" endpointBaseUri="~/Glimpse.axd">
<inspectors>
<ignoredTypes>
<add type="Glimpse.Mvc.Inspector.ExecutionInspector, Glimpse.Mvc"/>
</ignoredTypes>
</inspectors>
<tabs>
<ignoredTypes>
<add type="Glimpse.Mvc.Tab.Execution, Glimpse.Mvc"/>
</ignoredTypes>
</tabs>
</glimpse>
再次强调,只有在使用动作参数注入时才需要这样做。这是推荐使用控制器构造函数注入而不是动作方法参数注入的众多原因之一。
有关更多信息(包括来自 Glimpse 的相关信息链接),请参阅 此问题。
单元测试
当你在一个使用 Autofac 的 ASP.NET MVC 应用程序中进行单元测试,而你注册了 InstancePerRequest
组件时,你会在尝试解决这些组件时遇到异常,因为在单元测试中没有 HTTP 请求生命周期。
每一次请求生命周期范围 主题概述了测试和调试请求范围组件的策略。
示例
在 Autofac 示例仓库中有一个展示 ASP.NET MVC 集成的示例项目 此处。