.Net依賴註入神器Scrutor(上)

来源:https://www.cnblogs.com/ruipeng/p/18081965
-Advertisement-
Play Games

前言 從.Net Core 開始,.Net 平臺內置了一個輕量,易用的 IOC 的框架,供我們在應用程式中使用,社區內還有很多強大的第三方的依賴註入框架如: Autofac DryIOC Grace LightInject Lamar Stashbox Simple Injector 內置的依賴註入 ...


前言

從.Net Core 開始,.Net 平臺內置了一個輕量,易用的 IOC 的框架,供我們在應用程式中使用,社區內還有很多強大的第三方的依賴註入框架如:

內置的依賴註入容器基本可以滿足大多數應用的需求,除非你需要的特定功能不受它支持否則不建議使用第三方的容器。

我們今天介紹的主角Scrutor是內置依賴註入的一個強大的擴展,Scrutor有兩個核心的功能:一是程式集的批量註入 Scanning,二是 Decoration 裝飾器模式,今天的主題是Scanning

開始之前在項目中安裝 nuget 包:

Install-Package Scrutor

學習Scrutor前我們先熟悉一個.Net依賴註入的萬能用法。

builder.Services.Add(
    new ServiceDescriptor(/*"ServiceType"*/typeof(ISampleService), /*"implementationType"*/typeof(SampleService), ServiceLifetime.Transient)
    );

第一個參數ServiceType通常用介面表示,第二個implementationType介面的實現,最後生命周期,熟悉了這個後面的邏輯理解起來就容易些。

Scrutor官方倉庫和本文完整的源代碼在文末

Scanning

Scrutor提供了一個IServiceCollection的擴展方法作為批量註入的入口,該方法提供了Action<ITypeSourceSelector>委托參數。

builder.Services.Scan(typeSourceSelector => { });

我們所有的配置都是在這個委托內完成的,Setup by Setup 剖析一下這個使用過程。

第一步 獲取 types

typeSourceSelector 支持程式集反射獲取類型和提供類型參數

程式集選擇

ITypeSourceSelector有多種獲取程式集的方法來簡化我們選擇程式集


 typeSourceSelector.FromAssemblyOf<Program>();//根據泛型反射獲取所在的程式集

typeSourceSelector.FromCallingAssembly();//獲取開始發起調用方法的程式集

typeSourceSelector.FromEntryAssembly();//獲取應用程式入口點所在的程式集

typeSourceSelector.FromApplicationDependencies();//獲取應用程式及其依賴項的程式集

typeSourceSelector.FromDependencyContext(DependencyContext.Default);//根據依賴關係上下文(DependencyContext)中的運行時庫(Runtime Library)列表。它返回一個包含了所有運行時庫信息的集合。

typeSourceSelector.FromAssembliesOf(typeof(Program));//根據類型獲取程式集的集合

typeSourceSelector.FromAssemblies(Assembly.Load("dotNetParadise-Scrutor.dll"));//提供程式集支持Params或者IEnumerable

第二步 從 Types 中選擇 ImplementationType

簡而言之就是從程式中獲取的所有的 types 進行過濾,比如獲取的 ImplementationType 必須是非抽象的,是類,是否只需要 Public等,還可以用 ImplementationTypeFilter 提供的擴展方法等


builder.Services.Scan(typeSourceSelector =>
{
    typeSourceSelector.FromEntryAssembly().AddClasses();
});

AddClasses()方法預設獲取所有公開非抽象的類


還可以通過 AddClasses 的委托參數來進行更多條件的過濾
比如定義一個 Attribute,忽略IgnoreInjectAttribute

namespace dotNetParadise_Scrutor;

[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class IgnoreInjectAttribute : Attribute
{
}
builder.Services.Scan(typeSourceSelector =>
{
    typeSourceSelector.FromEntryAssembly().AddClasses(iImplementationTypeFilter =>
    {
        iImplementationTypeFilter.WithoutAttribute<IgnoreInjectAttribute>();
    });
});

利用 iImplementationTypeFilter 的擴展方法很簡單就可以實現

在比如 我只要想實現IApplicationService介面的類才可以被註入

namespace dotNetParadise_Scrutor;

/// <summary>
/// 依賴註入標記介面
/// </summary>
public interface IApplicationService
{
}
builder.Services.Scan(typeSourceSelector =>
{
    typeSourceSelector.FromEntryAssembly().AddClasses(iImplementationTypeFilter =>
    {
        iImplementationTypeFilter.WithoutAttribute<IgnoreInjectAttribute>().AssignableTo<IApplicationService>();
    });
});

類似功能還有很多,如可以根據命名空間也可以根據Type的屬性用lambda表達式對ImplementationType進行過濾


上面的一波操作實際上就是為了構造一個IServiceTypeSelector對象,選出來的ImplementationType對象保存了到了ServiceTypeSelectorTypes屬性中供下一步選擇。
除了提供程式集的方式外還可以直接提供類型的方式比如

創建介面和實現

public interface IForTypeService
{
}
public class ForTypeService : IForTypeService
{
}
builder.Services.Scan(typeSourceSelector =>
{
    typeSourceSelector.FromTypes(typeof(ForTypeService));
});

這種方式提供類型內部會調用AddClass()方法把符合條件的參數保存到ServiceTypeSelector

第三步確定註冊策略

AddClass之後可以調用UsingRegistrationStrategy()配置註冊策略是 AppendSkipThrowReplace
下麵是各個模式的詳細解釋

  • RegistrationStrategy.Append :類似於builder.Services.Add
  • RegistrationStrategy.Skip:類似於builder.Services.TryAdd
  • RegistrationStrategy.Throw:ServiceDescriptor 重覆則跑異常
  • RegistrationStrategy.Replace: 替換原有服務

這樣可以靈活地控制註冊流程

    builder.Services.Scan(typeSourceSelector =>
    {
        typeSourceSelector.FromEntryAssembly().AddClasses().UsingRegistrationStrategy(RegistrationStrategy.Skip);
    });

不指定則為預設的 Append 即 builder.Services.Add

第四步 配置註冊的場景選擇合適的ServiceType

ServiceTypeSelector提供了多種方法讓我們從ImplementationType中匹配ServiceType

  • AsSelf()
  • As<T>()
  • As(params Type[] types)
  • As(IEnumerable<Type> types)
  • AsImplementedInterfaces()
  • AsImplementedInterfaces(Func<Type, bool> predicate)
  • AsSelfWithInterfaces()
  • AsSelfWithInterfaces(Func<Type, bool> predicate)
  • AsMatchingInterface()
  • AsMatchingInterface(Action<Type, IImplementationTypeFilter>? action)
  • As(Func<Type, IEnumerable<Type>> selector)
  • UsingAttributes()

AsSelf 註冊自身

public class AsSelfService
{
}
{
    builder.Services.Scan(typeSourceSelector =>
    {
        typeSourceSelector.FromEntryAssembly().AddClasses(iImplementationTypeFilter =>
        {
            iImplementationTypeFilter.InNamespaces("dotNetParadise_Scrutor.Application.AsSelf").WithoutAttribute<IgnoreInjectAttribute>();
        }).AsSelf();
    });

    Debug.Assert(builder.Services.Any(_ => _.ServiceType == typeof(AsSelfService)));
}

等效於builder.Services.AddTransient<AsSelfService>();


As 批量為 ImplementationType 指定 ServiceType

public interface IAsService
{
}
public class AsOneService : IAsService
{
}
public class AsTwoService : IAsService
{
}
{
    builder.Services.Scan(typeSourceSelector =>
{
    typeSourceSelector.FromEntryAssembly().AddClasses(iImplementationTypeFilter =>
    {
        iImplementationTypeFilter.InNamespaces("dotNetParadise_Scrutor.Application.As").WithoutAttribute<IgnoreInjectAttribute>();
    }).As<IAsService>();
});
    Debug.Assert(builder.Services.Any(_ => _.ServiceType == typeof(IAsService)));
    foreach (var asService in builder.Services.Where(_ => _.ServiceType == typeof(IAsService)))
    {
        Debug.WriteLine(asService.ImplementationType!.Name);
    }
}

As(params Type[] types)和 As(IEnumerable types) 批量為ImplementationType指定多個 ServiceType,服務必須同時實現這裡面的所有的介面

上面的實例再改進一下

public interface IAsOtherService
{
}
public interface IAsSomeService
{
}

public class AsOneMoreTypesService : IAsOtherService, IAsSomeService
{
}
public class AsTwoMoreTypesService : IAsSomeService, IAsOtherService
{
}

{
    builder.Services.Scan(typeSourceSelector =>
    {
        typeSourceSelector.FromEntryAssembly().AddClasses(iImplementationTypeFilter =>
        {
            iImplementationTypeFilter.InNamespaces("dotNetParadise_Scrutor.Application.AsMoreTypes").WithoutAttribute<IgnoreInjectAttribute>();
        }).As(typeof(IAsSomeService), typeof(IAsOtherService));
    });
    List<Type> serviceTypes = [typeof(IAsSomeService), typeof(IAsOtherService)];
    Debug.Assert(serviceTypes.All(serviceType => builder.Services.Any(service => service.ServiceType == serviceType)));
    foreach (var asService in builder.Services.Where(_ => _.ServiceType == typeof(IAsSomeService) || _.ServiceType == typeof(IAsOtherService)))
    {
        Debug.WriteLine(asService.ImplementationType!.Name);
    }
}

AsImplementedInterfaces 註冊當前 ImplementationType 和實現的介面

public interface IAsImplementedInterfacesService
{
}
public class AsImplementedInterfacesService : IAsImplementedInterfacesService
{
}
//AsImplementedInterfaces 註冊當前ImplementationType和它實現的介面
{
    builder.Services.Scan(typeSourceSelector =>
    {
        typeSourceSelector.FromEntryAssembly().AddClasses(iImplementationTypeFilter =>
        {
            iImplementationTypeFilter.InNamespaces("dotNetParadise_Scrutor.Application.AsImplementedInterfaces").WithoutAttribute<IgnoreInjectAttribute>();
        }).AsImplementedInterfaces();
    });

    Debug.Assert(builder.Services.Any(service => service.ServiceType == typeof(IAsImplementedInterfacesService)));
    foreach (var asService in builder.Services.Where(_ => _.ServiceType == typeof(IAsImplementedInterfacesService)))
    {
        Debug.WriteLine(asService.ImplementationType!.Name);
    }
}

AsSelfWithInterfaces 同時註冊為自身類型和所有實現的介面

public interface IAsSelfWithInterfacesService
{
}
public class AsSelfWithInterfacesService : IAsSelfWithInterfacesService
{
}

{
    builder.Services.Scan(typeSourceSelector =>
    {
        typeSourceSelector.FromEntryAssembly().AddClasses(iImplementationTypeFilter =>
        {
            iImplementationTypeFilter.InNamespaces("dotNetParadise_Scrutor.Application.AsSelfWithInterfaces").WithoutAttribute<IgnoreInjectAttribute>();
        }).AsSelfWithInterfaces();
    });
    //Self
    Debug.Assert(builder.Services.Any(service => service.ServiceType == typeof(AsSelfWithInterfacesService)));
    //Interfaces
    Debug.Assert(builder.Services.Any(service => service.ServiceType == typeof(IAsSelfWithInterfacesService)));
    foreach (var service in builder.Services.Where(_ => _.ServiceType == typeof(AsSelfWithInterfacesService) || _.ServiceType == typeof(IAsSelfWithInterfacesService)))
    {
        Debug.WriteLine(service.ServiceType!.Name);
    }
}

AsMatchingInterface 將服務註冊為與其命名相匹配的介面,可以理解為一定約定假如服務名稱為 ClassName,會找 IClassName 的介面作為 ServiceType 註冊

public interface IAsMatchingInterfaceService
{
}
public class AsMatchingInterfaceService : IAsMatchingInterfaceService
{
}
//AsMatchingInterface 將服務註冊為與其命名相匹配的介面,可以理解為一定約定假如服務名稱為 ClassName,會找 IClassName 的介面作為 ServiceType 註冊
{
    builder.Services.Scan(typeSourceSelector =>
    {
        typeSourceSelector.FromEntryAssembly().AddClasses(iImplementationTypeFilter =>
        {
            iImplementationTypeFilter.InNamespaces("dotNetParadise_Scrutor.Application.AsMatchingInterface").WithoutAttribute<IgnoreInjectAttribute>();
        }).AsMatchingInterface();
    });
    Debug.Assert(builder.Services.Any(service => service.ServiceType == typeof(IAsMatchingInterfaceService)));
    foreach (var service in builder.Services.Where(_ => _.ServiceType == typeof(IAsMatchingInterfaceService)))
    {
        Debug.WriteLine(service.ServiceType!.Name);
    }
}

UsingAttributes 特性註入,這個還是很實用的在Scrutor提供了ServiceDescriptorAttribute來幫助我們方便的對Class進行標記方便註入

public interface IUsingAttributesService
{
}

[ServiceDescriptor<IUsingAttributesService>()]
public class UsingAttributesService : IUsingAttributesService
{
}
    builder.Services.Scan(typeSourceSelector =>
    {
        typeSourceSelector.FromEntryAssembly().AddClasses(iImplementationTypeFilter =>
        {
            iImplementationTypeFilter.InNamespaces("dotNetParadise_Scrutor.Application.UsingAttributes").WithoutAttribute<IgnoreInjectAttribute>();
        }).UsingAttributes();
    });
    Debug.Assert(builder.Services.Any(service => service.ServiceType == typeof(IUsingAttributesService)));
    foreach (var service in builder.Services.Where(_ => _.ServiceType == typeof(IUsingAttributesService)))
    {
        Debug.WriteLine(service.ServiceType!.Name);
    }

第五步 配置生命周期

通過鏈式調用WithLifetime函數來確定我們的生命周期,預設是 Transient

public interface IFullService
{
}
public class FullService : IFullService
{
}
{

    builder.Services.Scan(typeSourceSelector =>
    {
        typeSourceSelector.FromEntryAssembly().AddClasses(iImplementationTypeFilter =>
        {
            iImplementationTypeFilter.InNamespaces("dotNetParadise_Scrutor.Application.Full");
        }).UsingRegistrationStrategy(RegistrationStrategy.Skip).AsImplementedInterfaces().WithLifetime(ServiceLifetime.Scoped);
    });

    Debug.Assert(builder.Services.Any(service => service.ServiceType == typeof(IFullService)));
    foreach (var service in builder.Services.Where(_ => _.ServiceType == typeof(IFullService)))
    {
        Debug.WriteLine($"serviceType:{service.ServiceType!.Name},LifeTime:{service.Lifetime}");
    }
}

總結

到這兒基本的功能已經介紹完了,可以看出來擴展方法很多,基本可以滿足開發過程批量依賴註入的大部分場景。
使用技巧總結:

  • 根據程式集獲取所有的類型 此時 Scrutor 會返回一個 IImplementationTypeSelector 對象裡面包含了程式集的所有類型集合
  • 調用 IImplementationTypeSelectorAddClasses 方法獲取 IServiceTypeSelector 對象,AddClass 這裡面可以根據條件選擇 過濾一些不需要的類型
  • 調用UsingRegistrationStrategy確定依賴註入的策略 是覆蓋 還是跳過亦或是拋出異常 預設 Append 追加註入的方式
  • 配置註冊的場景 比如是 AsImplementedInterfaces 還是 AsSelf
  • 選擇生命周期 預設 Transient

藉助ServiceDescriptorAttribute更簡單,生命周期和ServiceType都是在Attribute指定好的只需要確定選擇程式集,調用UsingRegistrationStrategy配置依賴註入的策略然後UsingAttributes()即可

最後

本文從Scrutor的使用流程剖析了依賴註入批量註冊的流程,更詳細的教程可以參考Github 官方倉庫。在開發過程中看到很多項目還有一個個手動註入的,也有自己寫 Interface或者是Attribute反射註入的,支持的場景都十分有限,Scrutor的出現就是為了避免我們在項目中不停地造輪子,達到開箱即用的目的。

本文完整示例源代碼

本文來自博客園,作者:董瑞鵬,轉載請註明原文鏈接:https://www.cnblogs.com/ruipeng/p/18081965


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

-Advertisement-
Play Games
更多相關文章
  • 基於Js和Java+MyBatis實現xlsx\xls文檔的導入下載、導出 背景: ​ 實現xlsx\xls文檔的導入、導出 ​ 導入效果: ​ 導出效果: 導出效果圖 1、導入、下載 1.1、前臺 <div style="margin-left: 15px"> <input type="file" ...
  • 本文介紹基於Python中ArcPy模塊,讀取Excel表格數據並生成帶有屬性表的矢量要素圖層,同時配置該圖層的坐標系的方法~ ...
  • 前言 還有個迭代器,基礎語法基本已經說完了,後面想到啥再補充,之後的教程會從以下方面來講: 基礎庫的使用,比如string、table等 基礎控制項的使用,比如listview、tab等 aardio和Python交互,比如給Python寫個界面 自帶的範常式序 我寫的一些小程式 當然,我的理解也是很 ...
  • 在.NET中Newtonsoft.Json(Json.NET)是我們常用來進行Json序列化與反序列化的庫。 而在使用中常會遇到反序列化Json時,遇到不規則的Json數據解構而拋出異常。 Newtonsoft.Json 支持序列化和反序列化過程中的錯誤處理。 允許您捕獲錯誤並選擇是處理它並繼續序列 ...
  • 作者引言 很高興啊,我們來到了第一篇,程式員的HelloWorld,快速開始RPC之游 快速入門 演示如何在幾分鐘內,使用IceRPC,構建和運行一個完整的客戶端-伺服器(C/S)應用程式. 必要條件: 只要電腦安裝 .NET 8 SDK 就行了. 來吧,開始你的RPC之旅 接下來,我們要一起構建一 ...
  • 攔截器Interceptors是一種可以在編譯時以聲明方式替換原有應用的方法。 這種替換是通過讓Interceptors聲明它攔截的調用的源位置來實現的。 您可以使用攔截器作為源生成器的一部分進行修改,而不是向現有源編譯添加代碼。 演示 使用 .NET 8 創建一個控制台應用程式。併在Propert ...
  • 概述:C#中整數除法返回整數,維護與低級語言相容性,提高性能。雖然精度有損,但可通過顯式浮點數轉換實現小數保留。 在C#中,整數除法返回整數而不是浮點數,這是為了保持與低級語言(如C和C++)的相容性,同時提高性能和降低複雜性。這種設計使得整數之間的除法操作更加高效,但可能導致精度喪失。 基礎功能: ...
  • Linq的學習 這裡繼續使用之前文章創建的學生類,首先簡單介紹一下linq的使用。 Student.cs public class Student { public int Id { get; set; } public int ClassId { get; set; } public string ...
一周排行
    -Advertisement-
    Play Games
  • 1、預覽地址:http://139.155.137.144:9012 2、qq群:801913255 一、前言 隨著網路的發展,企業對於信息系統數據的保密工作愈發重視,不同身份、角色對於數據的訪問許可權都應該大相徑庭。 列如 1、不同登錄人員對一個數據列表的可見度是不一樣的,如數據列、數據行、數據按鈕 ...
  • 前言 上一篇文章寫瞭如何使用RabbitMQ做個簡單的發送郵件項目,然後評論也是比較多,也是準備去學習一下如何確保RabbitMQ的消息可靠性,但是由於時間原因,先來說說設計模式中的簡單工廠模式吧! 在瞭解簡單工廠模式之前,我們要知道C#是一款面向對象的高級程式語言。它有3大特性,封裝、繼承、多態。 ...
  • Nodify學習 一:介紹與使用 - 可樂_加冰 - 博客園 (cnblogs.com) Nodify學習 二:添加節點 - 可樂_加冰 - 博客園 (cnblogs.com) 介紹 Nodify是一個WPF基於節點的編輯器控制項,其中包含一系列節點、連接和連接器組件,旨在簡化構建基於節點的工具的過程 ...
  • 創建一個webapi項目做測試使用。 創建新控制器,搭建一個基礎框架,包括獲取當天日期、wiki的請求地址等 創建一個Http請求幫助類以及方法,用於獲取指定URL的信息 使用http請求訪問指定url,先運行一下,看看返回的內容。內容如圖右邊所示,實際上是一個Json數據。我們主要解析 大事記 部 ...
  • 最近在不少自媒體上看到有關.NET與C#的資訊與評價,感覺大家對.NET與C#還是不太瞭解,尤其是對2016年6月發佈的跨平臺.NET Core 1.0,更是知之甚少。在考慮一番之後,還是決定寫點東西總結一下,也回顧一下.NET的發展歷史。 首先,你沒看錯,.NET是跨平臺的,可以在Windows、 ...
  • Nodify學習 一:介紹與使用 - 可樂_加冰 - 博客園 (cnblogs.com) Nodify學習 二:添加節點 - 可樂_加冰 - 博客園 (cnblogs.com) 添加節點(nodes) 通過上一篇我們已經創建好了編輯器實例現在我們為編輯器添加一個節點 添加model和viewmode ...
  • 前言 資料庫併發,數據審計和軟刪除一直是數據持久化方面的經典問題。早些時候,這些工作需要手寫複雜的SQL或者通過存儲過程和觸發器實現。手寫複雜SQL對軟體可維護性構成了相當大的挑戰,隨著SQL字數的變多,用到的嵌套和複雜語法增加,可讀性和可維護性的難度是幾何級暴漲。因此如何在實現功能的同時控制這些S ...
  • 類型檢查和轉換:當你需要檢查對象是否為特定類型,並且希望在同一時間內將其轉換為那個類型時,模式匹配提供了一種更簡潔的方式來完成這一任務,避免了使用傳統的as和is操作符後還需要進行額外的null檢查。 複雜條件邏輯:在處理複雜的條件邏輯時,特別是涉及到多個條件和類型的情況下,使用模式匹配可以使代碼更 ...
  • 在日常開發中,我們經常需要和文件打交道,特別是桌面開發,有時候就會需要載入大批量的文件,而且可能還會存在部分文件缺失的情況,那麼如何才能快速的判斷文件是否存在呢?如果處理不當的,且文件數量比較多的時候,可能會造成卡頓等情況,進而影響程式的使用體驗。今天就以一個簡單的小例子,簡述兩種不同的判斷文件是否... ...
  • 前言 資料庫併發,數據審計和軟刪除一直是數據持久化方面的經典問題。早些時候,這些工作需要手寫複雜的SQL或者通過存儲過程和觸發器實現。手寫複雜SQL對軟體可維護性構成了相當大的挑戰,隨著SQL字數的變多,用到的嵌套和複雜語法增加,可讀性和可維護性的難度是幾何級暴漲。因此如何在實現功能的同時控制這些S ...