在這篇文章中,我將介紹在ASP.NET Core MVC中 IControllerActivator 是如何創建控制器的,以及通過依賴註入創建控制器存在的差異。 ...
本文翻譯自《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 容器中解析控制器的實例,只會解析控制器的依賴項。
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
文件中。在這裡我將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
實現,並提供了一種非常方便的擴展方法來啟用它。
ServiceBasedControllerActivator
如您所見,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。