ASP.NET Core 中的依賴注入

yinghualeihenmei發表於2024-06-06

原文連結:https://www.cnblogs.com/micenote/articles/9692207.html

什麼是依賴注入
軟體設計原則中有一個依賴倒置原則(DIP),為了更好的解耦,講究要依賴於抽象,不要依賴於具體。而控制反轉(Ioc)就是這樣的原則的其中一個實現思路, 這個思路的其中一種實現方式就是依賴注入(DI)。

什麼是依賴:當一個類需要另一個類協作來完成工作的時候就產生了依賴。

什麼是注入: 注入體現的是一個IOC(控制反轉的的思想)。正轉是自己來例項化需要的依賴。反轉是類不應該自己建立它,而是應該由它的呼叫者傳給它。於是可以透過建構函式等讓外界把依賴傳給類。

為什麼要反轉 為了在業務變化的時候盡少改動程式碼可能造成的問題。基於抽象新增新的實現。只需要在原來注入的地方改一下就可以了。

什麼是容器 容器統一管理系統中的所有依賴。容器負責兩件事情:
繫結服務與例項之間的對映關係
獲取例項並對例項進行管理(建立與銷燬)


ASP .NET Core 中使用依賴注入

IServiceCollection 負責註冊服務,是一個IList
IServiceProvider 負責提供例項,是由IServiceCollection的擴充套件方法BuildServiceProvider建立的。
ServiceDescriptor 單個服務描述

Type ServiceType: 服務的型別
Type ImplementationType: 實現的型別
ServiceLifetime Lifetime: 服務的生命週期
object ImplementationInstance: 實現服務的例項
Func<IServiceProvider, object> ImplementationFactory: 建立服務例項的工廠
註冊
ServiceCollection提供了三種註冊方法分別對應著三種例項生命週期。

AddSingleton 整個應用程式生命週期以內只建立一個例項
AddScoped 在同一個Scope內只初始化一個例項,可以理解為( 每一個request級別只建立一個例項,同一個http request會在一個 scope內)
AddTransient 每一次GetService都會建立一個新的例項
做一個簡單測試:

建立測試類:

public interface ITest
{
Guid Guid { get; }
}

public class Test : ITest
{
public Guid Guid { get; }

public Test()
{
Guid = Guid.NewGuid();
}
}
在ConfigureServices裡註冊

public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<ITest, Test>();
}
透過三種方法來獲取這個Test類例項, Controller和View中程式碼如下

public class HomeController : Controller
{
private ITest _test;
private ILogger<HomeController> _logger;

public HomeController(ITest test, ILogger<HomeController> logger)
{
this._test = test;
this._logger = logger;
}

public IActionResult Index()
{
//透過建構函式獲取
var res1 = this._test;
ViewBag.TestFromConstructor = res1;

//透過HttpContext獲取
var res2 = HttpContext.RequestServices.GetService<ITest>();
ViewBag.TestFromContext = res2;

return View();
}
}
@inject ITest TestFromView
<ul>
<li>@ViewBag.TestFromConstructor.Guid</li>
<li>@ViewBag.TestFromContext.Guid</li>
<li>@TestFromView.Guid</li>
</ul>
執行,結果如下

03d437d6-2f18-452e-a7fd-ce62cea90381
08b31487-b02b-4d62-bc2b-6d2026389f0c
21a7fc13-6e7b-4590-910b-68d21a7a03d1


說明三種方式獲取了三個不同的例項, 重新整理一下頁面, 又變成了另外三個不同的值.

現在在startup檔案中將原來的 services.AddTransient<ITest,Test>() 改為 services.AddScoped<ITest,Test>() , 其他不變, 重新執行一下, 結果如下

050fef7e-2dc3-4d7d-8733-683b54b40b0b
050fef7e-2dc3-4d7d-8733-683b54b40b0b
050fef7e-2dc3-4d7d-8733-683b54b40b0b
重新整理一下:

c9e5df8d-b085-4e3a-b883-fa083ba1d136
c9e5df8d-b085-4e3a-b883-fa083ba1d136
c9e5df8d-b085-4e3a-b883-fa083ba1d136
三組數字相同, 重新整理一下, 又變成了另外三組一樣的值, 這說明在同一次請求裡, 獲取的例項是同一個。

最常用的DBContext預設構建為Scope例項。即能減少例項初始化的消耗,還能實現跨Service事務的功能。

再將 services.AddScoped<ITest,Test>() 改為 services.AddSingleton<ITest,Test>() , 重新執行, 這次結果是

42ef5162-5781-427b-ac9d-a152500ed32f
42ef5162-5781-427b-ac9d-a152500ed32f
42ef5162-5781-427b-ac9d-a152500ed32f
發現三組值是一樣的, 說明獲得的是同一個例項, 在重新整理一下頁面, 仍然是這三組值, 說明多次請求獲得的結果也是同一個例項.

使用
在Startup類ConfigureService中初始化

public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

services.AddTransient<ITest, Test>();
}
方法中預設呼叫了services.AddMvc(), 是IServiceCollection的一個擴充套件方法 public static IMvcBuilder AddMvc(this IServiceCollection services), 作用就是向這個清單中新增了一些MVC需要的服務,例如Authorization、RazorViewEngin、DataAnnotations等。

Controller中使用

private ITest _test;
private ILogger<HomeController> _logger;

public HomeController(ITest test, ILogger<HomeController> logger)
{
this._test = test;
this._logger = logger;
}
透過HttpContext來獲取例項
HttpContext下有一個RequestedService同樣可以用來獲取例項物件,不過這種方法一般不推薦。需要新增Microsoft.Extension.DependencyInjection的using來呼叫這個方法的。

HttpContext.RequestServices.GetService<ITest>()
View中使用
在View中透過@inject宣告

@inject ITest TestFromView

<ul>
<li>@TestFromView.Guid</li>
</ul>
釋放
對於每次請求, 我們最初配置的根IServiceProvider透過CreateScope()建立了一個新的IServiceScope, 而這個IServiceScope的ServiceProvider屬性將負責本次該次請求的服務提供, 當請求結束, 這個ServiceProvider的dispose會被呼叫。

在2.0中, ServiceProvider只呼叫由它建立的 IDisposable 型別的 Dispose。 如果將一個例項新增到容器,它將不會被釋放。
例如:

services.AddSingleton<ITest>(new Test());
替換為其它的 Ioc 容器
可以將預設的容器改為其他的容器, 比如Autofac, 需要把Startup類裡面的 ConfigureService的 返回值從 void改為 IServiceProvider即可。而返回的則是一個AutofacServiceProvider。

public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddMvc();
// Add other framework services

// Add Autofac
var containerBuilder = new ContainerBuilder();
containerBuilder.RegisterModule<DefaultModule>();
containerBuilder.Populate(services);
var container = containerBuilder.Build();
return new AutofacServiceProvider(container);
}
public class DefaultModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<CharacterRepository>().As<ICharacterRepository>();
}
}

相關文章