asp.net core提供了依赖注入的支持,我们可以在Startup.ConfigureServices方法中注册系统所需要的服务映射关系,如services.TryAddScoped<TInterface, TImp>(),通过这样的方式可以完成一个服务注册,并在代码中可以通过注入的方式获取到TImp的实例。如果系统里需要的服务比较多,一个一个这样去注册,显然不是特别好的方法,如何简化注册的过程呢?我们可以制定一个规则,比如给服务定义增加特性描述信息,标识当前是一个需要注册的服务,然后通过反射解析方式获取到信息,并完成服务注册。或者把服务注册信息写到文件中,解析文件完成相同的过程。
首先我们先定义一个能够描述服务注册信息的一个类,定义如下:
public class ServiceRegisteDescriptor
{
public Type ServiceType { get; set; }//服务类型,可以是接口,也可以是一个类
public ServiceLifetime LifeTime { get; set; }//服务的生命周期类型
public bool AllowMultipleImp { get; set; }//该服务器是否允许多个实现
public Type Imp { get; set; }//指定特定的实现
public Type[] GenericParameterTypes { get; set; }//如果该服务是一个泛型类型,可以指定注册的泛型参数具体类型是什么
}
描述信息类定义好了,下面我们需要提供获取ServiceRegisteDescriptor集合的方法,我们这里先定义一个IServiceRegisteDescriptorCollectionProvider接口,定义如下:
public interface IServiceRegisteDescriptorCollectionProvider
{
ServiceRegisteDescriptorCollection ServiceRegisteDescriptors { get; }
}
这个接口包含一个ServiceRegisteDescriptors属性,它是一个ServiceRegisteDescriptorCollection类型,从这个名字上可以看出,ServiceRegisteDescriptorCollection表示的就是ServiceRegisteDescriptor集合,具体定义如下:
public class ServiceRegisteDescriptorCollection
{
public ServiceRegisteDescriptorCollection(IReadOnlyList<ServiceRegisteDescriptor> items)
{
Items = items ?? throw new ArgumentNullException(nameof(items));
}
public IReadOnlyList<ServiceRegisteDescriptor> Items { get; private set; }
}
接口定义好了,那如何去实现它?我们前面提到,服务注册信息源可以是程序集反射信息,也可以是文件内容,甚至可以是数据库中的数据等等,为了满足信息源多样性的支持以及方便扩展的需求,我们抽象一个IServiceRegisteDescriptorProvider接口,定义如下:
public interface IServiceRegisteDescriptorProvider
{
/// <summary>
/// 排序号,建议自定义的Provider,order从1开始,系统默认提供的Provider是0
/// </summary>
int Order { get; }
/// <summary>
/// 从特定目标获取服务注册描述信息,放到ServiceRegisteDescriptorProviderContext中
/// </summary>
/// <param name="context"></param>
void OnProvidersExecuting(ServiceRegisteDescriptorProviderContext context);
/// <summary>
/// 在该方法中可以对收集好的ServiceRegisteDescriptor集合进行修改,比如删除,替换等
/// </summary>
/// <param name="context"></param>
void OnProvidersExecuted(ServiceRegisteDescriptorProviderContext context);
}
这个接口的作用就是从特定目标获取信息,并转换成ServiceRegisteDescritor信息,放到我们一个ServiceRegisteDescriptorProviderContext中进行汇总,并且可以在OnProvidersExecuted中对结果进行修改。我们下面先实现一个从程序集中获取信息。从程序集中获取信息可以采用反射的方式,我们可以定义一个特性,反射的时候获取包含该特性的类型定义列表,然后根据特性提供的数据进行类型分析,最后得到一个服务映射列表。
实现方式说完了,下面就是具体实现了,那就先定义一个特性,定义如下:
[AttributeUsage(AttributeTargets.Class|AttributeTargets.Interface,AllowMultiple =true,Inherited =false)]
public class ServiceRegisteDescriptorAttribute:Attribute
{
public ServiceRegisteDescriptorAttribute(ServiceLifetime lifetime)
{
LifeTime = lifetime;
}
public ServiceLifetime LifeTime { get; }
public bool AllowMultipleImp { get; set; }
public Type Imp { get; set; }
public Type GenericType { get; set; }
}
这个特性的定义跟ServiceRegisteDescriptor很类似,唯一不包含ServiceType属性,其他属性含义跟ServiceRegisteDescriptor一致。这个特性可以应用到类,接口上。有了特性定义,我们可以在需要完成注册的类型定义上增加该特性,比如:
[ServiceRegisteDescriptor(Microsoft.Extensions.DependencyInjection.ServiceLifetime.Singleton)]
public interface IServiceTest
{
void TestM();
}
这表示IServiceTest是一个需要注册的服务,我们从当前程序所包含的程序集中,获取到某个实现了该接口的类,建议一个服务映射关系。下面就是IServiceRegisteDescriptorProvider具体实现了。定义一个类DefaultServiceRegisterDescriptorProvider,实现IServiceRegisteDescriptorProvider接口,如下:
public class DefaultServiceRegisterDescriptorProvider : IServiceRegisteDescriptorProvider
{}
核心是OnProvidersExecuting方法,在这个方法中实现我们刚才说的反射解析过程,具体实现代码:
public void OnProvidersExecuting(ServiceRegisteDescriptorProviderContext context)
{
//获取当前运行程序所有的程序集集合,一会再说AssemblyDisconvery实现
Assembly[] assemblys = AssemblyDiscovery.Discovery();
//得到所有包含ServiceRegisteDescriptorAttribute特性的类型集合
IEnumerable<Type> types = assemblys.SelectMany(m => m.GetTypes().Where(t => t.GetTypeInfo().GetCustomAttributes().Any(a => a.GetType() == typeof(ServiceRegisteDescriptorAttribute)))).ToList();
foreach (var type in types)
{
TypeInfo typeInfo = type.GetTypeInfo();
//获取当前类型的ServiceRegisteDescriptorAttribute特性对象
ServiceRegisteDescriptorAttribute attr = type.GetTypeInfo().GetCustomAttributes().FirstOrDefault(m => m.GetType() == typeof(ServiceRegisteDescriptorAttribute)) as ServiceRegisteDescriptorAttribute;
//如果当前类型是一个泛型类型,必须执行GenericType
if (typeInfo.IsGenericTypeDefinition && attr.GenericType == null)
{
throw new NotSupportedException(nameof(attr));
}
//下面的过程是获取当前服务的实现类集合
Type[] impTypes = null;
if ((typeInfo.IsInterface || typeInfo.IsAbstract) && attr.Imp == null)
{
//从程序集中获取所有实现了该服务端类
if (typeInfo.IsGenericTypeDefinition && typeInfo.IsInterface)
{
impTypes = assemblys.SelectMany(m => m.GetTypes().Where(t => t.GetTypeInfo().IsClass && !t.GetTypeInfo().IsAbstract && t.GetTypeInfo().GetInterfaces().Any(i => i.GetTypeInfo().IsGenericType && i.GetGenericTypeDefinition() == type))).ToArray();
}
else
{
if (typeInfo.IsInterface)
{
impTypes = assemblys.SelectMany(m => m.GetTypes().Where(t => t.GetTypeInfo().IsClass && !t.GetTypeInfo().IsAbstract && t.GetTypeInfo().GetInterfaces().Any(i => i == type))).ToArray();
}
else
{
impTypes = assemblys.SelectMany(m => m.GetTypes().Where(t => t.GetTypeInfo().IsClass && !t.GetTypeInfo().IsAbstract && t.GetTypeInfo().IsSubclassOf(type))).ToArray();
}
}
}
else if (attr.Imp!=null)
{
impTypes = new Type[1] { attr.Imp };
}
else
{
impTypes = new Type[1] { type };
}
//建立映射关系
foreach (var imp in impTypes)
{
ServiceRegisteDescriptor d=new ServiceRegisteDescriptor
{
AllowMultipleImp = attr.AllowMultipleImp,
Imp = imp,
ServiceType = type,
LifeTime = attr.LifeTime
};
//如果是泛型类型,获取所有类型为attr.GenericType的类型集合,包含所有子孙类型
if (typeInfo.IsGenericType)
{
d.GenericParameterTypes = assemblys.SelectMany(m => m.GetTypes().Where(t => !t.GetTypeInfo().IsAbstract && (t.GetTypeInfo().IsSubclassOf(attr.GenericType) || t == attr.GenericType))).ToArray();
}
context.Results.Add(d);
}
}
}
通过上面的方式,我们就能从程序集中获取所有的服务定义信息。前面使用了一个AssemblyDisconery获取程序集集合,它的实现如下:
public class AssemblyDiscovery
{
public static Assembly[] Discovery()
{
return DependencyContext.Default.RuntimeLibraries.SelectMany(l => l.GetDefaultAssemblyNames(DependencyContext.Default)).Select(Assembly.Load).ToArray();
}
}
AssemblyDiscovery使用DependencyContext来获取程序集集合。
上面是实现了从程序集中获取服务注册描述信息,如果源是文件呢?我们可以以xml或者json格式保存ServiceRegisteDescriptor配置集合,然后从文件中读取内容并按照特定格式解析,按照上面的分析过程,最终可以得到我们所需要的ServiceRegisteDescriptorCollection,如果源是数据库也一样,这些实现代码就不再提供了。
有了IServiceRegisteDescriptorProvider,我们可以调用所有IServiceRegisteDescriptorProvider对象的方法,完成信息收集,下面我们就实现IServiceRegisteDescriptorCollectionProvider,代码如下:
public class ServiceRegisteDescriptorCollectionProvider : IServiceRegisteDescriptorCollectionProvider
{
private readonly IServiceRegisteDescriptorProvider[] _serviceRegisteDescriptorProviders;
private ServiceRegisteDescriptorCollection _collection;
public ServiceRegisteDescriptorCollectionProvider(
IEnumerable<IServiceRegisteDescriptorProvider> serviceRegisteDescriptorProviders)
{
_serviceRegisteDescriptorProviders = serviceRegisteDescriptorProviders
.OrderBy(p => p.Order)
.ToArray();
}
private void UpdateCollection()
{
var context = new ServiceRegisteDescriptorProviderContext();
//循环所有的Provider完成解析
for (var i = 0; i < _serviceRegisteDescriptorProviders.Length; i++)
{
_serviceRegisteDescriptorProviders[i].OnProvidersExecuting(context);
}
//这里完成ServiceRegisteDescriptor集合的修改
for (var i = _serviceRegisteDescriptorProviders.Length - 1; i >= 0; i--)
{
_serviceRegisteDescriptorProviders[i].OnProvidersExecuted(context);
}
//生成集合
_collection = new ServiceRegisteDescriptorCollection(
new ReadOnlyCollection<ServiceRegisteDescriptor>(context.Results));
}
public ServiceRegisteDescriptorCollection ServiceRegisteDescriptors
{
get
{
if (_collection == null)
{
UpdateCollection();
}
return _collection;
}
}
完成ServiceRegisteDescriptorCollection收集之后,下面就是注册到ServiceCollection中了,我们直接扩展IServiceCollection对象,代码如下:
public static class ServiceScanServiceCollectionExtensions
{
public static IServiceCollection AddScanServices(this IServiceCollection services)
{
return AddScanServices(services,null);
}
public static IServiceCollection AddScanServices(this IServiceCollection services,Action<ServiceScanOptions> options)
{
//提供自定义IServiceRegisteDescriptorProvider扩展的配置入口
ServiceScanOptions option = new ServiceScanOptions();
option.DescriptorProviderTypes.Add(new DefaultServiceRegisterDescriptorProvider());
options?.Invoke(option);
//获取集合
IServiceRegisteDescriptorCollectionProvider provider = new ServiceRegisteDescriptorCollectionProvider(option.DescriptorProviderTypes);
ServiceRegisteDescriptorCollection collection = provider.ServiceRegisteDescriptors;
foreach (var item in collection.Items)
{
//完成注册
ServiceRegister.Registe(services,item);
}
return services;
}
我们一个问题一个问题介绍,首先是ServiceScanOptions,这个是为了自定义IServiceRegisteDescriptorProvider扩展配置提供的类,代码如下:
public class ServiceScanOptions
{
public IList<IServiceRegisteDescriptorProvider> DescriptorProviderTypes { get; } = new List<IServiceRegisteDescriptorProvider>();
}
通过上面的代码我们可以看出,我们通过调用IServiceCollectoin.AddScanServices方法时,利用Action<ServiceScanOptions>委托,可以把自定义的扩展加入到ServiceScanOptions.DescriptorProviderTypes中,系统默认提供的是DefaultServiceRegisterDescriptorProvider。
ServiceRegister.Registe方法是根据ServiceRegisteDescriptor把服务注册到IServiceCollectoin中,实现如下:
public class ServiceRegister
{
private static void RegisteItem(IServiceCollection services,ServiceLifetime lifeTime,Type serviceType,Type impType,bool allowMultipleImp)
{
ServiceDescriptor serviceDescriptor = null;
switch (lifeTime)
{
case ServiceLifetime.Singleton:
serviceDescriptor = ServiceDescriptor.Singleton(serviceType, impType);
break;
case ServiceLifetime.Scoped:
serviceDescriptor = ServiceDescriptor.Scoped(serviceType, impType);
break;
case ServiceLifetime.Transient:
serviceDescriptor = ServiceDescriptor.Transient(serviceType, impType);
break;
}
if (allowMultipleImp)
{
services.TryAddEnumerable(serviceDescriptor);
}
else
{
services.TryAdd(serviceDescriptor);
}
}
public static void Registe(IServiceCollection services,ServiceRegisteDescriptor descriptor)
{
//判断是否是泛型接口
if (descriptor.ServiceType.GetTypeInfo().IsGenericType)
{
if (descriptor.GenericParameterTypes == null)
{
throw new NullReferenceException(nameof(descriptor.GenericParameterTypes));
}
if (!descriptor.Imp.GetTypeInfo().IsGenericType)
{
throw new NotSupportedException(nameof(descriptor.Imp));
}
//根据泛型类型注册服务
foreach (var item in descriptor.GenericParameterTypes)
{
RegisteItem(services,descriptor.LifeTime, descriptor.ServiceType.MakeGenericType(item), descriptor.Imp.MakeGenericType(item), descriptor.AllowMultipleImp);
}
}
else
{
RegisteItem(services, descriptor.LifeTime, descriptor.ServiceType, descriptor.Imp,descriptor.AllowMultipleImp);
}
}
}
这里重点说下泛型的规则,比如定义了一个泛型接口如下:
[ServiceRegisteDescriptor(Microsoft.Extensions.DependencyInjection.ServiceLifetime.Scoped,AllowMultipleImp =true,GenericType =typeof(EntityTest))]
public interface IGenericTest<T>
{
}
ServiceRegisteDescriptor表示当前是一个需要注册的服务,然后泛型参数类型为EntityTest,意思就是说所有EntityTest类型(包括所有子孙类型)都要注册一个IGnericTest<>服务,如下映射关系:
IGenericTest<EntityTest> --> GenericTest<EntityTest>
IGenericTest<EntityTest1> --> GenericTest<EntityTest1> EntityTest1派生自EntityTest
到这里就介绍完了,具体使用方法:
1. 引入DepencencyInjectionScan库:Install-Package Microsoft.Extensions.DependencyInjection.Scan
2. 使用IServiceCollection.AddScanServices()完成服务注册
3. 自定义IServiceRegisteDescriptorProvider注册方法:IServiceCollection.AddScanServices(options=>{options.DescriptorProviderTypes.Add(对象);});
完整的实现代码也放到了github上,地址是:https://github.com/dxp909/DependencyInjectionScan.git,大家可以下载查看