依賴注入在 ASP.NET Core 中起中很重要的作用,也是一種高大上的程式設計思想,它的總體原則就是:俺要啥,你就給俺送啥過來。服務類型的執行個體轉由容器自動管理,無需我們在代碼中顯式處理。
是以,有了依賴注入後,你的程式設計思維就得變一變了。在過去,許多功能性的類型(比如一個加密解密的類),我們都喜歡将其定義為靜态(static),而有了依賴注入,你就要避免使用靜态類型,應該交由服務容器幫你管理,隻要你用好了,你會發現依賴注入是很友善的。
依賴注入的初級玩法,也是比較标準的玩法,此種玩法有兩種模式:
1、十代單傳模式:一個接口對應一個類,比如先定義接口 IA、IB,随後,類A實作 IA,類B 實作 IB。一對一。也可以是抽象類(或基類)E,然後 F 繼承 E 類。
2、斷子絕孫模式:直接就寫一個類,不考慮派生,直接就添加到服務容器中。
來,看個例子。
我先定義個接口。
public interface IPlayGame
{
void Play();
}
然後,寫一個類來實作它。
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsISPrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdsATOfd3bkFGazxCMx8VesATMfhHLlN3XnxCMwEzX0xiRGZkRGZ0Xy9GbvNGLpZTY1EmMZVDUSFTU4VFRR9Fd4VGdsYTMfVmepNHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-YWan5COxgTMzcDOhZWMllTYyIGOxYzX2QjMxATMwEzLcFTMxIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjL5M3Lc9CX6MHc0RHaiojIsJye.gif)
public class NBPlayGame : IPlayGame
{
public void Play()
{
Console.WriteLine("全民打麻藥。");
}
}
我們知道,所謂服務類,其實就是普通類,這些類一般用于完成某些功能,比如計算 MD5 值。接着呢,還記得 Startup 類有個 ConfigureServices 方法吧,對,就在這厮裡面把我們剛剛那個服務進行注冊(就是添加到 ServiceCollection 集合中)。
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<IPlayGame, NBPlayGame>();
}
添加的時候很簡單,類型一對一,IPlayGame 接口與 NBPlayGame 類對應。添加時有三種方法你可以調用,實際上對應着,服務類在容器中的生命周期。
AddSingleton:單個執行個體,這是壽命最長的,與天同壽。整個應用程式中僅用一個執行個體。
AddTransient:這個是最短命的,可能是天天晚上加班熬夜,死得很快。此種情況下,服務類的執行個體是用的時候建立,用完後直接銷毀。
AddScoped:這個比較難了解。它的生命周期在單個請求内,包括用戶端與伺服器之間随後産生的子請求,反正隻要請求的會話結束了,就會清理。
然後,你就可以進行注入了,比如在中間件,在控制器,或者在其他服務類的構造函數上(中間件是在 Invoke / InvokeAsync 方法上)進行執行個體接收。
現在來用一下,寫一個中間件。
public class TestMiddleware
{
public TestMiddleware(RequestDelegate next) { }
public Task InvokeAsync(HttpContext context, IPlayGame game)
{
game.Play();
return Task.CompletedTask;
}
}
已注冊的服務會注入到 InvokeAsync 方法的參數中。注意第一個參數是 HttpContext,這是必須參數,後面的是注入的參數。
最後,在 Startup 類的 Configure 方法中就可以 use 這個中間件了。
public void Configure(IApplicationBuilder app)
{
app.UseMiddleware<TestMiddleware>();
}
運作後,Play 方法調用,在控制台中輸出以下結果。
“斷子絕孫”模式,不使用接口規範,直接寫功能類。
public class DoSomething
{
public string GetMessage() => "你好,剛才 Boss 找你。";
}
注冊服務時更簡單。
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<DoSomething>();
}
在 Configure 方法中進行注入。
public void Configure(IApplicationBuilder app, DoSomething thing)
{
Console.WriteLine(thing.GetMessage());
}
運作後,輸出結果如下。
在容器中,使用 ServiceDescriptor 類來存儲服務類型相關的資訊。其中,ServiceType 表示的是服務的類型,如果服務是有接口與實作類的,那麼這個屬性指的是接口的類型,實作類的類型資訊由 ImplementationType 屬性存儲。如果沒有接口,直接隻定義類型,那麼這個類型的資訊就存到 ServiceType 屬性上,ImplementationType 屬性不使用。
上面這些例子中,ServiceType 是 IPlayGame 接口相關資訊,ImplementationType 是 NBPlayGame 類的資訊。如果像上面 DoSomething 類的情況,則 ServiceType 為 DoSomething 相關的資訊,ImplementationType 為空。
接下來,咱們看進階玩法。
定義一個接口。
public interface IDemoService
{
string Version { get; }
void Run();
}
然後,有兩個類實作這個接口。
public class DemoService1 : IDemoService
{
public string Version => "v1";
public void Run()
{
Console.WriteLine("第一個服務實作類。");
}
}
public class DemoService2 : IDemoService
{
public string Version => "v2";
public void Run()
{
Console.WriteLine("第二個服務實作類。");
}
}
然後,我們注冊服務。
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<IDemoService, DemoService1>();
services.AddTransient<IDemoService, DemoService2>();
}
然後我們照例,接收注入,咱們依舊使用中間件的方法參數接收。
public class DemoMiddleware
{
public DemoMiddleware(RequestDelegate next)
{
// 由于程式約定,此構造函數必須提供。
}
public async Task InvokeAsync(HttpContext context, IDemoService sv)
{
await context.Response.WriteAsync(sv.Version);
}
}
然後,在 Startup.Configure 方法中使用該中間件。
public void Configure(IApplicationBuilder app, DoSomething thing)
{
app.UseMiddleware<DemoMiddleware>();
}
運作之後,你發現問題了,看看輸出。
出事了,參數僅能接收到最後注冊的實作類型執行個體,也就是 DemoService2 類。是以就看到網上有不少朋友發貼問了,.NET Core 是不是不支援多個服務實作類的注入?這難倒了很多人。
實話告訴你,Core Core 兄是支援注入多個實作類的執行個體的。
下面,老周介紹兩種解決方法(其實有三種,還有一種不太好弄,尤其是你對 Core 兄不熟的時候,是以我說兩種,基本夠用)。
方法一、接收 IServiceProvider 類型的注入。
public async Task InvokeAsync(HttpContext context, IServiceProvider provider)
{
StringBuilder sb = new StringBuilder();
foreach (var sv in provider.GetServices<IDemoService>())
{
sb.Append($"{sv.Version}<br/>");
}
await context.Response.WriteAsync(sb.ToString());
}
隻要能接收到 IServiceProvider 所引用的執行個體,就能通過 GetServices 方法擷取多個服務執行個體。
方法二,這種方法老周很推薦,更簡單,直接注入 IEnumerable<T> 類型,本例中就是 IEnumerable<IDemoService>。
public async Task InvokeAsync(HttpContext context, IEnumerable<IDemoService> svs)
{
StringBuilder sb = new StringBuilder();
foreach (var sv in svs)
{
sb.Append($"{sv.Version}<br/>");
}
await context.Response.WriteAsync(sb.ToString());
}
IEnumerable<T> 的妙處就是可以 foreach ,這樣你也能通路多個執行個體,而且必要時還可以聯合 LINQ 一起耍。
運作結果如下。
不要問我是怎麼發現的,反正我告訴你了,你用就是了。
好了,今天的話題就到這兒了,3166。
public interface IPlayGame
{
void Play();
}
public class NBPlayGame : IPlayGame
{
public void Play()
{
Console.WriteLine("全民打麻藥。");
}
}
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<IPlayGame, NBPlayGame>();
}
public class TestMiddleware
{
public TestMiddleware(RequestDelegate next) { }
public Task InvokeAsync(HttpContext context, IPlayGame game)
{
game.Play();
return Task.CompletedTask;
}
}
public void Configure(IApplicationBuilder app)
{
app.UseMiddleware<TestMiddleware>();
}
public class DoSomething
{
public string GetMessage() => "你好,剛才 Boss 找你。";
}
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<DoSomething>();
}
public void Configure(IApplicationBuilder app, DoSomething thing)
{
Console.WriteLine(thing.GetMessage());
}
public interface IDemoService
{
string Version { get; }
void Run();
}
public class DemoService1 : IDemoService
{
public string Version => "v1";
public void Run()
{
Console.WriteLine("第一個服務實作類。");
}
}
public class DemoService2 : IDemoService
{
public string Version => "v2";
public void Run()
{
Console.WriteLine("第二個服務實作類。");
}
}
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<IDemoService, DemoService1>();
services.AddTransient<IDemoService, DemoService2>();
}
public class DemoMiddleware
{
public DemoMiddleware(RequestDelegate next)
{
// 由于程式約定,此構造函數必須提供。
}
public async Task InvokeAsync(HttpContext context, IDemoService sv)
{
await context.Response.WriteAsync(sv.Version);
}
}
public void Configure(IApplicationBuilder app, DoSomething thing)
{
app.UseMiddleware<DemoMiddleware>();
}
public async Task InvokeAsync(HttpContext context, IServiceProvider provider)
{
StringBuilder sb = new StringBuilder();
foreach (var sv in provider.GetServices<IDemoService>())
{
sb.Append($"{sv.Version}<br/>");
}
await context.Response.WriteAsync(sb.ToString());
}
public async Task InvokeAsync(HttpContext context, IEnumerable<IDemoService> svs)
{
StringBuilder sb = new StringBuilder();
foreach (var sv in svs)
{
sb.Append($"{sv.Version}<br/>");
}
await context.Response.WriteAsync(sb.ToString());
}