ToB企服应用市场:ToB评测及商务社交产业平台
标题:
.Net依赖注入神器Scrutor(上)
[打印本页]
作者:
兜兜零元
时间:
2024-5-14 16:20
标题:
.Net依赖注入神器Scrutor(上)
前言
从.Net Core 开始,.Net 平台内置了一个轻量,易用的 IOC 的框架,供我们在应用程序中使用,社区内还有许多强盛的第三方的依赖注入框架如:
Autofac
DryIOC
Grace
LightInject
Lamar
Stashbox
Simple Injector
内置的依赖注入容器根本可以满意大多数应用的需求,除非你需要的特定功能不受它支持否则不建议使用第三方的容器。
我们今天介绍的主角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委托参数。
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()
As(params Type[] types)
As(IEnumerable types)
AsImplementedInterfaces()
AsImplementedInterfaces(Func predicate)
AsSelfWithInterfaces()
AsSelfWithInterfaces(Func predicate)
AsMatchingInterface()
AsMatchingInterface(Action? action)
As(Func 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();
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 对象内里包罗了程序集的全部范例集合
调用 IImplementationTypeSelector 的 AddClasses 方法获取 IServiceTypeSelector 对象,AddClass 这内里可以根据条件选择 过滤一些不需要的范例
调用UsingRegistrationStrategy确定依赖注入的策略 是覆盖 还是跳过亦或是抛出非常 默认 Append 追加注入的方式
设置注册的场景 好比是 AsImplementedInterfaces 还是 AsSelf等
选择生命周期 默认 Transient
借助ServiceDescriptorAttribute更简单,生命周期和ServiceType都是在Attribute指定好的只需要确定选择程序集,调用UsingRegistrationStrategy设置依赖注入的策略然后UsingAttributes()即可
最后
本文从Scrutor的使用流程剖析了依赖注入批量注册的流程,更详细的教程可以参考
Github 官方仓库
。在开发过程中看到许多项目还有一个个手动注入的,也有本身写 Interface或者是Attribute反射注入的,支持的场景都十分有限,Scrutor的出现就是为了避免我们在项目中不绝地造轮子,达到开箱即用的目的。
本文
完备示例源代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/)
Powered by Discuz! X3.4