控制反转IOC与依赖注入DI
需要掌握
IOC控制反转--思想
-
什么是IOC?
IOC即控制反转,记住他是一种思想,目的是用来管理项目中对象的生命周期和依赖关系。
-
为什么需要IOC?
没有使用IOC之前,我们一般是通过new来实例化一个对象。但是我们使用IOC之后,我们无需手动在代码块中new一个实例来调用方法,创建这个对象的控制权将由内部转换到外部,即IOC容器,由IOC容器来统一创建和销毁。
举一个例子:在我们的代码中,A类需要B类的一个方法,那么我们可以理解为A类依赖于B类,我们一般就会直接new一个B类出来然后调用对应的方法,而采用IOC则代表,你不需要手动的new出来这个B类,这个B类的创造与销毁由容器自己决定,由它填充到A类中,A类只要被动的等待IOC容器来注入B类的资源。传统的new对象不易于修改,耦合太紧密,而采用IOC来设计的话,类与类的依赖关系清晰可见,且易于修改。
DI依赖注入--实现技术
DI是一种技术,它是用来实现IOC的,他的全称叫Dependency Injection,即依赖注入。
依赖:依赖于IOC容器提供的实例而非New的资源、依赖于抽象而非具体的实现。
注入:由IOC容器来查找A类所需要的资源并注入到A类中。A类所需要的资源不再由A类自己创建,而是由容器统一给予。
NetCore原生依赖注入
想要知道我们在Program.cs文件
中使用各种系统服务,或者注入自己写的服务是如何实现的,首先就需要了解以下几个接口和类。
IServiceCollection接口(WebApplicationBuilder):
之前我们在应用章节已经了解到WebApplicationBuilder类
有一个类型为IServiceCollection
的 services属性
,用于提供注册服务接口。
IServiceCollection接口
包含一个私有列表字段_descriptors
用于保存服务描述,继承于四个列表集合接口,用于实现对于服务描述列表的CURD服务,所以IServiceCollection接口
本质上就是服务的集合,里面存储的是我们配置和注入的服务。
IServiceCollection源码
// 指定服务描述符集合的协定。
public interface IServiceCollection : ICollection<ServiceDescriptor>, IEnumerable<ServiceDescriptor>, IEnumerable, IList<ServiceDescriptor>
{
// 用于保存服务描述
private readonly List<ServiceDescriptor> _descriptors = new List<ServiceDescriptor>();
}
3个拓展方法:用来添加服务
这3个拓展方法的原理其实很简单,无论是单例或者瞬时,都是调用的IServiceCollection接口
父类的Add()
私有方法,通过传入ServiceLifetime枚举
来控制服务的声明周期,简单来说就是往服务列表插入一条数据。
AddTransient()
:将服务注册为瞬时,在一次请求内,每使用一次服务都会创建一个新的实例;AddScoped()
:将服务注册为域,在一次请求内,创建一次实例,整个请求过程中共享该实例;AddSingleton()
:将服务注册为单例,在整个程序的生命周期内,每次请求都会使用同一个实例;
demo
// 常用的重载
// 把指定类型的单一实例服务注册到IServiceCollection
builder.Services.AddSingleton(new UserServiceImpl());
// 把指定类型的单一实例服务和对应的接口类型的实现注册到IServiceCollection
builder.Services.AddSingleton<IUserService, UserServiceImpl>();
Add()
方法源码的本质是首先把创建一个新的ServiceDescriptor对象
,然后添加到集合中。
IServiceCollection接口的Add()方法源码
private static IServiceCollection Add(
IServiceCollection collection,
Type serviceType,
Type implementationType,
ServiceLifetime lifetime)
{
// 创造一个新的ServiceDescriptor对象
var descriptor = new ServiceDescriptor(serviceType, implementationType, lifetime);
// 调用的是collection的泛型父类的add方法
collection.Add(descriptor);
return collection;
}
ServiceDescriptor类:
作用:用于描述注册的服务,描述服务类型与服务实例或者接口与生命周期的关系。
三个属性
ServiceDescriptor类源码:
public class ServiceDescriptor
{
// 服务的对应的接口类型
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
public Type? ImplementationType { get; }
// 服务的实例
public object? ImplementationInstance { get; }
// 泛型工厂
public Func<IServiceProvider, object>? ImplementationFactory { get; }
// 生命周期
public ServiceLifetime Lifetime { get; }
// 服务的类型
public Type ServiceType { get; }
}
三个构造函数
用于初始化属性
ServiceDescriptor构造函数源码
public class ServiceDescriptor
{
// 使用指定的 instance 作为 Singleton 来初始化 ServiceDescriptor 的新实例。
// 服务类型和实例 typeof(UserService),new UserService(),注入的是单例模式
// 所以我们看到的很多系统服务默认通过此构造函数来依赖注入
public ServiceDescriptor(Type serviceType, object instance);
// 用指定的 ServiceDescriptor 初始化 factory 的新实例。
// 对象类型、服务工厂、生命周期
public ServiceDescriptor(Type serviceType, Func<IServiceProvider, object> factory, ServiceLifetime lifetime);
// 用指定的 ServiceDescriptor 初始化 implementationType 的新实例。
// 服务类型、接口类型、生命周期
public ServiceDescriptor(Type serviceType, Type implementationType, ServiceLifetime lifetime);
}
Describe方法
通过这个方法来生成一个ServiceDescripts对象
,该方法有3个重载
ServiceDescripts类的Describe方法源码
public static ServiceDescriptor Describe(
Type serviceType,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type implementationType,
ServiceLifetime lifetime
)
{
return new ServiceDescriptor(serviceType, implementationType, lifetime);
}
IServiceProvider接口(WebApplication):
接口的作用是在程序运行的时候提供服务的实例。
来源:
在WebApplication类
上有一个IServiceProvider类型
的的一个属性,他是由调用WebApplicationBuilder.build()
方法的时候,会调用BuildServiceProvider()
拓展方法来把WebApplicationBuilder对象
中的services属性
生成服务描述的实体赋值给它。
它内部有一个方法:
GetService()
来获取对象实例,查看源码发现,它调用的其实是IServiceProviderEngine接口
里面的方法
IServiceProvider接口源码
public interface IServiceProvider
{
private readonly IServiceProviderEngine _engine;
// 摘要: 构造函数实例化ServiceProvider对象
internal ServiceProvider(IEnumerable<ServiceDescriptor> serviceDescriptors, ServiceProviderOptions options)
{
IServiceProviderEngineCallback callback = null;
if (options.ValidateScopes)
{
callback = this;
_callSiteValidator = new CallSiteValidator();
}
// 根据ServiceProviderMode来实例化不同的对象
switch (options.Mode)
{
// 默认
case ServiceProviderMode.Default:
if (RuntimeFeature.IsSupported("IsDynamicCodeCompiled"))
{
_engine = new DynamicServiceProviderEngine(serviceDescriptors, callback);
}
else
{
_engine = new RuntimeServiceProviderEngine(serviceDescriptors, callback);
}
break;
// 动态
case ServiceProviderMode.Dynamic:
_engine = new DynamicServiceProviderEngine(serviceDescriptors, callback);
break;
// 运行时
case ServiceProviderMode.Runtime:
_engine = new RuntimeServiceProviderEngine(serviceDescriptors, callback);
break;
// IL
case ServiceProviderMode.ILEmit:
_engine = new ILEmitServiceProviderEngine(serviceDescriptors, callback);
break;
// 表达式
case ServiceProviderMode.Expressions:
_engine = new ExpressionsServiceProviderEngine(serviceDescriptors, callback);
break;
default:
throw new NotSupportedException(nameof(options.Mode));
}
//判断是否开启编译时范围校验
if (options.ValidateOnBuild)
{
List<Exception> exceptions = null;
foreach (var serviceDescriptor in serviceDescriptors)
{
try
{
_engine.ValidateService(serviceDescriptor);
}
catch (Exception e)
{
}
}
}
}
// 摘要: 获取指定类型的服务对象。
// 参数: serviceType:一个对象,它指定要获取的服务对象的类型。
// 返回结果:服务类型类型的服务对象。-or- null 如果没有服务对象类型服务类型。
public object? GetService(Type serviceType)
{
return _engine.GetService(serviceType)
};
}
BuildServiceProvider()
这个拓展方法IServiceCollection接口
,属于用于构建ServiceProvider对象
ServiceCollectionContainerBuilderExtensions拓展类源码
public static class ServiceCollectionContainerBuilderExtensions
{// 用于创建ServiceProvider实例
public static ServiceProvider BuildServiceProvider(this IServiceCollection services, ServiceProviderOptions options)
{
if (services null)
{
throw new ArgumentNullException(nameof(services));
}
if (options null)
{
throw new ArgumentNullException(nameof(options));
}
IServiceProviderEngine engine;
#if !NetCoreAPP
engine = new DynamicServiceProviderEngine(services);
#else
if (RuntimeFeature.IsDynamicCodeCompiled)
{
engine = new DynamicServiceProviderEngine(services);
}
else
{
// Don't try to compile Expressions/IL if they are going to get interpreted
engine = new RuntimeServiceProviderEngine(services);
}
#endif
// 这个就是用来就是构建ServiceProvider的代码,我们可以看到它传入了服务、IServiceProviderEngine对象、以及ServiceProviderOptions对象,
// 根据这几个参数ServiceProviderd的构造函数回实例化不同的_engine对象()
return new ServiceProvider(services, engine, options);
}
}
IServiceProviderEngine接口
服务引擎,用于DI容器当中服务的创建
一个ServiceProvider = 一个ServiceProviderEngine = 一个ServiceProviderEngineScope
因为IServiceProviderEngine接口
同样继承了IServiceProvider接口
,所以serviceProvider.GetService()方法本质上就是调用的这个接口的
GetService()`来的创建服务。
IServiceProviderEngine接口源码
internal interface IServiceProviderEngine : IDisposable, IServiceProvider
{
IServiceScope RootScope { get; }
}
他的实现类ServiceProviderEngine
因为继承了IServiceScopeFactory接口
所以可以调用CreateScope()
方法用于创建子容器,这个就是我们声明不同的服务生命周期的时候,比如AddScope()
,就会调用这个方法来创造一个子容器。
他有很多实现类,像DynamicServiceProviderEngine、RuntimeServiceProviderEngine
等都是继承了这个抽象类来拥有这2个方法(也就是上面我们BuildServiceProvider()
得到的不同对象的父类)。这些实现类有一个参数为IServiceCollection类型
的构造函数。
ServiceProviderEngine抽象类源码
internal abstract class ServiceProviderEngine : IServiceProviderEngine, IServiceScopeFactory
{
// 构造函数是实例化服务的范围
protected ServiceProviderEngine(IEnumerable<ServiceDescriptor> serviceDescriptors, IServiceProviderEngineCallback callback)
{
Root = new ServiceProviderEngineScope(this);
}
// 获取服务实例
public object GetService(Type serviceType) => GetService(serviceType, Root);
// 服务范围
public ServiceProviderEngineScope Root { get; }
// 创造子容器
public IServiceScope CreateScope()
{
return new ServiceProviderEngineScope(this);
}
}
IServiceScope接口
通过调用ServiceProviderEngine.CreateScope()
来创建一个子容器,里面有一个IServiceProvider类型
的私有属性,所以我们调用AddTransient() AddScoped()
拓展方法注册的服务的时候,就会调用这个接口的一个私有属性也就是ServiceProvider
的GetService()
方法
IServiceProviderEngineScope接口
看接口名字其实就知道这个接口实现了IServiceScope接口,IServiceProvider接口
,以及持有一个ServiceProviderEngine类型
的属性
internal class ServiceProviderEngineScope : IServiceScope, IDisposable, IServiceProvider, IAsyncDisposable
{
// 服务范围
public ServiceProviderEngine Engine { get; }
// 服务容器
public IServiceProvider ServiceProvider { get; }
// 获取服务实例
public object GetService(Type serviceType);
}
ServiceProviderOptions类
用于配置默认 System.IServiceProvider 的各种行为的选项实现。
ServiceProviderOptions类源码
// 摘要:用于配置默认 System.IServiceProvider 的各种行为的选项实现。
public class ServiceProviderOptions
{
public ServiceProviderOptions();
// 摘要:如果为 true,则执行检查,验证DI容器中已注册的所有服务,如果检查出异常了,则抛出异常现象
public bool ValidateOnBuild { get; set; }
// 摘要:如果为 true,则执行检查,验证作用域内服务是否永远不会从根提供程序解析;否则为假。默认为 false。
public bool ValidateScopes { get; set; }
// 枚举用于提供ServiceProvider的承载的具体实例
internal ServiceProviderMode Mode { get; set; } = ServiceProviderMode.Default;
}
这些接口的关系:
一定要捋清楚这几个接口的关系,所以要多看源码!才能明白它整个构造的原理。最好自己写demo或者流程图来理解比较好
ServiceProvider对象
是通过调用ServiceCollection.BuildServiceProvider()
拓展方法构造出来的。这个方法传入2个参数,一个是代表服务描述集合的IServiceCollection
,一个是代表不同行为的ServiceProviderOption枚举
,
首先这个方法会根据枚举值来实例化不同的ServiceProviderEngine抽象类的实现类
,这个抽象类的作用是用于提供服务引擎。
这个抽象类继承了3个重要接口,
第一个是继承于IServiceProviderEngine
,这个接口持有一个类型为IServiceScope
类型的root属性,在实例化抽象类的时候在构造函数中,会把当创造一个ServiceProviderEngineScope对象
赋值,ServiceProviderEngineScope
有一个ServiceProvider类型
的属性,用于存储顶级服务,也就是我们的单例模式的一些系统服务。
第二个是IServiceProvider接口
,目的是使用它的GetService()
方法,所以一个ServiceProviderEngine对象
其实就是一个ServiceProvider对象
。第三个继承了一个IServiceScopeFactory接口
,目的调用他的方法创造CreateScope()
来创造IServiceScope子容器
。需要子容器的原因是,因为IServiceScope持有一个ServiceProvider类型属性
,是对ServiceProvider再次封装,目的是当我们请求如瞬时、作用域类型的服务,就会创造它用于保存所需服务,在需要服务的时候,从这个子容器里面拿,在请求完成后,就把整个IServiceScope
释放掉,这就是能实现瞬时、作用域的原因。而
得到具体的ServiceProviderEngine对象
后,就会把枚举参数、服务引擎、服务集合创造出一个ServiceProvider对象
。
所以创造一个ServiceProvider对象
,等于创造一个IServiceProviderEngine对象
和一个IServiceScope对象
,而创建一个IServiceScope对象
等于创造ServiceProviderEngineScope对象
,所以当我们需要单例服务的时候,则是调用ServiceProvider.GetService(XXX)
方法其实是从ServiceProvider对象
里面拿,如果我们调用注册为瞬时或者作用域的服务的时候,则是调用ServiceProvider.ServiceScoper.GetService(XXX)
方法先创造一个ServiceProviderEngineScope对象
,然后把所需要的服务实例放到给子容器,此次请求内所需就从子容器拿。
依赖注入的声明周期
Singleton(单例模式):
原理:每次请求都是从ServiceProvider属性
中去拿实例。
作用域:服务在第一次请求的时候创建,之后的每次请求都沿用此次创建的服务,直到应用程序停止才释放在程序初始化的时候。一些大部分都是与请求无关的,比如系统配置的服务,注入控制器服务等,这类方法或者实例一般都是和程序声明周期保持一致,即单例模式 如:
- 通过
StartUp.cs
构造函数注入的IHostEnvironment
、IWebHostEnvironment
、IConfiguration
; - 通过
Startup.cs
类中的Configure()
方法注入的; - 自定义中间件;
Scoped(作用域模式):
原理:是通过ServiceProvider
的CreateScope()
方法创造一个子容器,得到IServiceScoped对象
,然后调用这个子容器的ServiceProvider
的GetService()
方法来获取服务实例。
作用域:每次请求都会在当前容器创造一个作用域,即子容器,在这个请求期间每次获取服务都是从这个子容器里面去拿,当前子容器内唯一,请求结束后,子容器释放。
Transient(瞬时模式):
原理:即通过ServiceProvider.CreateScope()
创造一个IServiceScope对象
,然后调用这个子容器的ServiceProvider.GetService()
获取子容器作用域内的实例,每次获取注册为Transient类型的服务都是返回新的实例,虽然每次获取的都是新实例,但是释放的时候都是统一释放的,即当前IServiceScope.Dispose()
的时候去释放。
作用域:服务每次请求都创建一次,服务容器不会保存它,直到请求结束才释放。
在方法内手动获取服务实例
可以使用HttpContext.RequestServices()
来从依赖容器中获取服务的实例,具体请看HttpContext章节。
注入方式
-
构造函数注入:
-
属性注入:
-
方法注入:
简单来说的总结
-
WebApplicationBuilder
里面有一个IServiceCollection services {get;}
成员属性,这个接口有一个私有属性list集合用于存储ServiceScripts对象
。这个接口的目的是用于提供注册服务。继承了4个泛型接口用于对服务集合进行CURD操作,如
AddScope()、AddSingletion()
等三个拓展方法的本质其实就是调用父类的Add()
方法,通过传入ServiceLifeTime枚举
的不同值,来实例化ServiceDescripts对象
,并添加到list集合中,是对父类Add()
方法的再次封装。 -
ServiceDescripts类
就是服务描述,有4个重要的属性,服务类型、服务实例、服务对应接口、生命周期,三个构造函数和三个重载的Describe()
方法来构建ServiceDescripts对象。 -
在我们调用
builder.Build()
方法的时候,会调用IServiceCollection
的一个拓展方法BuilderServiceProvider()
方法来构造ServiceProvider对象
赋值给WebApplication
的IServiceProvider成员属性
,这是用于提供给在程序运行期间需要解析的服务。通过调用他的CreateScope()
方法创造子容器,和GetService()
方法来获取服务实例。 -
BuilderServiceProvider()
拓展方法其实就是DI容器当中服务的创建。通过传入ServiceProviderOptions对象
和IServiceProviderEngine对象
来传入ServiceProvider
的构造函数中,构造函数会根据ServiceProviderOptions枚举
的值,把服务描述集合实例化不同的IServiceProviderEngine对象
赋值给一个私有属性_engine,构造函数做完所有处理后得到的ServiceProvider对象
就是我们的容器。GetService()方法
来获取服务实例在源码上体现就是调用的私有属性engine的GetService()
。
一定要注意的事情!!!
原生只支持构造函数注入,使用UseServiceProviderFactory拓展方法来应用第三方依赖注入服务。
注册生命周期为Singleton的Service类里面的构造函数的方法即使注册为Scoped或者Transient,也会是单例,所以要注意。我们只有在什么情况下设置为Singleton,比如helper方法之类的,一成不变的,如果你用了内存也是memercache服务,那么也要谨记不要将调用了的服务注册为单例,否则,很有可能在另一个类读取缓存的数据无法读取到
AutoFac集成
为什么使用AutoFac?
因为NetCore自带的依赖注入只支持构造函数注入,每有一个自定义服务就需要手动使用拓展方法来注入,虽然也可也通过反射循环批量实现。AutoFac还额外提供方法、属性注入等多种方式。且AutoFac与C#联系紧密,可以使用lambda表达式来注册组件,速度更加快、可以批量注入。
AutoFac使用
- 首先安装nuget包Autofac和Autofac.Extensions.DependencyInjection;
- 然后需要在最小托管模型中使用UseServiceProviderFactory方法来替换掉NetCore自带的容器;
- 编写自己的注入模块类;
- 然后调用RegisterModule注入;
demo
// 引入 AutofacServiceProvider 工厂
builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());
// 注册模组
builder.Host.ConfigureContainer<ContainerBuilder>(p =>
{
// 自定义模组
p.RegisterModule<ModuleRegister>();
});
// 自定义的注入模块需要继承Autofac.Module类
public class ModuleRegister : Autofac.Module
{
private const string BLL_DLL_NAME = "BLL";
private const string SERVER_FUNTION_ENDNAME = "ServiceImpl";
/// <summary>
/// 重写Load方法进行Autofac的初始化注入
/// </summary>
/// <param name="builder"></param>
protected override void Load(ContainerBuilder builder)
{
// 批量注入服务
// 通过加载程序集的名称注入
// 默认是构造函数注入,如果需要指定属性注入则需要手动使用PropertiesAutowired方法
builder.RegisterAssemblyTypes(GetAssembly(BLL_DLL_NAME))
.Where(p => p.Name.EndsWith(SERVER_FUNTION_ENDNAME))
.PropertiesAutowired()// 属性注入
.SingleInstance(); // 每次都是同一个对象。
builder.RegisterAssemblyTypes(GetAssembly(BLL_DLL_NAME))
.Where(p => p.Name.EndsWith(SERVER_FUNTION_ENDNAME))
.AsImplementedInterfaces()// 使用接口注入
.SingleInstance(); // 每次都是同一个对象。
// 手动注入服务
// 注入为单例
builder.RegisterType<UserIdProvider>().As<IUserIdProvider>().SingleInstance();
}
/// <summary>
/// 通过程序集名称加载对应程序集到内存中
/// </summary>
/// <param name="assemblyName"></param>
/// <returns></returns>
private Assembly GetAssembly(string assemblyName)
{
return AssemblyLoadContext.
Default.
LoadFromAssemblyPath(AppContext.BaseDirectory + $"{assemblyName}.dll");
}
}
AutoFac多租户使用
应用场景:
一个系统中有4个租户,分别为A租户、B租户、C租户、D租户,其中关于ITestService接口,原本它只有一个实现类TestServiceImpl,4个租户都正常使用这个实现类,直到某一天,A租户这个接口需要特殊定制化、而B租户也是同样需要特殊处理,按照正常操作,我们应该继承ITestService分别实现ATestServiceImpl和BTestServiceImpl,那么如何做到在运行过程中根据请求者的租户身份,动态获取对应的实例呢?比如如果是A请求那么我们就给ATestServiceImpl,但是如果是C请求那么我们就要给TestServiceImpl。
流程
- 安装nuget包Autofac.AspNetCore.Multitenant(7.0)
- 在颁发Token的时候,请携带对应的代表租户信息的唯一Code信息。
- 替换掉原始的依赖容器
- 配置对应的服务
- 解析
demo
// AutoFac替换原始容器
builder.Services.AddAutofacMultitenantRequestServices();
// 配置多租户业务支持服务
builder.Host.UseServiceProviderFactory(new AutofacMultitenantServiceProviderFactory(MultitenantServiceRegister.ConfigureMultitenantContainer));
// AutoFac服务注入
builder.Host.ConfigureContainer<ContainerBuilder>(p =>
{
p.RegisterModule<ServiceRegister>();
});
/// <summary>
/// 多租户特殊业务
/// </summary>
public static class MultitenantServiceRegister
{
/// <summary>
/// 自定义租户识别策略
/// </summary>
/// <param name="container"></param>
/// <returns></returns>
/// <remarks>当Service需要需要特殊化的时候,在此方法内注入</remarks>
public static MultitenantContainer ConfigureMultitenantContainer(IContainer container)
{
// 获取解析策略
MyTenantIdentificationStrategy tenantIdentifier = new(container.Resolve<IHttpContextAccessor>());
MultitenantContainer mtc = new(tenantIdentifier, container);
mtc.ConfigureTenant("A", b => b.RegisterType<ATestServiceImpl>().As<ITestService>().SingleInstance().InstancePerTenant().WithAttributeFiltering());
mtc.ConfigureTenant("B", b => b.RegisterType<BTestServiceImpl>().As<ITestService>().SingleInstance().InstancePerTenant().WithAttributeFiltering());
// 注意:默认的实现需要放在最后一行,实现类不需要加任何的前缀
// 为什么这里的要写Default?原因是在根据策略注入资源的时候,如果在依赖注入容器内租户Code找不到对应的实例,就会默认拿这个实现。
// 比如A能找到,那就拿A对应的实例,但是你看我们在上方没有注入C,如果C请求过来,他在依赖注入容器里面找不到,就会拿这个。
mtc.ConfigureTenant("Default", b => b.RegisterType<TestServiceImpl>().As<ITestService>().SingleInstance().InstancePerTenant().WithAttributeFiltering());
return mtc;
}
}
/// <summary>
/// 策略获取租户ID
/// </summary>
public class MyTenantIdentificationStrategy : ITenantIdentificationStrategy
{
private IHttpContextAccessor httpContextAccessor;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="httpContextAccessor"></param>
public MyTenantIdentificationStrategy(IHttpContextAccessor httpContextAccessor)
{
this.httpContextAccessor = httpContextAccessor;
}
/// <summary>
/// 尝试从当前上下文获取标识的租户。
/// </summary>
/// <param name="SchemeName"></param>
/// <returns></returns>
public bool TryIdentifyTenant(out object schemeName)
{
SchemeName = null;
try
{
if (httpContextAccessor.HttpContext != null)
{
// 从Token中获取携带的租户Code信息
string token = httpContextAccessor.HttpContext.Request.Headers[ClaimsUserConsts.HTTP_Token_Head].ToString();
var claims = TokenTool.GetClaims(token.Substring("bearer".Length).Trim()).ToList();
SchemeName = claims.FirstOrDefault(p => p.Type == ClaimsUserConsts.SCHEME_NAME).Value;
}
}
catch (Exception)
{
}
return SchemeName != null;
}
}
拓展
这里只介绍平常用的,更多的请看官网。
生命周期:
如果不指定注册的服务生命周期,则默认是瞬时。
.SingleInstance()
:把服务注册为单例;.InstancePerLifetimeScope()
:注册为作用域;.InstancePerDependency()
:注册为瞬时;
常用注册方式:
builder是指ContainerBuilder,继承Autofac.Module重写Load方法的参数。
builder.RegisterAssemblyTypes(“类库名称.dll”)
:通过程序集名称来注册,可以使用如 .PublicOnly()只注册公共方法;builder.RegisterType<Service>().As<IService>()
::通过接口注册,若要通过接口启用代理,组件必须仅通过接口提供服务,即构造函数中的参数以及接受的私有属性均为接口;builder.RegisterType<typeof(Service)>().As<typeof(IService)>()
:或者类型和接口注册;builder.RegisterInstance(new Service())
:通过实例注册;
高级注册方式
-
多态注入:NetCore虽然也可以实现一个接口多个服务注入,但是默认构造函数注入的是最后一个注册服务的实例,所以需要手动从ServiceProvider中获取,而不能动态解析获取。
AutoFac使用Keyed枚举、Named字符串来区分(不太推荐使用字符串,不好管理),这会导致将容器用作服务定位器,这是不鼓励的。需要对使用的类注册服务WithAttributeFiltering()。
// 枚举服务,如果需要获取指定的服务则使用[KeyFilter("名称" | 枚举)] builder.RegisterType<AServiceImpl>().Keyed<IService>(VersionType.Test); builder.RegisterType<BServiceImpl>().Named<IService>("B"); // 因为在Test类中使用了多态注入,所以使用KeyFilterAttribute特性来区分到底是哪个实例,而且特地告诉autofac这个类使用了这个特性,以便容器知道要查找它 builder.RegisterType<ServiceCatProxyImpl>().WithAttributeFiltering()
注意:
如果你是想要在Controller层直接使用KeyFilter去判断获取具体的服务,是不可以的,因为controller的创建不是由ioc容器完成的,所以没办法这样做,可以新增一个代理类/装饰类来做这样的处理哦!
如果你想用这个方法来做不同租户不同业务处理,不太推荐使用这种方式获取服务实例,Auto的租户处理程序会更好。
// 把这个Test作为代理类提供给控制层使用 public class ServiceCatProxyImpl { private readonly IService _aService; private readonly IService _bService; public ServiceCatProxyImpl( [KeyFilter(VersionType.Test)] IService AService, [KeyFilter("B")] IService BService ) { _aService = AService; _bService = BService; } public string ASay() => _aService.Say(); public string BSay() => _bService.Say(); }
特殊判断和处理:
.OnlyIf(判断条件)
:增加注册的时候的条件,如果满足条件才允许注册;
.IfNotRegistered(typeof(IService))
:如果么有人注册,则默认使用提供的接口注册;
动态AOP:
看官网怎么写类型拦截器 — Autofac 4.0 文档 (autofac-.readthedocs.io)
多租户应用程序:
多租户应用程序 — Autofac 4.0 文档 (autofac-.readthedocs.io)
可以用来实现不同的租户同一个业务实现不同的逻辑。也可也使用多态注入来实现这个效果咯,但是效果可能没有这个好,因为如果有很多租户的话,key就要写很多。
注意:
- AutoFac对于循环依赖支持比较差,所以尽量不要在构造函数中出现循环依赖的问题,如果一定有或者存在,则自行看官网关于循环依赖的章节