.NET手记-为ASP.NET MVC程序集成Autofac

Changwei | 1/19/2016 4:47:00 PM


MVC

 

Autofac总是会紧跟最新版本的ASP.NET MVC框架,所以文档也会一直保持更新。一般来讲,不同版本的框架集成Autofac的方法一般不变。

MVC集成需要引用 Autofac.Mvc5 NuGet 包.

MVC 集成库提供对控制器(Controller)、模型绑定器(model binders)、行为筛选器(action filters)和视图(views)的依赖注入. 它也添了对 每次请求生命周期(per-request lifetime)的支持.

 

快速开始 Quick Start

 

为了将Autofac集成进MVC项目,你需要引用Autofac.MVC集成库,注册你的控制器以及设定依赖解析器。你也可以启用其他可选的特性。

 

protected void Application_Start()
{
  var builder = new ContainerBuilder();

  // Register your MVC controllers.
  builder.RegisterControllers(typeof(MvcApplication).Assembly);

  // OPTIONAL: Register model binders that require DI.
  builder.RegisterModelBinders(Assembly.GetExecutingAssembly());
  builder.RegisterModelBinderProvider();

  // OPTIONAL: Register web abstractions like HttpContextBase.
  builder.RegisterModule<AutofacWebTypesModule>();

  // OPTIONAL: Enable property injection in view pages.
  builder.RegisterSource(new ViewRegistrationSource());

  // OPTIONAL: Enable property injection into action filters.
  builder.RegisterFilterProvider();

  // Set the dependency resolver to be Autofac.
  var container = builder.Build();
  DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
}

 

接下来的部分将说明关于这些特性进一步的细节以及如何使用它们。

 

注册控制器 Register Controllers

 

在应用程序启动时,你在构建自己的容器(ContainerBuilder)时, 也应该注册所有的控制器以及它们的依赖项. 这通常会发生在典型的OWIN启动类或者  Global.asax 的 Application_Start 方法中.

 

var builder = new ContainerBuilder();

// You can register controllers all at once using assembly scanning...
builder.RegisterControllers(typeof(MvcApplication).Assembly);

// ...or you can register individual controlllers manually.
builder.RegisterType<HomeController>().InstancePerRequest();

 

注意,ASP.NET MVC通过实体类型来请求控制器实例,所以不能将他们注册为 As<IController>(). 同时,如果你手动注册控制器且选择定制生命周期,你必须将他们注册为InstancePerDependency() 或者 InstancePerRequest()- 在这种情况下,如果你尝试为多个请求重用一个控制器实例,ASP.NET MVC程序将会抛出异常

 

设定依赖解析器 Set the Dependency Resolver

 

在构建好容器后, 将他传入一个AutofacDependencyResolver 类的实例中. 使用静态方法 DependencyResolver.SetResolver 来使 ASP.NET MVC知道它应该使用 AutofacDependencyResolver 来定位服务(Service). 这是一个 IDependencyResolver 接口的Autofac实现.

 

var container = builder.Build();
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));

 

注册模型绑定器 Register Model Binders

 

你可以使用的一个可选步骤是确保对模型绑定器的依赖注入。和注册控制器类似,模型绑定器(IModelBinder接口的实现类)能够在应用程序启动时注册进容器中。你可以使用 RegisterModelBinders() 方法来这样做。你同时也必须记住要使用 RegisterModelBinderProvider() 拓展方法注册 AutofacModelBinderProvider。这是一个 IModelBinderProvider 接口的Autofac实现。

 

builder.RegisterModelBinders(Assembly.GetExecutingAssembly());
builder.RegisterModelBinderProvider();

 

由于 RegisterModelBinders() 方法采用集合(Assembly)扫描来添加模型绑定器,所以你需要指定模型绑定器为哪种类型注册。

可以通过 Autofac.Integration.Mvc.ModelBinderTypeAttribute 属性标签来指定,如下:

 

[ModelBinderType(typeof(string))]
public class StringBinder : IModelBinder
{
  public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
  {
    // Implementation here
  }
}

 

如果要注册为多个类型的模型绑定器,可以直接为类添加多个 ModelBinderTypeAttribute 标签。

 

注册网络抽象 Register Web Abstractions

 

MVC集成库包括了一个Autofac模块,这个模块能够为网络抽象类添加HTTP请求生命周期域的注册 . 这允许你将网络抽象作为你的类的依赖,同时能够在运行时获取注入的正确值(Value).

 

这包括了下面的抽象类:

 

  • HttpContextBase
  • HttpRequestBase
  • HttpResponseBase
  • HttpServerUtilityBase
  • HttpSessionStateBase
  • HttpApplicationStateBase
  • HttpBrowserCapabilitiesBase
  • HttpFileCollectionBase
  • RequestContext
  • HttpCachePolicyBase
  • VirtualPathProvider
  • UrlHelper

为了使用这些抽象类,通过使用标准的 RegisterModule() 方法来将 AutofacWebTypesModule 添加到容器中。

 

builder.RegisterModule<AutofacWebTypesModule>();

 

 

确保视图页面的属性注入 Enable Property Injection for View Pages

 

 在构建应用容器之前,你可以通过将ViewRegistrationSource添加到你的ContainerBuilder使得可以在视图页中使用属性注入。

 

builder.RegisterSource(new ViewRegistrationSource());

 

 你的视图页必须继承一个MVC用于创建视图的基类。在使用Razor视图引擎时,这时的基类是指WebViewPage类。

 

public abstract class CustomViewPage : WebViewPage
{
  public IDependency Dependency { get; set; }
}

 

使用Web form引擎时,支持的基类为ViewPageViewMasterPage以及ViewUserControl

 

public abstract class CustomViewPage : ViewPage
{
  public IDependency Dependency { get; set; }
}

 

确保你实际的视图页继承于你的自定义基类。对于Razor视图引擎,你可以直接在.cshtml文件中使用@Inherits来实现.

 

@inherits Example.Views.Shared.CustomViewPage

 

使用Web Form引擎时,你可以在.aspx文件中的@Page里直接设置Inherits属性来实现。

 

<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="Example.Views.Shared.CustomViewPage"%>

 

由于ASP.NET MVC内部的问题,在Razor Layout页面中是不能使用依赖注入的。Razor视图中则可以使用,但是布局页面是不可以的。

 

确保对行文筛选器的属性注入 Enable Property Injection for Action Filters

 

为了能够为你的筛选器属性使用属性注入,需要在构建容器并将容器提供为AutofacDependencyResolver之前调用ContainerBuilderRegisterFilterProvider()方法。

 

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

 

在应用筛选器到你的action中,它会像正常一样工作。

 

[CustomActionFilter]
[CustomAuthorizeAttribute]
public ActionResult Index()
{
}

 

 

OWIN集成 OWIN Integration

 

如果你正在使用MVC作为OWIN程序的一部分,那么你需要做下面的事情:

  • 做完所有标准的MVC集成步骤-注册控制器,设置依赖解析器等等
  • 使用 Autofac 基本OWIN集成步骤设置你的应用。
  • 添加对 Autofac.Mvc5.Owin 包的引用.
  • 在你的应用启动类中, 在注册完基本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();
  }
}

 

 

使用“插件”集合 Using “Plugin” Assemblies

 

 

如果你在一个插件集合中有没有被主程序引用的控制器,那么你需要使用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">
      <!--
          If you put your plugin in a folder that isn't bin, add it to the probing path
      -->
      <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>

 

如果你选择编程来注册,你需要在应用预启动中处理.

创建一个初始化类来实现集合扫描/载入同时使用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依赖解析器 Using the Current Autofac DependencyResolver

 一旦你将 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 所以我们抛出了一个如下异常:

The dependency resolver is of type ‘Some.Other.DependencyResolver’ but was expected to be of type ‘Autofac.Integration.Mvc.AutofacDependencyResolver’. It also does not appear to be wrapped using DynamicProxy from the Castle Project. This issue could be the result of a change in the DynamicProxy implementation or the use of a different proxy library to wrap the dependency resolver.

最典型的地方是当我们通过 ContainerBuilder.RegisterFilterProvider() 使用行为筛选提供器(action filter provider). 筛选器提供者需要访问依赖解析器 同时能够使用 AutofacDependencyResolver.Current 来实现.

如果你看到这些,这意味着你正在用某种不能被折叠的方式修改(或者直译为装饰?)解析器,依赖于 AutofacDependencyResolver.Current 的功能将会失败. 当前的解决方案不会修改/装饰?解析器.

 

单元测试 Unit Testing

 

当对一个使用了 InstancePerRequest 组件的ASP.NET MVC 应用进行单元测试时, 你在尝试解析它的实例时将会抛出一个异常.这是因为在单元测试时没有HTTP请求。

每次请求生命周期域(per-request lifetime scope )这篇主题文章概述了对 per-request-scope 组件测试的策略.

 

 

示例实现 Example Implementation

 

Autofac源码中包含了一个示例web应用程序,叫做Remember.Web。它演示了各种使用Autofac对MVC的注入方式。

项目地址:https://github.com/autofac/Autofac