ASP.NET Core

2023-03-01 18:00:57

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 系列:

目錄:ASP.NET Core 系列總結
上一篇:ASP.NET Core - 依賴注入(三)