4. ASP.NET Core預設服務
之前講了中介軟體,實際上一個中介軟體要正常進行工作,通常需要許多的服務配合進行,而中介軟體中的服務自然也是透過 Ioc 容器進行註冊和注入的。前面也講到,按照約定中介軟體的封裝一般會提供一個 User{Middleware} 的擴充套件方法給使用者使用,而服務註冊中也有一個類似的約定,一般會有一個 Add{Services} 的擴充套件方法。
例如一個WebApi專案中,對於控制器路由終結點中介軟體的配置使用:
builder.Services.AddControllers();
var app = builder.Build();
app.MapControllers();
這也是我們在日常開發中可以學習的方式,隨著業務增長,需要依賴注入的服務也越來越多,我們可以根據業務模組,透過擴充套件方法講相應模組的服務注入註冊進行封裝,命名為 Add{Services},更加清晰明瞭地對我們的業務進行封裝。
.NET Core 框架下預設提供250個以上的的服務,包括 ASP.NET Core MVC、EF Core 等等,當然這些服務很多不會預設就注入到容器中,我們在新建一個專案的時候,不同專案框架的模板會幫我們預設配置好一些最基本的必須的服務,其他的服務我們可以根據自己的需要進行使用。
5. 依賴注入配置變形
隨著業務的增長,我們專案工作中的型別、服務越來越多,而每一個服務的依賴注入關係都需要在入口檔案透過Service.Add{}方法去進行註冊,這將是非常麻煩的,入口檔案需要頻繁改動,而且程式碼組織管理也會變得麻煩,非常不優雅。
在許多框架中會對這種透過 Service.Add{xxx} 的方式在程式碼中顯式註冊依賴注入關係的方式進行變形,有的可以透過配置檔案進行註冊,例如 Java Spring 框架就有這樣大量的配置檔案,有的可以透過介面進行預設註冊,有的透過特性進行預設註冊。
這裡稍微簡單介紹一下依賴注入預設註冊的原理,其實也就是透過放射的一些手段,再加上一些約定好的規則而已。
首先需要三個生命週期介面,如下,這三個介面沒有內容,僅僅只是作為標記而已。
public interface ISingleton
{
}
public interface IScoped
{
}
public interface ITransient
{
}
之後需要一個擴充套件方法,如下:
namespace Microsoft.Extensions.DependencyInjection
{
public static class ServiceCollectionDependencyExtensions
{
public static IServiceCollection AddAutoInject<T>(this IServiceCollection services)
{
var register = new ServiceRegister();
register.AddAssembly(services, typeof(T).Assembly);
return services;
}
}
}
這個擴充套件方法中呼叫了註冊器,往容器中注入服務,實現如下:
public class ServiceRegister
{
public void AddAssembly(IServiceCollection services, Assembly assembly)
{
// 查詢程式中的型別
var types = assembly.GetTypes().Where(t => t != null && t.IsClass && !t.IsAbstract && !t.IsGenericType);
// 遍歷每一個類檢查釋放滿足約定的規則
foreach (var type in types)
{
AddType(services, type);
}
}
/// <summary>
/// 新增當前型別的依賴注入關係
/// </summary>
/// <param name="services"></param>
/// <param name="type"></param>
public void AddType(IServiceCollection services, Type type)
{
var lifetime = GetLifetimeOrNull(type);
if (lifetime == null)
{
return;
var exposeServices = ExposeService(type);
foreach (var serviceType in exposeServices)
{
var serviceDescriptor = new ServiceDescriptor(serviceType, type, lifetime.Value);
services.Add(serviceDescriptor);
}
}
/// <summary>
/// 根據標記介面確定生命週期,如果沒有新增標記介面的,則不會被自動註冊到容器
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public ServiceLifetime? GetLifetimeOrNull(Type type)
{
if (typeof(ISingleton).IsAssignableFrom(type))
{
return ServiceLifetime.Singleton;
}
if(typeof(IScoped).IsAssignableFrom(type))
{
return ServiceLifetime.Scoped;
}
if(typeof(ITransient).IsAssignableFrom(type))
{
return ServiceLifetime.Transient;
}
return null;
}
/// <summary>
/// 根據約定的規則查詢當前類對於的服務型別
/// 透過介面實現的方式,查詢當前類實現的介面,如果一個介面名稱去除了 "I" 之後與當前類的後半段一樣,
/// 則當前類應該被註冊為這個介面的服務。
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public IList<Type> ExposeService(Type type)
{
var serviceTypes = new List<Type>();
var interfaces = type.GetInterfaces();
foreach (var interfacesType in interfaces)
{
var interfaceName = interfacesType.Name;
if (interfaceName.StartsWith("I"))
{
interfaceName = interfaceName.Substring(1);
}
if (type.Name.EndsWith(interfaceName))
{
serviceTypes.Add(interfacesType);
}
}
return serviceTypes;
}
}
整體的邏輯就是查詢遍歷程式集中的所有型別,並透過判別型別是否實現之前定好的三個生命週期介面,從而確定型別是否需要自動註冊到容器中,如果需要再根據約定好的規則獲取需要註冊的服務型別,並且構建服務描述器,再將其新增到容器中。
之後在入口檔案中這樣使用:
builder.Services.AddAutoInject<Program>();
而需要自動注入的服務只要多實現一個標記介面即可:
public class Rabbit : IRabbit, ITransient
{
}
以上主要介紹一下依賴注入自動化註冊的思路和基本實現,程式碼只是一個基本的演示,比較簡單,很多細節也沒有在這裡體現,但是核心的思路是和ABP框架中的自動注入的方式一樣的,有興趣詳細瞭解一下ABP中的依賴注入的機制的童鞋,可以看一下我其他的文章: ABP 依賴注入(1)
參考文章:
ASP.NET Core 依賴注入 | Microsoft Learn
理解ASP.NET Core - 依賴注入(Dependency Injection)
ASP.NET Core 系列: