01-《AspNetCore》-IOC

来源:https://www.cnblogs.com/chaeyeon/archive/2023/02/14/17119502.html
-Advertisement-
Play Games

IOC 視頻講解 基礎概念 Microsoft.Extensions.DependencyInjection.Abstractions:抽象包 Microsoft.Extensions.DependencyInjection:實現包 IServiceCollection:用於註冊服務(菜譜,記錄了每 ...


IOC

視頻講解

基礎概念

Microsoft.Extensions.DependencyInjection.Abstractions:抽象包

Microsoft.Extensions.DependencyInjection:實現包

IServiceCollection:用於註冊服務(菜譜,記錄了每一道菜的製作流程)

ServiceCollection:IServiceCollection介面預設的派生類

ServiceDescriptor:服務描述,(描述某一到菜的製作流程)

IServiceProvider:用於解析服務(廚師,可以通過菜名點菜)

ActivatorUtilities:有些服務我們不想註冊到容器,但是這個服務依賴了容器中的服務。此時可以通過ActivatorUtilities來創建。

使用容器不是說解耦合,解耦合還是得通過介面。依賴註入就是基於耦合來依賴註入的,來創建服務的。只不過可以簡化這個實列化過程和後續的維護。

依賴:DbContext和IConnection,就是依賴關係,註意不要出現迴圈依賴。

註冊:吧服務添加到IServiceCollection的過程。

註入:容器通過依賴關係,查找IConnection實列,把IConnection實列註入到DbContext的構造器的過程。

手動註入:根據依賴關係手動創建依賴的對象,並且註入到目標服務的過程。

自動註入:根據依賴關係容器通過反射創建依賴的對象,並且註入到目標服務的過程。

public class IConnection 
{

}
public class SqlConnection : IConnection
{

}
public class DbContext
{
    //DbContext依賴IConnection
	public DbContext(IConnection connection)
	{
	
	}
}
IConnection connection = new SqlConnectioon();
//註入:手動註入,容器可以自動註入
var context = new DbContext(connection);

服務註冊

//創建容器
IServiceCollection services = new ServiceCollection();
//1.通過ServiceDescriptor創建,寫框架時有用(萬能公式)
services.Add(new ServiceDescriptor(typeof(IConnection),typeof(SqlConnection),ServiceLifetime.Singleton));
//2.泛型方式,此時服務類型為IConnection
services.AddSingleton<IConnection, SqlConnection>();
//3.委托註冊,可以定義創建邏輯,在註冊服務時也能解析服務
services.AddSingleton(sp => 
{
    //sp:是容器實列可以用於解析以註冊的服務(IServiceProvider)
    var connection = sp.GetService<IConnection>();
    return new DbContext(connection,"fff");
    //高級
	//return ActivatorUtilities.CreateInstance<IConnection>(sp,"fff");
});
//4.服務類型和實現類型相同
services.AddSingleton<SqlConnection>();
//5.泛型註冊,這樣可以獲取到所有Logger<>的完整類型(泛型參數不要寫死)
services.AddSingleton(typeof(Logger<>));
//6.反射的方式,寫框架很有用
services.AddSingleton(typeof(SqlConnection));
//7.註意使用反射構建泛型參數,此時註冊的是Logger<Program>服務(ps:寫框架的人會用到)
services.AddSingleton(typeof(Logger<>).MakeGenericType(typeof(Program)));  
//8.替換服務,如果服務已註冊則不進行註冊。一般寫框架會用到,如果框架使用了Try...那麼你可以使用自定義的服務在它之前進行替換
services.TryAddSingleton(typeof(SqlConnection));

設計模式

設計模式就是解決特定問題的套路,使用設計模式可以方便溝通、理解和相互學習。不要把設計模式學死了。

工廠模式-(側重對象管理)

1.工廠模式主要用於實現對象的創建,多實例的管理,命名對象的管理,也可以用於管理Provider。和Manager模式的區別是,工廠模式一般不負責執行業務。

2.由於微軟的容器只能更加類型來解析服務,有時候我們需要通過名稱來解析服務,此時需要使用工廠模式。

3.命名模式的支持,比如ILoggerFactory

public class Connection
{

}

public class ConnectionFactory
{
    private IServiceProvider _serviceProvider;
    private Dictionary<string, Type> _connections;

    public ConnectionFactory(IServiceProvider provider, Dictionary<string, Type> connections)
    {
        _serviceProvider = provider;
        _connections = connections;
    }

    public Connection? Get(string name)
    {
        if (_connections.TryGetValue(name, out Type? connectionType))
        {
            return _serviceProvider.GetService(connectionType) as Connection;                
        }
        return default;
    }
}

構造者模式-(側重對象構建)

1.通過一個構造器(Builder)來提供豐富的api來構造目標對象。簡化目標對象的創建,豐富目標對象的創建方式。構造器一般要提供一個Build用來返回被構造的對象的實列。

2.IServiceCollection:就是IServiceProvider的構造者

3.註意區分構造函數

public class Connection
{

}
public class ConnectionFactoryBuilder
{
    private Dictionary<string, Connection> _connections = new();

    public ConnectionFactoryBuilder Add(string name,Connection connection)
    {
        _connections.Add(name, connection);
        return this;//一般要支持鏈式調用
    }

    public ConnectionFactory Build()
    {
        return new ConnectionFactory(_connections);
    }
}
public static class ConnectionFactoryBuilderExtensions
{
    public static ConnectionFactoryBuilder Add(this ConnectionFactoryBuilder builder, Connection connection)
    {
        var name = connection.GetType().Name;
        builder.Add(name, connection);
        return this;//一般要支持鏈式調用
    }
}

提供者模式-(側重業務)

1.提供者模式一般支持用戶實現,並且支持多實現的。比如日誌有控制台提供程式,Debug提供程式,自定義提供程式。

2.提供者更加傾向於業務,一般提供者都是設計成可以支持用戶去實現,支持多種擴展的。

3.提供者模式和工廠模式相識,一般我們希望用戶可以自定義並且支持多實現的時候使用提供者模式。

4.Provider和工廠模式的區別是,Provider更加傾向於業務邏輯的封裝。

public interface IConfigurationProvider
{
	string Get(string key);
}

public class JsonConfigurationProvider: IConfigurationProvider
{

}
public class XmlConfigurationProvider : IConfigurationProvider
{

}

管理者模式-(側重業務管理)

1.用於管理模式,當我們有多個策略需要一個管理者來管理的時候,可以使用Manager模式。管理者模式可以用於管理Provider。

2.管理者模式和工廠模式也很像,但是管理者模式除了管理對象,還負責執行業務。

public class ConfigurationManager
{
	private List<IConfigurationProvider> _providers = new (); 
    
    public void AddProvider(IConfigurationProvider provider)
    {
        _providers.Add(provider);
    }
    
    public string Get(string key)
    {
        foreach(var item in _providers)
        {
            var value = item.Get(key);
            if(value != null)
            {
                return value;
            }    
        }
        return default;
    }
}

基本使用

IServiceCollection services = new ServiceCollection();
services.AddSingleton<IConnection, SqlConnection>();
services.AddSingleton<IConnection, MySqlConnection>();
services.AddSingleton<AService>();
IServiceProvider container = services.BuildServiceProvider();
var connection = container.GetRequiredService<IConnection>();
var service = container.GetService<AService>();
Console.WriteLine(connection.GetType().Name);

服務解析

//創建容器
IServiceCollection services = new ServiceCollection();
//註冊服務
services.AddSingleton<IConnection, SqlConnection>();
//構建容器
IServiceProvider container = services.BuildServiceProvider();
//解析服務
var connection = container.GetService<IConnection>();
//解析服務,如果解析不到會拋出異常
var connection = container.GetRequiredService<IConnection>();
//解析所有IConnection類型的服務
IEnumerable<IConnection> connections = container.GetServices<IConnection>();
//解析一個沒有註冊到容器但是依賴了容器已註冊的服務,寫框架常用
ActivatorUtilities.CreateInstance<DbContext>(container);

生命周期

根容器:生命周期與應用程式一致。

子容器:聲明周期由開發者決定。

Singleton:同一個容器無論是否是根容器解析出來的實列都是唯一的。

Transient:每次解析都是一個新的實列

Scoped:同一個IServiceScope解析出來的實列是唯一的。

Scoped要點:

1.不要通過根容器來解析Scope實例的服務,因為根容器在程式運行過程中不會釋放。那麼解析出來的服務也不會釋放。

2.Scope的範圍有多大不是卻決於一次http請求,而是卻決於你何時釋放。

3.IServiceScope會記錄下由它解析出來的服務,如果IServiceScope實列被釋放,那麼由它解析出來的實列都將被釋放。

4.註意雖然根容器和子容器都實現了IServiceProvider介面,但是他們的實現類不一樣。

5.單實列的服務不要去依賴一個Scope級別的服務。

搭建測試案例

public class Connection
{
    public string Id { get; }

    //每次實列化的時候執行一次,得到一個唯一id
    public Connection()
    {
        Id = Guid.NewGuid().ToString();
    }
}
var services = new ServiceCollection();
services.AddScoped<Connection>();
var container = services.BuildServiceProvider(new ServiceProviderOptions()
{
    ValidateScopes = true,//指示是否可以通過根容器來解析Scope實列。
	ValidateOnBuild = true//構建之前時檢查是否有依賴沒有註冊的服務	
});
//測試scoped
var connection1 = container.GetRequiredService<Connection>();
Console.WriteLine(connection1.Id);
using(var scope = container.CreateScope())
{
    var connection2 = scope.ServiceProvider.GetRequiredService<Connection>();
    Console.WriteLine(connection2.Id);
}

組件掃描

組件掃描可以通過介面或者特性的方式。這裡我們展示使用特性的方式,因為特性可以配置參數。

//定義一個註解
[AttributeUsage(AttributeTargets.Class)]
public class InjectionAttribute : Attribute
{
    public Type? ServiceType { get; set; }
    public ServiceLifetime Lifetime { get; set; } = ServiceLifetime.Transient;
}
public static class DependencyInjectionExtensions
{
    //掃描
    public static IServiceCollection AddInjectionServices<T>(this IServiceCollection services)
    {
        var serviceTypes = GetInjectionServiceTypeList(typeof(T).Assembly);
        foreach (var item in serviceTypes)
        {
            var injection = item.GetCustomAttribute<InjectionAttribute>();
            if (injection!.ServiceType == null)
            {
                services.Add(new ServiceDescriptor(item, item, injection.Lifetime));
            }
            else
            {
                services.Add(new ServiceDescriptor(injection!.ServiceType, item, injection.Lifetime));
            }
        }
        return services;
    }

    private static IEnumerable<Type> GetInjectionServiceTypeList(Assembly assembly)
    {
        var serviceType = assembly.GetTypes()
            .Where(a => a.IsClass)
            .Where(a => a.GetCustomAttribute<InjectionAttribute>() != null)
            .Where(a => !a.IsAbstract);
        return serviceType;
    }
}

public interface ILogger<T>
{
    void Log();
}
//註入
[Injection(ServiceType = typeof(ILogger<>), Lifetime = ServiceLifetime.Singleton)]
internal class Logger<T>: ILogger<T>
{
    public void Log()
    {
        Console.WriteLine(typeof(T).Name+":success!");
    }
}
public static void TestScanner()
{
     var services = new ServiceCollection();
     //掃描組件
     services.AddInjectionServices<Program>();
     var container = services.BuildServiceProvider();
     var logger = container.GetRequiredService<ILogger<Program>>();
     logger.Log();
}

基本原理

自定義IOC需要實現一下兩步:

1.編寫一個ContailerBuilder,用於註冊服務的描述信息,並且能夠相容IServiceCollection註冊的服務描述。

2.實現IServiceProvider介面,通過載入ContailerBuilder,並解析服務。

思考為什麼是這兩步?

1.因為微軟的大部分組件都是基於IServiceProvider來進行服務解析的。因此這個介面必須實現。

2.ServiceCollection註冊服務的描述信息很簡單。你需要更加複雜的容器實現,因此ServiceDescriptor無法描述你的服務類型。因此你需要寫一個ContailerBuilder用於記錄你的服務的描述信息。

3.需要相容IServiceCollection註冊的服務的描述。比如autofac支持屬性註冊,但是通過ServiceDescriptor無法描述。因為很多框架的服務註冊是基於IServiceCollection,因此你必須能相容微軟的IOC的全部能力。

原理

我們需要一個ContainerBuilder和一個Container類和服務描述類ServiceDescriptor。ContainerBuilder本質是一個集合,用於記錄用戶註冊的服務組件,以及描述信息。

ServiceDescriptor:服務描述信息(服務類型、實列類型,生命周期,創建委托等等),告訴Container將來如何解析實列化服務。

ContainerBuilder:用於記錄描述信息的集合,提供api快速便捷的構建容器。

Container:用於解析服務,創建實列。

註冊過程:就是創建服務描述的過程,向ContainerBuilder添加服務描述,比如告訴容器這個服務的生命周期,服務類型,實現類型,創建方式,是否支持屬性註入,配置實例化時的回調,釋放時的回調等等。

構建過程:創建容器的過程,完成容器的一些初始化,並講服務註冊的描述信息傳遞給容器。

解析過程:一般通過服務的類型,我們可以理解為它是服務的key,通過服務的key找到服務註冊的描述信息。

如果是普通註冊的服務,那麼解析這個實列的時候,找到這個實列的構造器,得到這個實列依賴的其他服務,創建依賴的實列,這是一個遞歸的過程。

如果是委托註冊的服務,那麼解析這個實列的時候,調用委托返回實列。

如果是命名註冊的服務,那麼一般是通過一個工廠模式來解析。

實列化的方式可以參考OOP中的幾種實列化方式,反射,表達式樹,Emit等技術。

Autofac

autofac/Autofac.Extensions.DependencyInjection:Microsoft.Extensions.DependencyInjection.Abstractions(.NET Core dependency injection abstraction)中介面的Autofac實現。 (github.com)

autofac提供了更多的功能,比如屬性註入,組件掃描等等非常豐富的功能。我也很少使用。微軟的IOC容器只支持構造函數的依賴關係註入但是基本夠用,如果還要其它需求的可以選擇使用autofac。

public static void TestAutofac()
{
    var services = new ServiceCollection();
    //微軟的容器註冊服務
    services.AddScoped(typeof(ILogger<>),typeof(Logger<>));
    var builder = new ContainerBuilder();
    //autofac容器註冊服務
    builder.RegisterType<CService>().PropertiesAutowired()
        .As<CService>()
        .InstancePerLifetimeScope();
    builder.Populate(services);//將IServiceCollection中的服務註冊到autofac
    //使用AutofacServiceProvider的實現方案,創建容器
    //載入autofac中的服務
    IServiceProvider container = new AutofacServiceProvider(builder.Build());
    var logger = container.GetRequiredService<ILogger<Program>>();
    var service = container.GetRequiredService<CService>();
}

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 文章內容整理自 博學谷狂野架構師 概述 什麼是函數式介面?簡單來說就是只有一個抽象函數的介面。為了使得函數式介面的定義更加規範,java8 提供了@FunctionalInterface 註解告訴編譯器在編譯器去檢查函數式介面的合法性,以便在編譯器在編譯出錯時給出提示。為了更加規範定義函數介面,給出 ...
  • 教程簡介 數字營銷概述 - 從簡單和簡單的步驟學習數字營銷,從基本到高級概念,包括概述,搜索引擎優化,社交媒體,內容,電子郵件,移動,點擊付費,CRO,網站分析,Facebook,Pinterest,Twitter,Linkedin ,Youtube Marketing,Google Adwords ...
  • 演算法分析 棋盤型狀態壓縮dp 這類dp有一個通用的狀態表示法:f[i][j][k],表示前i行(放了j個棋子後)的狀態表示為k。 由於本題無棋子要求,因此可以省去中間一維, 即: 用f[i][j]表示前i行土地的狀態為j。 首先由於玉米地有不肥沃的地方不能種植,因此需要通過二進位表示出來可以種植和不 ...
  • 摘要:基於.NET Core 7.0WebApi後端架構實戰【1-項目結構分層設計】 2023/02/05, ASP.NET Core 7.0, VS2022 引言 從實習到現在回想自己已經入行四年了,很慶幸自己一直還是從事著開發的工作。這幾年不管是工作還是生活都有很多不甘與失落還有收穫,從學校出來 ...
  • 如果是首次安裝Dev只需要下麵兩步流程就可以第一步安裝試用的最新版 Devexpress 22.2.4這步看直接去官網,安裝官方試用的就可以第二步安裝破解補丁關閉防火牆或360 然後打開 DevExpress.Universal.Patch 選擇22.2 版本 和對應的visual studio 的 ...
  • 什麼是API限流: API 限流是限制用戶在一定時間內 API 請求數量的過程。應用程式編程介面 (API) 充當用戶和軟體應用程式之間的網關。例如,當用戶單擊社交媒體上的發佈按鈕時,點擊該按鈕會觸發 API 調用。此 API 與社交媒體應用程式的網路伺服器進行交互,並執行發佈操作。此用戶可以是人, ...
  • AOP 視頻講解 面向切麵編程AOP的對面向對象編程OOP的一個補充,它的特點是將系統邏輯和業務邏輯採取《非侵入式》分離。我們把系統封裝成一個一個的切麵(單一職責)進行順意編排組合,插入(織入)到業務邏輯的執行過程(織入點)。 系統邏輯:異常處理,身份認證,授權,mvc,數據校驗,事務處理。 業務邏 ...
  • Configuration 視頻講解 package說明 Microsoft.Extensions.Configuration.Abstractions:抽象包,一大堆的介面 Microsoft.Extensions.Configuration.Binder:提供一大堆的擴展,比如類型轉換 Micr ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...