本文翻译自《Controller activation and dependency injection in ASP.NET Core MVC》,由于水平有限,故无法保证翻译完全准确,欢迎指出错误。谢谢!
在我最后一篇关于 ASP.NET Core 释放IDsiposable
对象的文章(中文、英文原文)中,Mark Rendle 指出,MVC 控制器在请求结束时也会释放资源。乍一看,此范围内的资源在请求结束时会释放似乎是显而易见的,但是 MVC 控制器的处理方式实际上与大多数服务略有不同。
在这篇文章中,我将介绍在ASP.NET Core MVC中IControllerActivator
是如何创建控制器的,以及通过依赖注入创建控制器存在的差异。
默认的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 使用的是DefaultControllerActivator
,它通过TypeActivatorCache来创建控制器。TypeActivatorCache
通过调用类的构造函数,并试图从 DI 容器中解析构造函数所需参数的实例。
有一点很重要,DefaultControllerActivator
不会试图从 DI 容器中解析控制器的实例,只会解析控制器的依赖项。
为了演示这个行为,我创建了一个简单的 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
文件中。在这里我将TestService
注册在 DI 容器中作为范围内服务,并设置 MVC 中间件和服务:
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
的实例。将HomeController
类型注册到 DI 容器中,并且在TestService
实例中传递自定义Name
属性。
如果您运行应用程序,您会看到什么结果?
您可以看到,该TestService.Name
属性使用的是默认值,表示TestService
实例是直接从 DI 容器中获取的,直接忽略了创建HomeController
的工厂方法。
这很容易理解,当您通过DefaultControllerActivator
创建控制器时,它不会从DI容器中创建HomeController
实例,只会解析构造函数的依赖项。
大多数情况下,使用DefaultControllerActivator
是一个不错的选择,但有时您可能希望直接通过 DI 容器来创建控制器,比如您希望使用具有拦截器或装饰器等功能的第三方容器。
幸运的是,MVC 框架包含了一个这样的IControllerActivator
实现,并提供了一种非常方便的扩展方法来启用它。
如您所见,DefaultControllerActivator
使用TypeActivatorCache
来创建控制器,MVC还包括另一个实现,称为 ServiceBasedControllerActivator
,它是直接从 DI 容器中获取控制器。它的实现非常简单:
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 服务添加到应用程序时,可以使用AddControllersAsServices()
扩展方法配置基于 DI 的激活器:
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
属性:
AddControllersAsServices
方法实现了两件事情 - 它将您应用程序中的所有控制器注册到 DI 容器(如果尚未注册),并将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
;不过我找不到任何理由,这两点实现还不能满足您的需求!
- 默认情况下,在ASP.NET Core MVC 中
IControllerActivator
配置为DefaultControllerActivator
。 DefaultControllerActivator
使用TypeActivatorCache
来创建控制器。它从 DI 容器加载构造函数所需参数来创建控制器的实例。- 您也可以使用
ServiceBasedControllerActivator
作替代方法,它直接从 DI 容器加载控制器。您可以在Startup.ConfigureServices
方法中使用MvcBuilder
的AddControllersAsServices()
扩展方法来配置此激活方式。
转载请注明出处,原文链接:http://www.cnblogs.com/tdfblog/p/controller-activation-and-dependency-injection-in-asp-net-core-mvc.html。