.Net Core中一种应用层各个项目工程自动注入(DI)的方式

大家好,在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中注册

以上 谢谢!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值