前言 從.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
對象保存了到了ServiceTypeSelector
的Types
屬性中供下一步選擇。
除了提供程式集的方式外還可以直接提供類型的方式比如
創建介面和實現
public interface IForTypeService
{
}
public class ForTypeService : IForTypeService
{
}
builder.Services.Scan(typeSourceSelector =>
{
typeSourceSelector.FromTypes(typeof(ForTypeService));
});
這種方式提供類型內部會調用AddClass()
方法把符合條件的參數保存到ServiceTypeSelector
第三步確定註冊策略
在AddClass
之後可以調用UsingRegistrationStrategy()
配置註冊策略是 Append
,Skip
,Throw
,Replace
下麵是各個模式的詳細解釋
- 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>();
AsImplementationType
指定 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(IEnumerableImplementationType
指定多個 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
對象裡面包含了程式集的所有類型集合 - 調用
IImplementationTypeSelector
的AddClasses
方法獲取IServiceTypeSelector
對象,AddClass
這裡面可以根據條件選擇 過濾一些不需要的類型 - 調用
UsingRegistrationStrategy
確定依賴註入的策略 是覆蓋 還是跳過亦或是拋出異常 預設Append
追加註入的方式 - 配置註冊的場景 比如是
AsImplementedInterfaces
還是AsSelf
等 - 選擇生命周期 預設
Transient
藉助ServiceDescriptorAttribute
更簡單,生命周期和ServiceType
都是在Attribute
指定好的只需要確定選擇程式集,調用UsingRegistrationStrategy
配置依賴註入的策略然後UsingAttributes()
即可
最後
本文從Scrutor
的使用流程剖析了依賴註入批量註冊的流程,更詳細的教程可以參考Github 官方倉庫。在開發過程中看到很多項目還有一個個手動註入的,也有自己寫 Interface
或者是Attribute
反射註入的,支持的場景都十分有限,Scrutor
的出現就是為了避免我們在項目中不停地造輪子,達到開箱即用的目的。
本文完整示例源代碼
本文來自博客園,作者:董瑞鵬,轉載請註明原文鏈接:https://www.cnblogs.com/ruipeng/p/18081965