大家好,在DotNetCore开发中我们会用到DI也就是注入 一般在Program.cs文件中使用IServiceCollection去添加要注入的对象,但在实际开发中我们不可能去频繁的修改Program.cs文件并且把所有业务相关的注入对象都暴露在Program中也是存在开发风险的,因此有必要封装一个自动注入的服务,在Program中仅注册该定制服务就可实现上述需求是最好的方式。本文主要介绍一种符合该定制服务的功能实现。
大概分为以下几部分
1.创建一个自动DI的接口:IModuleInitializer
2.创建实现自动DI接口的实现类:ProjectModuleInitializer
3.给Microsoft.Extensions.DependencyInjection类添加扩展方法(Program中利用IServiceCollection注册相关注入对象用)
4.其他帮助类
整体思路:声明一个接口,在Microsoft.Extensions.DependencyInjection空间下追加扩展类和扩展方法,让该空间下的IServiceCollection能够引入到该扩展方法中,同时该接口在扩展方法中被订阅,这样每个项目中都可以自己写一些实现了该接口的类,在其中注册自己需要的服务,也就是相当于把自动注册这部分封装起来了
下面我们展示代码
1.自动DI注入接口:IModuleInitializer
using Microsoft.Extensions.DependencyInjection;
namespace WebApi.Core.ProjectDependencyInjection
{
public interface IModuleInitializer
{
/// <summary>
/// 所有项目中的实现了IModuleInitializer接口都会被调用,请在Initialize中编写注册本模块需要的服务。
/// 一个项目中可以放多个实现了IModuleInitializer的类。
/// 不过为了集中管理,还是建议一个项目中只放一个实现了IModuleInitializer的类
/// </summary>
public interface IModuleInitializer
{
public void Initialize(IServiceCollection services);
}
}
}
注意该接口的引用命名空间
using Microsoft.Extensions.DependencyInjection
此处很重要
2.给Microsoft.Extensions.DependencyInjection类添加扩展方法:
创建一个静态类ModuleInitializerExtensions和静态方法RunModuleInitializers
using System.Reflection;
using static WebApi.Core.ProjectDependencyInjection.IModuleInitializer;
namespace Microsoft.Extensions.DependencyInjection
{
public static class ModuleInitializerExtensions
{
/// <summary>
/// 每个项目中都可以自己写一些实现了IModuleInitializer接口的类,在其中注册自己需要的服务,这样避免所有内容到入口项目中注册
/// </summary>
/// <param name="services">IServiceCollection</param>
/// <param name="assemblies">获取工程下所有的项目文件</param>
public static IServiceCollection RunModuleInitializers(this IServiceCollection services,
IEnumerable<Assembly> assemblies)
{
//遍历项目文件
foreach (var asm in assemblies)
{
//获取当前文件类型
Type[] types = asm.GetTypes();
//筛选并获取需要的类型(即:实现了IModuleInitializer接口的类)
//筛选条件:非抽象类&&且实现了IModuleInitializer接口
var moduleInitializerTypes = types.Where(t => !t.IsAbstract && typeof(IModuleInitializer).IsAssignableFrom(t));
foreach (var implType in moduleInitializerTypes)
{
var initializer = (IModuleInitializer?)Activator.CreateInstance(implType);
if (initializer == null)
{
throw new ApplicationException($"Cannot create ${implType}");
}
//通过接口实现需要批量注入的指定的业务对象
initializer.Initialize(services);
}
}
return services;
}
}
}
注意命名空间和相关引用
3.创建获取解决方案下的所有项目文件获取方法
所需引用:
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics;
using System.Reflection.PortableExecutable;
using System.Reflection;
using System.Reflection.Metadata;
public static class ReflectionHelper
{
/// <summary>
/// 据产品名称获取程序集
/// </summary>
/// <param name="productName"></param>
/// <returns></returns>
public static IEnumerable<Assembly> GetAssembliesByProductName(string productName)
{
var asms = AppDomain.CurrentDomain.GetAssemblies();
foreach (var asm in asms)
{
var asmCompanyAttr = asm.GetCustomAttribute<AssemblyProductAttribute>();
if (asmCompanyAttr != null && asmCompanyAttr.Product == productName)
{
yield return asm;
}
}
}
//是否是微软等的官方Assembly
private static bool IsSystemAssembly(Assembly asm)
{
var asmCompanyAttr = asm.GetCustomAttribute<AssemblyCompanyAttribute>();
if (asmCompanyAttr == null)
{
return false;
}
else
{
string companyName = asmCompanyAttr.Company;
return companyName.Contains("Microsoft");
}
}
private static bool IsSystemAssembly(string asmPath)
{
var moduleDef = AsmResolver.DotNet.ModuleDefinition.FromFile(asmPath);
var assembly = moduleDef.Assembly;
if (assembly == null)
{
return false;
}
var asmCompanyAttr = assembly.CustomAttributes.FirstOrDefault(c => c.Constructor?.DeclaringType?.FullName == typeof(AssemblyCompanyAttribute).FullName);
if (asmCompanyAttr == null)
{
return false;
}
var companyName = ((AsmResolver.Utf8String?)asmCompanyAttr.Signature?.FixedArguments[0]?.Element)?.Value;
if (companyName == null)
{
return false;
}
return companyName.Contains("Microsoft");
}
/// <summary>
/// 判断file这个文件是否是程序集
/// </summary>
/// <param name="file"></param>
/// <returns></returns>
private static bool IsManagedAssembly(string file)
{
using var fs = File.OpenRead(file);
using PEReader peReader = new PEReader(fs);
return peReader.HasMetadata && peReader.GetMetadataReader().IsAssembly;
}
private static Assembly? TryLoadAssembly(string asmPath)
{
AssemblyName asmName = AssemblyName.GetAssemblyName(asmPath);
Assembly? asm = null;
try
{
asm = Assembly.Load(asmName);
}
catch (BadImageFormatException ex)
{
Debug.WriteLine(ex);
}
catch (FileLoadException ex)
{
Debug.WriteLine(ex);
}
if (asm == null)
{
try
{
asm = Assembly.LoadFile(asmPath);
}
catch (BadImageFormatException ex)
{
Debug.WriteLine(ex);
}
catch (FileLoadException ex)
{
Debug.WriteLine(ex);
}
}
return asm;
}
/// <summary>
/// loop through all assemblies
/// </summary>
/// <returns></returns>
public static IEnumerable<Assembly> GetAllReferencedAssemblies(bool skipSystemAssemblies = true)
{
Assembly? rootAssembly = Assembly.GetEntryAssembly();
if (rootAssembly == null)
{
rootAssembly = Assembly.GetCallingAssembly();
}
var returnAssemblies = new HashSet<Assembly>(new AssemblyEquality());
var loadedAssemblies = new HashSet<string>();
var assembliesToCheck = new Queue<Assembly>();
assembliesToCheck.Enqueue(rootAssembly);
if (skipSystemAssemblies && IsSystemAssembly(rootAssembly) != false)
{
if (IsValid(rootAssembly))
{
returnAssemblies.Add(rootAssembly);
}
}
while (assembliesToCheck.Any())
{
var assemblyToCheck = assembliesToCheck.Dequeue();
foreach (var reference in assemblyToCheck.GetReferencedAssemblies())
{
if (!loadedAssemblies.Contains(reference.FullName))
{
var assembly = Assembly.Load(reference);
if (skipSystemAssemblies && IsSystemAssembly(assembly))
{
continue;
}
assembliesToCheck.Enqueue(assembly);
loadedAssemblies.Add(reference.FullName);
if (IsValid(assembly))
{
returnAssemblies.Add(assembly);
}
}
}
}
var asmsInBaseDir = Directory.EnumerateFiles(AppContext.BaseDirectory,
"*.dll", new EnumerationOptions { RecurseSubdirectories = true });
foreach (var asmPath in asmsInBaseDir)
{
if (!IsManagedAssembly(asmPath))
{
continue;
}
AssemblyName asmName = AssemblyName.GetAssemblyName(asmPath);
//如果程序集已经加载过了就不再加载
if (returnAssemblies.Any(x => AssemblyName.ReferenceMatchesDefinition(x.GetName(), asmName)))
{
continue;
}
if (skipSystemAssemblies && IsSystemAssembly(asmPath))
{
continue;
}
Assembly? asm = TryLoadAssembly(asmPath);
if (asm == null)
{
continue;
}
//Assembly asm = Assembly.Load(asmName);
if (!IsValid(asm))
{
continue;
}
if (skipSystemAssemblies && IsSystemAssembly(asm))
{
continue;
}
returnAssemblies.Add(asm);
}
return returnAssemblies.ToArray();
}
private static bool IsValid(Assembly asm)
{
try
{
asm.GetTypes();
asm.DefinedTypes.ToList();
return true;
}
catch (ReflectionTypeLoadException)
{
return false;
}
}
class AssemblyEquality : EqualityComparer<Assembly>
{
public override bool Equals(Assembly? x, Assembly? y)
{
if (x == null && y == null) return true;
if (x == null || y == null) return false;
return AssemblyName.ReferenceMatchesDefinition(x.GetName(), y.GetName());
}
public override int GetHashCode([DisallowNull] Assembly obj)
{
return obj.GetName().FullName.GetHashCode();
}
}
}
4.在Progarm.cs中注册封装好的服务
public class Program
{
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
//应用各个工程自动DI
var asmr = ReflectionHelper.GetAllReferencedAssemblies();
builder.Services.RunModuleInitializers(asmr);
var app = builder.Build();
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
//其他服务注册
...
app.MapControllers();
app.Run();
}
}
5.接口实现并进行业务对象注入
注意引用:
using static WebApi.Core.ProjectDependencyInjection.IModuleInitializer;
public class ProjectModuleInitializer : IModuleInitializer
{
public void Initialize(IServiceCollection services)
{
//DBContext
services.AddSingleton(typeof(DBContext<>));
//services.AddSingleton(typeof(DBContextFactory<>));
//Base
services.AddScoped(typeof(IBaseRepository<>), typeof(BaseRepositoryImpl<>));
//Book
services.AddScoped<IBookRepository, BookRepositoryImpl>();
services.AddScoped<IBookService, BookServiceImpl>();
//Person
services.AddScoped<IPersonRepository, PersonRepositoryImpl>();
services.AddScoped<IPersonService, PersonServiceImpl>();
//Student
services.AddScoped<IStudentRepository, StudentRepositoryImpl>();
services.AddScoped<IStudentService, StudentServiceImpl>();
//Image
services.AddScoped<IImageRepository, ImageRepositoryImpl>();
services.AddScoped<IImageService, ImageServiceImpl>();
//Product
//services.AddScoped<IProductRepository, ProductRepositoryImpl>();
services.AddScoped<IProductService, ProductServiceImpl>();
}
}
项目结构截图:
综上,即可在项目实际开发中进行自动DI注入 不必把所有业务注入对象都在Program.cs中注册
以上 谢谢!