.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

 1 using Microsoft.Extensions.DependencyInjection;
 2 
 3 namespace WebApi.Core.ProjectDependencyInjection
 4 {
 5     public interface IModuleInitializer
 6     {
 7         /// <summary>
 8         /// 所有项目中的实现了IModuleInitializer接口都会被调用,请在Initialize中编写注册本模块需要的服务。
 9         /// 一个项目中可以放多个实现了IModuleInitializer的类。
10         /// 不过为了集中管理,还是建议一个项目中只放一个实现了IModuleInitializer的类
11         /// </summary>
12         public interface IModuleInitializer
13         {
14             public void Initialize(IServiceCollection services);
15         }
16     }
17 }

注意该接口的引用命名空间
using Microsoft.Extensions.DependencyInjection
此处很重要
2.给Microsoft.Extensions.DependencyInjection类添加扩展方法:
创建一个静态类ModuleInitializerExtensions和静态方法RunModuleInitializers

 1 using System.Reflection;
 2 using static WebApi.Core.ProjectDependencyInjection.IModuleInitializer;
 3 
 4 namespace Microsoft.Extensions.DependencyInjection
 5 {
 6     public static class ModuleInitializerExtensions
 7     {
 8         /// <summary>
 9         /// 每个项目中都可以自己写一些实现了IModuleInitializer接口的类,在其中注册自己需要的服务,这样避免所有内容到入口项目中注册
10         /// </summary>
11         /// <param name="services">IServiceCollection</param>
12         /// <param name="assemblies">获取工程下所有的项目文件</param>
13         public static IServiceCollection RunModuleInitializers(this IServiceCollection services,
14          IEnumerable<Assembly> assemblies)
15         {
16             //遍历项目文件
17             foreach (var asm in assemblies)
18             {
19                 //获取当前文件类型
20                 Type[] types = asm.GetTypes();
21                 //筛选并获取需要的类型(即:实现了IModuleInitializer接口的类)
22                 //筛选条件:非抽象类&&且实现了IModuleInitializer接口
23                 var moduleInitializerTypes = types.Where(t => !t.IsAbstract && typeof(IModuleInitializer).IsAssignableFrom(t));
24                 foreach (var implType in moduleInitializerTypes)
25                 {
26                     var initializer = (IModuleInitializer?)Activator.CreateInstance(implType);
27                     if (initializer == null)
28                     {
29                         throw new ApplicationException($"Cannot create ${implType}");
30                     }
31                     //通过接口实现需要批量注入的指定的业务对象
32                     initializer.Initialize(services);
33                 }
34             }
35             return services;
36         }
37     }
38 }

注意命名空间和相关引用
3.创建获取解决方案下的所有项目文件获取方法
所需引用:
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics;
using System.Reflection.PortableExecutable;
using System.Reflection;
using System.Reflection.Metadata;

  1  public static class ReflectionHelper
  2  {
  3      /// <summary>
  4      /// 据产品名称获取程序集
  5      /// </summary>
  6      /// <param name="productName"></param>
  7      /// <returns></returns>
  8      public static IEnumerable<Assembly> GetAssembliesByProductName(string productName)
  9      {
 10          var asms = AppDomain.CurrentDomain.GetAssemblies();
 11          foreach (var asm in asms)
 12          {
 13              var asmCompanyAttr = asm.GetCustomAttribute<AssemblyProductAttribute>();
 14              if (asmCompanyAttr != null && asmCompanyAttr.Product == productName)
 15              {
 16                  yield return asm;
 17              }
 18          }
 19      }
 20      //是否是微软等的官方Assembly
 21      private static bool IsSystemAssembly(Assembly asm)
 22      {
 23          var asmCompanyAttr = asm.GetCustomAttribute<AssemblyCompanyAttribute>();
 24          if (asmCompanyAttr == null)
 25          {
 26              return false;
 27          }
 28          else
 29          {
 30              string companyName = asmCompanyAttr.Company;
 31              return companyName.Contains("Microsoft");
 32          }
 33      }
 34 
 35      private static bool IsSystemAssembly(string asmPath)
 36      {
 37          var moduleDef = AsmResolver.DotNet.ModuleDefinition.FromFile(asmPath);
 38          var assembly = moduleDef.Assembly;
 39          if (assembly == null)
 40          {
 41              return false;
 42          }
 43          var asmCompanyAttr = assembly.CustomAttributes.FirstOrDefault(c => c.Constructor?.DeclaringType?.FullName == typeof(AssemblyCompanyAttribute).FullName);
 44          if (asmCompanyAttr == null)
 45          {
 46              return false;
 47          }
 48          var companyName = ((AsmResolver.Utf8String?)asmCompanyAttr.Signature?.FixedArguments[0]?.Element)?.Value;
 49          if (companyName == null)
 50          {
 51              return false;
 52          }
 53          return companyName.Contains("Microsoft");
 54      }
 55 
 56      /// <summary>
 57      /// 判断file这个文件是否是程序集
 58      /// </summary>
 59      /// <param name="file"></param>
 60      /// <returns></returns>
 61      private static bool IsManagedAssembly(string file)
 62      {
 63          using var fs = File.OpenRead(file);
 64          using PEReader peReader = new PEReader(fs);
 65          return peReader.HasMetadata && peReader.GetMetadataReader().IsAssembly;
 66      }
 67 
 68      private static Assembly? TryLoadAssembly(string asmPath)
 69      {
 70          AssemblyName asmName = AssemblyName.GetAssemblyName(asmPath);
 71          Assembly? asm = null;
 72          try
 73          {
 74              asm = Assembly.Load(asmName);
 75          }
 76          catch (BadImageFormatException ex)
 77          {
 78              Debug.WriteLine(ex);
 79          }
 80          catch (FileLoadException ex)
 81          {
 82              Debug.WriteLine(ex);
 83          }
 84 
 85          if (asm == null)
 86          {
 87              try
 88              {
 89                  asm = Assembly.LoadFile(asmPath);
 90              }
 91              catch (BadImageFormatException ex)
 92              {
 93                  Debug.WriteLine(ex);
 94              }
 95              catch (FileLoadException ex)
 96              {
 97                  Debug.WriteLine(ex);
 98              }
 99          }
100          return asm;
101      }
102 
103      /// <summary>
104      /// loop through all assemblies
105      /// </summary>
106      /// <returns></returns>
107      public static IEnumerable<Assembly> GetAllReferencedAssemblies(bool skipSystemAssemblies = true)
108      {
109          Assembly? rootAssembly = Assembly.GetEntryAssembly();
110          if (rootAssembly == null)
111          {
112              rootAssembly = Assembly.GetCallingAssembly();
113          }
114          var returnAssemblies = new HashSet<Assembly>(new AssemblyEquality());
115          var loadedAssemblies = new HashSet<string>();
116          var assembliesToCheck = new Queue<Assembly>();
117          assembliesToCheck.Enqueue(rootAssembly);
118          if (skipSystemAssemblies && IsSystemAssembly(rootAssembly) != false)
119          {
120              if (IsValid(rootAssembly))
121              {
122                  returnAssemblies.Add(rootAssembly);
123              }
124          }
125          while (assembliesToCheck.Any())
126          {
127              var assemblyToCheck = assembliesToCheck.Dequeue();
128              foreach (var reference in assemblyToCheck.GetReferencedAssemblies())
129              {
130                  if (!loadedAssemblies.Contains(reference.FullName))
131                  {
132                      var assembly = Assembly.Load(reference);
133                      if (skipSystemAssemblies && IsSystemAssembly(assembly))
134                      {
135                          continue;
136                      }
137                      assembliesToCheck.Enqueue(assembly);
138                      loadedAssemblies.Add(reference.FullName);
139                      if (IsValid(assembly))
140                      {
141                          returnAssemblies.Add(assembly);
142                      }
143                  }
144              }
145          }
146          var asmsInBaseDir = Directory.EnumerateFiles(AppContext.BaseDirectory,
147              "*.dll", new EnumerationOptions { RecurseSubdirectories = true });
148          foreach (var asmPath in asmsInBaseDir)
149          {
150              if (!IsManagedAssembly(asmPath))
151              {
152                  continue;
153              }
154              AssemblyName asmName = AssemblyName.GetAssemblyName(asmPath);
155              //如果程序集已经加载过了就不再加载
156              if (returnAssemblies.Any(x => AssemblyName.ReferenceMatchesDefinition(x.GetName(), asmName)))
157              {
158                  continue;
159              }
160              if (skipSystemAssemblies && IsSystemAssembly(asmPath))
161              {
162                  continue;
163              }
164              Assembly? asm = TryLoadAssembly(asmPath);
165              if (asm == null)
166              {
167                  continue;
168              }
169              //Assembly asm = Assembly.Load(asmName);
170              if (!IsValid(asm))
171              {
172                  continue;
173              }
174              if (skipSystemAssemblies && IsSystemAssembly(asm))
175              {
176                  continue;
177              }
178              returnAssemblies.Add(asm);
179          }
180          return returnAssemblies.ToArray();
181      }
182 
183      private static bool IsValid(Assembly asm)
184      {
185          try
186          {
187              asm.GetTypes();
188              asm.DefinedTypes.ToList();
189              return true;
190          }
191          catch (ReflectionTypeLoadException)
192          {
193              return false;
194          }
195      }
196 
197      class AssemblyEquality : EqualityComparer<Assembly>
198      {
199          public override bool Equals(Assembly? x, Assembly? y)
200          {
201              if (x == null && y == null) return true;
202              if (x == null || y == null) return false;
203              return AssemblyName.ReferenceMatchesDefinition(x.GetName(), y.GetName());
204          }
205 
206          public override int GetHashCode([DisallowNull] Assembly obj)
207          {
208              return obj.GetName().FullName.GetHashCode();
209          }
210      }
211  }

4.在Progarm.cs中注册封装好的服务

 1     public class Program
 2     {
 3         public static void Main(string[] args)
 4         {
 5             var builder = WebApplication.CreateBuilder(args);
 6             
 7             //应用各个工程自动DI
 8             var asmr = ReflectionHelper.GetAllReferencedAssemblies();
 9             builder.Services.RunModuleInitializers(asmr);
10             
11             var app = builder.Build();
12             app.UseHttpsRedirection();
13             app.UseAuthentication();
14             app.UseAuthorization();
15             //其他服务注册
16                    ...
17             app.MapControllers();
18             app.Run();
19         }
20     }

5.接口实现并进行业务对象注入
注意引用:
using static WebApi.Core.ProjectDependencyInjection.IModuleInitializer;

 1    public class ProjectModuleInitializer : IModuleInitializer
 2    {
 3        public void Initialize(IServiceCollection services)
 4        {
 5            //DBContext
 6            services.AddSingleton(typeof(DBContext<>));
 7            //services.AddSingleton(typeof(DBContextFactory<>));
 8            //Base
 9            services.AddScoped(typeof(IBaseRepository<>), typeof(BaseRepositoryImpl<>));
10            //Book
11            services.AddScoped<IBookRepository, BookRepositoryImpl>();
12            services.AddScoped<IBookService, BookServiceImpl>();
13            //Person
14            services.AddScoped<IPersonRepository, PersonRepositoryImpl>();
15            services.AddScoped<IPersonService, PersonServiceImpl>();
16            //Student
17            services.AddScoped<IStudentRepository, StudentRepositoryImpl>();
18            services.AddScoped<IStudentService, StudentServiceImpl>();
19            //Image
20            services.AddScoped<IImageRepository, ImageRepositoryImpl>();
21            services.AddScoped<IImageService, ImageServiceImpl>();
22            //Product
23            //services.AddScoped<IProductRepository, ProductRepositoryImpl>();
24            services.AddScoped<IProductService, ProductServiceImpl>();
25        }
26    }

项目结构截图:

 

 

 

 

综上,即可在项目实际开发中进行自动DI注入 不必把所有业务注入对象都在Program.cs中注册

以上 谢谢!

posted @ 2024-12-10 18:25  小北-bsx133  阅读(213)  评论(0)    收藏  举报