ASP.NET Core技術研究-探祕依賴注入框架
ASP.NET Core在底層內建了一個依賴注入框架,通過依賴注入的方式註冊服務、提供服務。依賴注入不僅服務於ASP.NET Core自身,同時也是應用程式的服務提供者。
毫不誇張的說,ASP.NET Core通過依賴注入實現了各種服務物件的註冊和建立,同時也實現了面向抽象的程式設計模式和程式設計體驗,提升了應用程式的擴充套件性。
今天,我們普及一下ASP.NET Core中依賴注入的一些基本知識。
一、服務的註冊
我們通過建立一個ASP.NET Core的專案,可以發現在Startup.cs 類中,有一個方法ConfigureServices,這個方法的註釋是這樣的:
This method gets called by the runtime. Use this method to add services to the container.
在ConfigureServices方法中我們可以將通過ASP.NET Core內建的依賴注入框架實現服務的的註冊。
這個方法有個引數:IServiceCollection,見名知意,服務集合。
ASP.NET Core內建的依賴注入框架將服務註冊資訊儲存到一個實現了IServiceCollection介面的物件中。預設情況下這個介面的實現類是ServiceCollection,以下是這個類的說明:
https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.dependencyinjection.servicecollection?view=dotnet-plat-ext-3.1
通過這個介面和類實現,我們可以發現,註冊服務其實就是將一個服務的ServiceDescriptor物件新增到ServiceCollection集合中。
例如:
public void ConfigureServices(IServiceCollection services) { services.Add(new ServiceDescriptor(typeof(IUserRepository), new UserRepository())); services.AddControllers(); }
ServiceDescriptor可以理解為對某個服務註冊項的描述。ASP.NET Core的依賴注入容器IServiceProvider通過ServiceDescriptor的資訊,動態建立服務的例項Instance.
我們看一下這個ServiceDescriptor類:
有幾個關鍵的屬性:
1. ServiceType:服務的型別,例如服務介面的型別資訊
2. ImplementationType:服務的實現型別,例如服務介面實現類的型別資訊
3. ImplementationInstance:實現服務的例項,一般是服務單例模式場景下使用。 https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.dependencyinjection.servicedescriptor.-ctor?view=dotnet-plat-ext-3.1#Microsoft_Extensions_DependencyInjection_ServiceDescriptor__ctor_System_Type_System_Object_
4. Lifetime:服務生命週期:Scoped(同一個請求中同一個IServiceProvider提供的物件是同一個)、Singleton(單例)、Transient(每次從服務容器進行請求時建立)
5. ImplementationFactory 服務例項建立工廠,自定義的IServiceProvider服務提供容器
服務註冊提供了一系列過載的方法,大家可以根據需要進行選擇:
服務註冊的過程中,涉及到了服務的生命週期的概念,接下來我們詳細看一下。
二、服務生命週期
服務的生命週期設定,決定了服務提供容器IServiceProvider使用什麼樣的方式提供服務例項物件。正如上面第一章節所說的,
ASP.NET Core服務依賴注入框架,支援三種類型的服務生命週期:
- Singleton
- Scoped
- Transient
其中:
Transient:暫時的,每次從服務容器進行請求時建立。 這種生存期適合輕量級、 無狀態的服務。
Singleton:單一例項,在第一次請求時(或者在執行 Startup.ConfigureServices 並且使用服務註冊指定例項時)建立的。每個後續請求都使用相同的例項。
Scoped:範圍內的,作用域生存期服務,以每個客戶端請求(連線)一次的方式建立。可以這麼理解:同一個請求中同一個IServiceProvider提供的物件是同一個。
微軟給了個例子不錯:先註冊服務,三種類型
public void ConfigureServices(IServiceCollection services) {
services.AddRazorPages();
services.AddScoped<IMyDependency, MyDependency>(); services.AddTransient<IOperationTransient, Operation>(); services.AddScoped<IOperationScoped, Operation>(); services.AddSingleton<IOperationSingleton, Operation>(); services.AddSingleton<IOperationSingletonInstance>(new Operation(Guid.Empty)); //OperationService depends on each of the other Operation types. services.AddTransient<OperationService, OperationService>(); }
第一個請求:
控制器操作: 暫時性:d233e165-f417-469b-a866-1cf1935d2518 作用域:5d997e2d-55f5-4a64-8388-51c4e3a1ad19 單一例項:01271bc1-9e31-48e7-8f7c-7261b040ded9 例項:00000000-0000-0000-0000-000000000000 OperationService 操作: 暫時性:c6b049eb-1318-4e31-90f1-eb2dd849ff64 作用域:5d997e2d-55f5-4a64-8388-51c4e3a1ad19 單一例項:01271bc1-9e31-48e7-8f7c-7261b040ded9 例項:00000000-0000-0000-0000-000000000000
第二個請求:
第二個請求: 控制器操作: 暫時性:b63bd538-0a37-4ff1-90ba-081c5138dda0 作用域:31e820c5-4834-4d22-83fc-a60118acb9f4 單一例項:01271bc1-9e31-48e7-8f7c-7261b040ded9 例項:00000000-0000-0000-0000-000000000000 OperationService 操作: 暫時性:c4cbacb8-36a2-436d-81c8-8c1b78808aaf 作用域:31e820c5-4834-4d22-83fc-a60118acb9f4 單一例項:01271bc1-9e31-48e7-8f7c-7261b040ded9 例項:00000000-0000-0000-0000-000000000000
大家可以根據實際的需要選擇服務的生命週期,建立不同型別的服務。
三、服務的消費
前面,我們將服務註冊到IServiceCollection,ASP.NET Core服務提供容器IServiceProvider就可以根據IServiceCollection 建立具體型別的服務物件了。
我們先看一下IServiceProvider介面,可以發現:只有一個GetService方法。
我們可以通過以下程式碼使用:
public static void Main(string[] args) { var builder = CreateHostBuilder(args); var host = builder.Build(); var userRepo = host.Services.GetService(typeof(IUserRepository)) as IUserRepository; userRepo.AddUser("user"); host.Run(); }
同時,我們更多常用的是:
將服務通過ASP.NET Core依賴注入框架注入到控制器中
ASP.NET Core MVC 控制器通過建構函式顯式請求依賴關係。即:通過建構函式注入服務的實現。
前面,我們通過ConfigureServices註冊了服務IUserRepository,在Controller這一層如何消費使用這個服務呢?答案就是在Controller建構函式中注入。
看一段示例程式碼:(HomeController的建構函式中,增加了一個引數IUserRepository)
public class HomeController : Controller { private readonly ILogger<HomeController> _logger; private IUserRepository _userRepository; public HomeController(ILogger<HomeController> logger, IUserRepository userRepository) { _logger = logger; _userRepository = userRepository; } public IActionResult Index() { _userRepository.AddUser(new User() { }); return View(); } public IActionResult Privacy() { return View(); } [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] public IActionResult Error() { return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); } }
同時,ASP.NET Core MVC 控制器支援通過註解FromServicesAttribute, 將服務直接注入到Action方法中,而無需使用建構函式注入:
public IActionResult Index([FromServices] IUserRepository userRepository) { userRepository.AddUser(new User() { }); return View(); }
ASP.NET Core除了支援將服務注入到控制器,同時還支援將服務依賴注入到檢視,可以參考以下連結:
https://docs.microsoft.com/zh-cn/aspnet/core/mvc/views/dependency-injection?view=aspnetcore-3.0
以上是對ASP.NET Core依賴注入框架的研究,分享給大家。
周國慶
2020/4/12