本文翻译自《Controller activation and dependency injection in ASP.NET Core MVC》,由于水平有限,故无法保证翻译完全准确,欢迎指出错误。谢谢!
在我最后一篇关于 ASP.NET Core 释放
对象的文章(中文、英文原文)中,Mark Rendle 指出,MVC 控制器在请求结束时也会释放资源。乍一看,此范围内的资源在请求结束时会释放似乎是显而易见的,但是 MVC 控制器的处理方式实际上与大多数服务略有不同。
- IDsiposable
在这篇文章中,我将介绍在 ASP.NET Core MVC 中
是如何创建控制器的,以及通过依赖注入创建控制器存在的差异。
- IControllerActivator
在 ASP.NET Core 中,当 MVC 中间件接收到请求时,通过路由选择要执行的控制器和操作方法。为了实际的执行操作, MVC 中间件必须创建所选控制器的实例。
创建控制器的过程依赖众多不同的提供者和工厂类,但最终是由实现
接口的实例来决定的。实现类只需要实现两个方法:
- IControllerActivator
- public interface IControllerActivator {
- object Create(ControllerContext context);
- void Release(ControllerContext context, object controller);
- }
如您所见,该
方法传递了用于创建控制器的
- IControllerActivator.Create
实例。控制器的创建方式取决于具体的实现。
- ControllerContext
众所周知,ASP.NET Core 使用的是
,它通过 TypeActivatorCache 来创建控制器。
- DefaultControllerActivator
通过调用类的构造函数,并试图从 DI 容器中解析构造函数所需参数的实例。
- TypeActivatorCache
有一点很重要,
不会试图从 DI 容器中解析控制器的实例,只会解析控制器的依赖项。
- DefaultControllerActivator
为了演示这个行为,我创建了一个简单的 MVC 应用程序,包括一个单一的服务和一个控制器。服务实例有一个 name 属性,它通过构造函数来设置。默认情况下,它使用
作为默认值。
- "default"
- public class TestService {
- public TestService(string name = "default") {
- Name = name;
- }
- public string Name {
- get;
- }
- }
在应用程序中
依赖于
- HomeController
,并返回
- TestService
属性的值:
- Name
- public class HomeController: Controller {
- private readonly TestService _testService;
- public HomeController(TestService testService) {
- _testService = testService;
- }
- public string Index() {
- return "TestService.Name: " + _testService.Name;
- }
- }
还有一块代码在
文件中。在这里我将
- Startup
注册在 DI 容器中作为范围内服务,并设置 MVC 中间件和服务:
- TestService
- public class Startup {
- public void ConfigureServices(IServiceCollection services) {
- services.AddMvc();
- services.AddScoped < TestService > ();
- services.AddTransient(ctx = >new HomeController(new TestService("Non-default value")));
- }
- public void Configure(IApplicationBuilder app) {
- app.UseMvcWithDefaultRoute();
- }
- }
您会注意到,我定义了一个工厂方法用于创建
的实例。将
- HomeController
类型注册到 DI 容器中,并且在
- HomeController
实例中传递自定义
- TestService
属性。
- Name
如果您运行应用程序,您会看到什么结果?
您可以看到,该
属性使用的是默认值,表示
- TestService.Name
实例是直接从 DI 容器中获取的,直接忽略了创建
- TestService
的工厂方法。
- HomeController
这很容易理解,当您通过
创建控制器时,它不会从 DI 容器中创建
- DefaultControllerActivator
实例,只会解析构造函数的依赖项。
- HomeController
大多数情况下,使用
是一个不错的选择,但有时您可能希望直接通过 DI 容器来创建控制器,比如您希望使用具有拦截器或装饰器等功能的第三方容器。
- DefaultControllerActivator
幸运的是,MVC 框架包含了一个这样的
实现,并提供了一种非常方便的扩展方法来启用它。
- IControllerActivator
如您所见,
使用
- DefaultControllerActivator
来创建控制器,MVC 还包括另一个实现,称为
- TypeActivatorCache
,它是直接从 DI 容器中获取控制器。它的实现非常简单:
- ServiceBasedControllerActivator
- public class ServiceBasedControllerActivator: IControllerActivator {
- public object Create(ControllerContext actionContext) {
- var controllerType = actionContext.ActionDescriptor.ControllerTypeInfo.AsType();
- return actionContext.HttpContext.RequestServices.GetRequiredService(controllerType);
- }
- public virtual void Release(ControllerContext context, object controller) {}
- }
当您将 MVC 服务添加到应用程序时,可以使用
扩展方法配置基于 DI 的激活器:
- AddControllersAsServices()
- public class Startup {
- public void ConfigureServices(IServiceCollection services) {
- services.AddMvc().AddControllersAsServices();
- services.AddScoped < TestService > ();
- services.AddTransient(ctx = >new HomeController(new TestService("Non-default value")));
- }
- public void Configure(IApplicationBuilder app) {
- app.UseMvcWithDefaultRoute();
- }
- }
通过上面的代码,点击主页将通过 DI 容器来创建一个控制器。由于我们已经注册了一个创建
的工厂方法,我们自定义
- HomeController
配置将被保留,使用替换后的
- TestService
属性:
- Name
方法实现了两件事情 - 它将您应用程序中的所有控制器注册到 DI 容器(如果尚未注册),并将
- AddControllersAsServices
注册为
- IControllerActivator
:
- ServiceBasedControllerActivator
- public static IMvcBuilder AddControllersAsServices(this IMvcBuilder builder) {
- var feature = new ControllerFeature();
- builder.PartManager.PopulateFeature(feature);
- foreach(var controller in feature.Controllers.Select(c = >c.AsType())) {
- builder.Services.TryAddTransient(controller, controller);
- }
- builder.Services.Replace(ServiceDescriptor.Transient < IControllerActivator, ServiceBasedControllerActivator > ());
- return builder;
- }
如果需要做一些更复杂的事情,您可以随时实现自己
;不过我找不到任何理由,这两点实现还不能满足您的需求!
- IControllerActivator
配置为
- IControllerActivator
。
- DefaultControllerActivator
使用
- DefaultControllerActivator
来创建控制器。它从 DI 容器加载构造函数所需参数来创建控制器的实例。
- TypeActivatorCache
作替代方法,它直接从 DI 容器加载控制器。您可以在
- ServiceBasedControllerActivator
方法中使用
- Startup.ConfigureServices
的
- MvcBuilder
扩展方法来配置此激活方式。
- AddControllersAsServices()
来源: http://www.cnblogs.com/tdfblog/p/controller-activation-and-dependency-injection-in-asp-net-core-mvc.html