项目
版本

Autofac Web API 集成

Web API 2 集成需要使用 Autofac.WebApi2 NuGet 包。而 Web API 1.x 集成则需要使用 Autofac.WebApi NuGet 包。Web API 集成提供了控制器、模型绑定器和动作过滤器的依赖注入支持,并且添加了 按请求生命周期支持

此页面解释了 ASP.NET 经典 Web API 集成。 如果你正在使用 ASP.NET Core,请参阅 ASP.NET Core 集成页面

快速入门

要使 Autofac 与 Web API 集成,你需要引用 Web API 集成的 NuGet 包,注册控制器,并设置依赖解析器。你可以选择性地启用其他功能。

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

    // 获取你的HttpConfiguration。
    var config = GlobalConfiguration.Configuration;

    // 注册你的Web API控制器。
    builder.RegisterApiControllers(Assembly.GetExecutingAssembly());

    // 可选:注册Autofac过滤器提供程序。
    builder.RegisterWebApiFilterProvider(config);

    // 可选:注册Autofac模型绑定器提供程序。
    builder.RegisterWebApiModelBinderProvider();

    // 将依赖解析器设置为Autofac。
    var container = builder.Build();
    config.DependencyResolver = new AutofacWebApiDependencyResolver(container);
}

下面的各节将详细介绍这些功能以及如何使用它们。

获取 HttpConfiguration

在 Web API 中,设置应用程序需要更新 HttpConfiguration 对象的属性并设置值。根据你的应用程序部署方式,获取这个配置的方式可能不同。在文档中,我们将提到“你的 HttpConfiguration ”,你需要决定如何获取它。

对于标准 IIS 托管,HttpConfigurationGlobalConfiguration.Configuration

var builder = new ContainerBuilder();
var config = GlobalConfiguration.Configuration;
builder.RegisterApiControllers(Assembly.GetExecutingAssembly());
var container = builder.Build();
config.DependencyResolver = new AutofacWebApiDependencyResolver(container);

对于自托管,HttpConfiguration 是你的 HttpSelfHostConfiguration 实例。

var builder = new ContainerBuilder();
var config = new HttpSelfHostConfiguration("http://localhost:8080");
builder.RegisterApiControllers(Assembly.GetExecutingAssembly());
var container = builder.Build();
config.DependencyResolver = new AutofacWebApiDependencyResolver(container);

对于 OWIN 集成,HttpConfiguration 是你在应用启动类中创建并传递给 Web API 中间件的那个。

var builder = new ContainerBuilder();
var config = new HttpConfiguration();
builder.RegisterApiControllers(Assembly.GetExecutingAssembly());
var container = builder.Build();
config.DependencyResolver = new AutofacWebApiDependencyResolver(container);

注册控制器

在应用程序启动时,在构建 Autofac 容器时,你应该注册 Web API 控制器及其依赖项。这通常发生在 OWIN 启动类或 Global.asax 文件的 Application_Start 方法中。

默认情况下,实现 IHttpController 且名字以 Controller 结尾的类型会被注册。

var builder = new ContainerBuilder();

// 你可以一次通过assembly扫描注册所有控制器...
builder.RegisterApiControllers(Assembly.GetExecutingAssembly());

// ...或者手动注册单个控制器。
builder.RegisterType<ValuesController>().InstancePerRequest();

如果你的控制器不遵循标准命名约定,可以选择使用 RegisterApiControllers 方法的重载来提供自定义后缀。

// 也可以使用assembly扫描来注册带有自定义后缀的控制器。
builder.RegisterApiControllers("MyCustomSuffix", Assembly.GetExecutingAssembly());

设置依赖解析器

构建容器后,将容器传递给 AutofacWebApiDependencyResolver 的新实例。将新的解析器附加到 HttpConfiguration.DependencyResolver ,以便 Web API 知道应该使用 AutofacWebApiDependencyResolver 来查找服务。这是 Autofac 实现的 IDependencyResolver 接口。

var container = builder.Build();
config.DependencyResolver = new AutofacWebApiDependencyResolver(container);

通过依赖注入提供过滤器

由于属性是通过反射 API 创建的,你不能自己调用构造函数。除了属性注入外,你没有其他选择。Autofac 的 Web API 集成提供了一种机制,让你可以创建实现过滤器接口(如 IAutofacActionFilterIAutofacContinuationActionFilterIAutofacAuthorizationFilterIAutofacExceptionFilter )的类,并使用容器构建器的注册语法将它们与所需的控制器或动作方法关联起来。

注册过滤器提供程序

你需要注册 Autofac 过滤器提供程序的实现,因为它负责根据注册来连接过滤器。这是通过在容器构建器上调用 RegisterWebApiFilterProvider 方法并提供一个 HttpConfiguration 实例来完成的。

var builder = new ContainerBuilder();
builder.RegisterWebApiFilterProvider(config);

实现过滤器接口

使用现有的 Web API 过滤器属性创建类时,你的类应实现集成中定义的适当过滤器接口。

标准动作过滤器接口

IAutofacActionFilter 接口让你可以在执行动作前后定义过滤器,就像你从 ActionFilterAttribute 派生一样。

下面的过滤器是一个动作过滤器,它实现了 IAutofacActionFilter 而不是 System.Web.Http.Filters.IActionFilter

public class LoggingActionFilter : IAutofacActionFilter
{
    readonly ILogger _logger;

    public LoggingActionFilter(ILogger logger)
    {
        _logger = logger;
    }

    public Task OnActionExecutingAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
    {
        _logger.Write(actionContext.ActionDescriptor.ActionName);
        return Task.FromResult(0);
    }

    public Task OnActionExecutedAsync(HttpActionExecutedContext actionExecutedContext, CancellationToken cancellationToken)
    {
        _logger.Write(actionContext.ActionDescriptor.ActionName);
        return Task.FromResult(0);
    }
}

请注意,示例中没有实际的异步代码运行,所以返回 Task.FromResult(0) ,这是一种常见的返回“空任务”的方式。如果过滤器确实需要异步代码,你可以返回一个真正的 Task 对象,或者像其他异步方法那样使用 async / await

继续动作过滤器接口

除了上面的常规 IAutofacActionFilter,还有一个 IAutofacContinuationActionFilter 。该接口也作为动作过滤器工作,但不使用 OnActionExecutingAsyncOnActionExecutedAsync 方法,而是采用延续风格,只有一个接受回调的方法 ExecuteActionFilterAsync ,用于运行链中的下一个过滤器。

你可能会选择使用 IAutofacContinuationActionFilter 而不是 IAutofacActionFilter ,例如如果你想将整个请求包装在一个 using 块中,比如你想为请求分配一个 TransactionScope ,如下所示:

public class TransactionScopeFilter : IAutofacContinuationActionFilter
{
    public async Task<HttpResponseMessage> ExecuteActionFilterAsync(
        HttpActionContext actionContext,
        CancellationToken cancellationToken,
        Func<Task<HttpResponseMessage>> next)
    {
        using (new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
        {
            return await next();
        }
    }
}

注意 常规的 IAutofacActionFilter 在延续过滤器内部运行,因此在 OnActionExecutingAsync 、动作方法本身和过滤器的 OnActionExecutedAsync 之间,异步上下文也会保留。

注册过滤器

为了执行过滤器,你需要将其注册到容器中,并告知它应针对哪个控制器(或控制器),以及可选的动作,进行操作。这通过使用容器构建器扩展方法完成,每个过滤器类型都有相应的扩展方法:

  • ActionFilter
  • ActionFilterOverride
  • AuthenticationFilter
  • AuthenticationFilterOverride
  • AuthorizationFilter
  • AuthorizationFilterOverrideW
  • ExceptionFilter
  • ExceptionFilterOverride

对于每个过滤器类型,都有几种注册方法:

  • AsWebApi{FilterType}ForAllControllers
    将此过滤器注册到所有控制器的所有动作方法上,就像注册全局 Web API 过滤器一样。

  • AsWebApi{FilterType}For<TController>()
    将过滤器注册到指定控制器上,就像在控制器级别放置基于属性的过滤器一样。

    指定基控制器类会导致该过滤器应用于所有继承自它的控制器。

    此方法接受一个可选的 lambda 表达式,指示控制器上的特定方法,就好像你在应用一个动作过滤器到特定动作一样。

    在下面的例子中,动作过滤器被应用于 ValuesControllerGet 动作方法。

    var builder = new ContainerBuilder();
    
    builder.RegisterType<LoggingActionFilter>()
        .AsWebApiActionFilterFor<ValuesController>(c => c.Get(default(int)))
        .InstancePerRequest();
    

    当应用于需要参数的动作方法时,使用 default 关键字和参数的数据类型作为 lambda 表达式的强类型占位符。例如,上面示例中的 Get 动作方法需要一个 int 类型的参数,并在 lambda 表达式中使用 default(int) 作为占位符。

  • AsWebApi{FilterType}Where() *Where 方法允许您指定一个谓词,以便对哪些动作和/或控制器进行更高级的自定义决策。

    以下示例中,将异常过滤器应用到所有 POST 方法上:

    var builder = new ContainerBuilder();
    
    builder.Register(c => new LoggingExceptionFilter(c.Resolve<ILogger>()))
        .AsWebApiExceptionFilterWhere(action => action.SupportedHttpMethods.Contains(HttpMethod.Post))
        .InstancePerRequest();
    

    还有一个接受 ILifetimeScope 的谓词版本,您可以在此内部使用服务:

    var builder = new ContainerBuilder();
    
    builder.Register(c => new LoggingExceptionFilter(c.Resolve<ILogger>()))
        .AsWebApiExceptionFilterWhere((scope, action) => scope.Resolve<IFilterConfig>().ShouldFilter(action))
        .InstancePerRequest();
    

    注意 过滤器谓词对于每个动作/过滤器组合只会调用一次;它们不会在每次请求上都调用。

您可以应用任意数量的过滤器。注册一种类型的过滤器不会移除或替换之前已注册的过滤器。

您可以将过滤器注册链接在一起,以针对多个控制器应用过滤器,如下所示:

builder.Register(c => new LoggingActionFilter(c.Resolve<ILogger>()))
    .AsWebApiActionFilterFor<LoginController>()
    .AsWebApiActionFilterFor<ValuesController>(c => c.Get(default(int)))
    .AsWebApiActionFilterFor<ValuesController>(c => c.Post(default(string)))
    .InstancePerRequest();

过滤器覆盖

注册过滤器时,有基本的注册方法(如 AsWebApiActionFilterFor<TController>() ),以及覆盖注册方法(如 AsWebApiActionFilterOverrideFor<TController>() )。

覆盖方法的目的是提供一种确保某些过滤器优先执行的方式。您可以有任意数量的覆盖——这些不是替换过滤器,只是优先执行的过滤器。

过滤器将以以下顺序执行:

  • 控制器范围的覆盖
  • 动作范围的覆盖
  • 控制器范围的过滤器
  • 动作范围的过滤器

在 Autofac 动作过滤器中设置响应

与标准 Web API 过滤器类似,您可以在动作过滤器的 OnActionExecutingAsync 方法中设置 HttpResponseMessage

class RequestRejectionFilter : IAutofacActionFilter
{
  public async Task OnActionExecutingAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
  {
    // 某些原因导致请求不合法。
    actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, "Request not valid");
    await Task.FromResult(0);
  }

  public void Task OnActionExecutedAsync(HttpActionExecutedContext actionExecutedContext, CancellationToken cancellationToken)
  {
  }
}

要匹配标准 Web API 行为,如果您设置了 Response 属性,则后续的动作过滤器将不会被调用。但是,已经调用过的任何动作过滤器都将调用 OnActionExecutedAsync 方法,并填充适当的响应。

标准 Web API 过滤器特性是单例

您可能会注意到,如果您使用标准 Web API 过滤器,则无法使用 InstancePerRequest 依赖项。

与 MVC 中的过滤器提供程序不同,Web API 中的过滤器提供程序不允许您指定过滤器实例不应缓存。这意味着所有 Web API 的过滤器特性本质上都是整个应用程序生命周期内的单例实例。

如果要在过滤器中获取按请求的服务,您会发现只有使用 Autofac 过滤器接口时才能实现。使用标准 Web API 过滤器时,依赖项将在过滤器首次解决时注入一次,之后将不再注入。

现有的 Web API 过滤器特性的单例性质是我们需要自定义过滤器接口的原因。

**如果您无法使用 Autofac 接口,并且在过滤器中需要按请求或依赖项实例的服务,请使用服务定位。**幸运的是,Web API 使获取当前请求范围变得非常容易——它随 HttpRequestMessage 一起提供。

以下是使用 Web API 的 IDependencyScope 服务定位的过滤器示例,以获取按请求的服务:

public class ServiceCallActionFilterAttribute : ActionFilterAttribute
{
  public override void OnActionExecuting(HttpActionContext actionContext)
  {
    // 获取请求生命周期上下文,以便可以解析服务。
    var requestScope = actionContext.Request.GetDependencyScope();

    // 解析要使用的服务。
    var service = requestScope.GetService(typeof(IMyService)) as IMyService;

    // 在过滤器中完成其他工作。
    service.DoWork();
  }
}

实例过滤器不会注入

在设置过滤器时,您可能想手动向集合中添加过滤器,如下所示:

config.Filters.Add(new MyActionFilter());

**Autofac 不会为通过这种方式注册的过滤器的属性注入。**这有点像您使用 RegisterInstance 将预构造的对象放入 Autofac 时的情况——Autofac 不会注入或修改预构造的对象。对于预先构造并添加到过滤器集合中的过滤器实例也是如此。与属性过滤器(如上所述)一样,您可以使用服务定位而不是属性注入来解决这个问题。

通过依赖注入提供模型绑定器

Autofac 与 Web API 的集成提供了通过依赖注入解决您的模型绑定器的能力,并使用一个流式接口将绑定器与类型关联起来。

注册绑定器提供程序

为了在需要时能解决任何注册的 IModelBinder 实现,您需要注册 Autofac 模型绑定器提供程序。这是通过在容器构建器上调用 RegisterWebApiModelBinderProvider 方法完成的。

var builder = new ContainerBuilder();
builder.RegisterWebApiModelBinderProvider();

注册模型绑定器

一旦实现了 System.Web.Http.ModelBinding.IModelBinder 来处理绑定问题,将其与 Autofac 注册,并告诉 Autofac 应使用哪个绑定器处理哪些类型。

builder
  .RegisterType<AutomobileBinder>()
  .AsModelBinderForTypes(typeof(CarModel), typeof(TruckModel));

为参数标记 ModelBinderAttribute

即使您已注册模型绑定器,但仍需将参数标记为 [ModelBinder] 属性,以便 Web API 知道使用模型绑定器而不是媒体类型格式化器来绑定模型。您不再需要指定模型绑定器类型,但需要标记参数。有关此内容,参阅 Web API 文档

public HttpResponseMessage Post([ModelBinder] CarModel car) { ... }

按控制器类型的特定服务

Web API 具有一个有趣的功能,允许您通过添加实现 IControllerConfiguration 接口的属性来配置按控制器类型的 Web API 服务(例如 IActionValueBinder )。

通过传递给 IControllerConfiguration.Initialize 方法的 HttpControllerSettings 参数上的 Services 属性,您可以覆盖全局设置的服务。这种基于属性的方法似乎鼓励您直接实例化服务对象,然后覆盖全局注册的服务。Autofac 允许您通过容器配置这些按控制器类型的特定服务,而不是将它们隐藏在没有依赖注入支持的属性中。

添加控制器配置属性

由于 Web API 定义了扩展点,因此无法避免在控制器上添加用于应用配置的属性。Autofac 集成包含一个 AutofacControllerConfigurationAttribute ,您可以在 Web API 控制器上应用该属性,以表示它们需要按控制器类型的配置。

关键是要记住的是,实际的配置信息将由您构建容器时确定,而无需在实际属性中实现任何配置。在这种情况下,该属性可以被视为纯粹的标记,指示容器将定义配置信息并提供服务实例。

[AutofacControllerConfiguration]
public class ValuesController : ApiController
{
    // 实现...
}

支持的服务

支持的服务可以分为单个样式或多样式服务。例如,您只能有一个 IHttpActionInvoker ,但可以有多个 ModelBinderProvider 服务。

您可以为以下单个样式服务使用依赖注入:

  • IHttpActionInvoker
  • HttpActionSelector
  • ActionValueBinder
  • IBodyModelValidator
  • IContentNegotiator
  • IHttpControllerActivator
  • ModelMetadataProvider

以下多样式服务受支持:

  • ModelBinderProvider
  • ModelValidatorProvider
  • ValueProviderFactory
  • MediaTypeFormatter

在多样式服务列表中,MediaTypeFormatter 实际上是与众不同的。从技术上讲,它实际上不是一个服务,而是添加到 HttpControllerSettings 实例的 MediaTypeFormatterCollection 中,而不是 ControllerServices 容器。我们考虑过不支持 MediaTypeFormatter 实例的依赖注入,但确保它们也可以按控制器类型从容器中解决。

服务注册

下面是一个将自定义的 IHttpActionSelector 实现为 InstancePerApiControllerType() 的例子,用于 ValuesController。当应用到控制器类型时,所有衍生控制器也将接收相同的配置。AutofacControllerConfigurationAttribute 被衍生控制器类型继承,并且容器中对注册的服务的行为相同。当你为单例风格的服务注册时,它总是会替换全局级别配置的默认服务。

builder.Register(c => new CustomActionSelector())
       .As<IHttpActionSelector>()
       .InstancePerApiControllerType(typeof(ValuesController));

清除现有服务

默认情况下,多个风格的服务会被附加到全局级别已配置的服务集中。当你使用容器注册多个风格的服务时,可以选择清除现有的服务集,以便只使用你注册为 InstancePerApiControllerType() 的服务。这可以通过在 InstancePerApiControllerType() 方法上设置 clearExistingServices 参数为 true 来完成。如果任何多个风格服务的注册表示希望发生这种情况,将移除该类型的现有服务。

builder.Register(c => new CustomModelBinderProvider())
       .As<ModelBinderProvider>()
       .InstancePerApiControllerType(
          typeof(ValuesController),
          clearExistingServices: true);

按控制器类型的服务限制

如果你使用按控制器类型的服务,就不能依赖注册为 InstancePerRequest() 的其他服务。问题在于 Web API 会缓存这些服务,并不是每次创建该类型的控制器时都会从容器中请求它们。在不引入依赖注入集成的关键概念(即控制器类型的键)的情况下,Web API 很可能无法轻松添加这种支持,而这意味着所有容器都需要支持键值对服务。

批处理

如果你选择使用 Web API 批处理功能,请注意,向批处理端点发送的初始多部分请求是 Web API 创建请求生命周期范围的地方。批处理中的子请求都发生在内存中,并共享同一个请求生命周期范围 - 批处理中的每个子请求不会得到单独的生命周期范围。

这是因为 Web API 中的批处理处理方式会从父请求复制属性到子请求。ASP.NET Web API 框架有意从父请求复制到子请求的一个属性就是请求生命周期范围。对此没有工作绕过方法,也不受 Autofac 控制。

OWIN 集成

如果你的 Web API 是作为 OWIN 应用程序的一部分 使用的,你需要:

  • 完成标准 Web API 集成的所有步骤 - 注册控制器、设置依赖解析器等。
  • 设置你的应用程序使用 基础 Autofac OWIN 集成
  • 添加对 Autofac.WebApi2.Owin NuGet 包的引用。
  • 在你的应用程序启动类中,在注册基础 Autofac 中间件之后,注册 Autofac Web API 中间件。
public class Startup
{
  public void Configuration(IAppBuilder app)
  {
    var builder = new ContainerBuilder();

    // STANDARD WEB API SETUP:

    // 获取你的 HttpConfiguration。在 OWIN 中,你需要创建一个,而不是使用 GlobalConfiguration。
    var config = new HttpConfiguration();

    // 注册你的 Web API 控制器。
    builder.RegisterApiControllers(Assembly.GetExecutingAssembly());

    // 运行其他可选步骤,如注册过滤器、按控制器类型的服务等,然后设置依赖解析器为 Autofac。
    var container = builder.Build();
    config.DependencyResolver = new AutofacWebApiDependencyResolver(container);

    // OWIN WEB API SETUP:

    // 先注册 Autofac 中间件,然后是 Autofac Web API 中间件,最后是标准 Web API 中间件。
    app.UseAutofacMiddleware(container);
    app.UseAutofacWebApi(config);
    app.UseWebApi(config);
  }
}

在 OWIN 集成中常见的错误是使用 GlobalConfiguration.Configuration在 OWIN 中,你需要从头开始创建配置。 使用 OWIN 集成时,不应在任何地方引用 GlobalConfiguration.Configuration

单元测试

当你正在单元测试使用 Autofac 的 ASP.NET Web API 应用程序,其中注册了 InstancePerRequest 组件时,尝试解决这些组件时会遇到异常,因为在单元测试中没有 HTTP 请求生命周期。

请求生命周期范围 主题概述了测试和调试按请求范围组件的策略。

示例

Autofac 示例仓库 中,有一个示例项目展示了 Web API 与 OWIN 自托管的结合。

在本文档中