反射反射程式猿的快樂,使用反射實現NetCore3.1依賴注入
十年河東,十年河西,莫欺少年窮
學無止境,精益求精
開局一張圖,內容全靠編
如下圖:
上圖是我簡單構造的一個NetCore的分層專案,解釋如下:
appModel:實體層
appDataInterface:資料訪問介面層
appDataService:資料訪問介面層的實現層
appLogicInterface:業務邏輯介面層
appLogicService:業務邏輯層的實現層
appWeb:webApi站點
很簡單的一個基礎的專案構造就這樣悄無聲息的構建出來了,層次之間的引用,我就不多贅述了。哈哈
然後,我們搞一個介面,如下:
appDataInterface 的 ILoginRepository
using appModel; using System; using System.Collections.Generic; using System.Text; namespace appDataInterface { public interface ILoginRepository { LoginModel UserLogin(string userAccount, string userPassword); } }
appLogicInterface 的 ILoginService
using appModel; using System;using System.Collections.Generic; using System.Text; namespace appLogicInterface { public interface ILoginService { LoginModel UserLogin(string userAccount, string userPassword); } }
再然後,我們搞下介面的實現,如下:
appDataService 的 LoginRepository
using appDataInterface; using appModel; usingSystem; namespace appDataService { public class LoginRepository: ILoginRepository { public LoginModel UserLogin(string userAccount, string userPassword) { return new LoginModel() { userName = "陳大六", userRole = "超級管理員" }; } } }
appLogicService 的LoginService
using appDataInterface; using appLogicInterface; using appModel; using System; namespace appLogicService { public class LoginService: ILoginService { private readonly ILoginRepository repository; public LoginService(ILoginRepository repository) { this.repository = repository; } /// <summary> /// 使用者登入介面 /// </summary> /// <param name="userAccount"></param> /// <param name="userPassword"></param> /// <returns></returns> public LoginModel UserLogin(string userAccount, string userPassword) { return repository.UserLogin(userAccount, userPassword); } } }
注意:業務邏輯層需要依賴注入資料訪問層,因此她的構造方法為:
private readonly ILoginRepository repository; public LoginService(ILoginRepository repository) { this.repository = repository; }
最後,我們來寫個簡單的控制器,如下:
using appLogicInterface; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace appWeb.Controllers { [Route("api/Login")] [ApiController] public class LoginController : ControllerBase { private readonly ILoginService service; public LoginController(ILoginService service) { this.service = service; } [HttpGet("UserLogin")] public IActionResult UserLogin(string userName,string userPassword) { return Ok(service.UserLogin(userName, userPassword)); } } }View Code
截止到這兒,我們還差一步、、、那就是在StartUp.cs中注入介面及實現類,如下:
public void ConfigureServices(IServiceCollection services) { services.AddControllers(); services.AddScoped<ILoginRepository, LoginRepository>(); services.AddScoped<ILoginService, LoginService>(); }
專案構造完成後,我們來看看返回結果:
那麼,問題來了,如果我們的專案足夠大,介面及實現類足夠多,我們就需要在StartUp.cs中寫很多很多依賴注入,這樣給人一種臃腫的感覺。
有沒有什麼好的方法來避免寫很多的依賴注入呢?
答案是肯定的,反射反射,程式猿的快樂,通過反射我們來解決此問題。
1、在該專案中增加一個專案,命名為:appInit
多餘的話,我也不想多說,直接上程式碼吧,程式碼有一定的註釋:
Constants.cs
using System; using System.Collections.Generic; using System.Text; namespace app.Init { public class Constants { /// <summary> /// 業務邏輯層DLL檔案:appLogicService.dll /// </summary> public const string ServiceDllFullName = "appLogicService.dll"; /// <summary> /// 業務邏輯層實現類的命名字尾,也就是說,我們在介面層定義一個介面為:IABCService 那麼,它的實現類就必須為:ABCService /// </summary> public const string ServiceSuffix = "Service"; /// <summary> /// 資料訪問層DLL檔案:appDataService.dll /// </summary> public const string RepositoryDllFullName = "appDataService.dll"; /// <summary> /// 業務邏輯層實現類的命名字尾,也就是說,我們在介面層定義一個介面為:IBugRepository 那麼,它的實現類就必須為:BugRepository /// </summary> public const string RepositorySuffix = "Repository"; } }View Code
DIExtension.cs
using Microsoft.Extensions.DependencyInjection; using System; using System.Linq; using System.Reflection; namespace app { /// <summary> /// 反射注入 /// </summary> public static class DIExtension { //public static void RegisterAssemblyEndsWith(this IServiceCollection services, Assembly assembly, params string[] endsWith) //{ // string @namespace = typeof(DIExtension).Namespace; // foreach (var type in assembly.GetTypes()) // { // if ((!type.IsInterface) && (!type.IsAbstract) && CheckEndsWith(type.FullName, endsWith)) // { // var typeInterface = type.GetInterfaces().Where(p => p.FullName.StartsWith(@namespace)).FirstOrDefault(i => CheckEndsWith(i.FullName, endsWith)); // if (typeInterface != null) // { // services.AddScoped(typeInterface, type); // } // else // { // services.AddScoped(type, type); // } // } // } //} public static void RegisterAssemblyEndsWith(this IServiceCollection services, Assembly assembly, params string[] endsWith) { ///app string @namespace = typeof(DIExtension).Namespace;//用於只查詢app開頭的名稱空間 foreach (var type in assembly.GetTypes()) { if ((!type.IsInterface) && (!type.IsAbstract) && CheckEndsWith(type.FullName, endsWith)) { ///找到對應的介面,驗證介面命名規則 例如 AService 對應的介面應為:IAService 且 名稱空間的開頭帶有"app"|||用於只查詢app開頭的名稱空間 var typeInterfaces = type.GetInterfaces().Where(p => p.FullName.StartsWith(@namespace) && CheckEndsWith(p.FullName, endsWith)).ToList(); if (typeInterfaces?.Count > 0) { foreach (var typeInterface in typeInterfaces) { services.AddScoped(typeInterface, type); } } else { services.AddScoped(type, type); } } } } /// <summary> /// 命名規則檢測 /// </summary> /// <param name="source"></param> /// <param name="endsWith"></param> /// <returns></returns> private static bool CheckEndsWith(string source, string[] endsWith) => endsWith.Any(p => source.EndsWith(p, StringComparison.OrdinalIgnoreCase)); } }View Code
DIRegister.cs
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using System; using System.Collections.Generic; using System.IO; using System.Reflection; using System.Text; namespace app.Init { public static class DIRegister { /// <summary> ///此處 this 是將RegisterDI方法標記為IServiceCollection 的 擴充套件方法 /// </summary> /// <param name="services"></param> public static void RegisterDI(this IServiceCollection services) { ///D:\525gitProject\netcore\WuAnManager\WuAnChangeApi\bin\Debug\netcoreapp3.1 var rootPath = Path.GetDirectoryName(typeof(DIExtension).Assembly.Location); var rootDir = new DirectoryInfo(rootPath); ///D:\525gitProject\netcore\WuAnManager\WuAnChangeApi\bin\Debug\netcoreapp3.1 var basePath = rootDir.FullName; RegisterDll(services, basePath, Constants.ServiceDllFullName, Constants.ServiceSuffix); RegisterDll(services, basePath, Constants.RepositoryDllFullName, Constants.RepositorySuffix); } /// <summary> /// 依賴注入 /// </summary> /// <param name="services"></param> /// <param name="basePath"></param> /// <param name="dllName"></param> /// <param name="endsWith"></param> private static void RegisterDll(IServiceCollection services, string basePath, string dllName, params string[] endsWith) { ///D:\525gitProject\netcore\WuAnManager\WuAnChangeApi\bin\Debug\netcoreapp3.1\WuAnService.dll string assemblyPath = Path.Combine(basePath, dllName); var assembly = Assembly.LoadFrom(assemblyPath); services.RegisterAssemblyEndsWith(assembly, endsWith); } /// <summary> /// 用於檢視注入的介面及實現類 此處的This表明DIListPage方法為IApplicationBuilder的一個擴充套件方法 /// </summary> /// <param name="app"></param> /// <param name="_services"></param> public static void DIListPage(this IApplicationBuilder app, IServiceCollection _services) { app.Map($"/api/allservices", builder => builder.Run(async context => { var sb = new StringBuilder(); sb.Append("<h1>All Services</h1>"); sb.Append("<table><thead>"); sb.Append("<tr><th>Type</th><th>Lifetime</th><th>Instance</th></tr>"); sb.Append("</thead><tbody>"); foreach (var svc in _services) { sb.Append("<tr>"); sb.Append($"<td>{svc.ServiceType.FullName}</td>"); sb.Append($"<td>{svc.Lifetime}</td>"); sb.Append($"<td>{svc.ImplementationType?.FullName}</td>"); sb.Append("</tr>"); } sb.Append("</tbody></table>"); await context.Response.WriteAsync(sb.ToString()); })); } } }View Code
最後,我們修改Startup.cs 如下:
using app.Init; using appDataInterface; using appDataService; using appLogicInterface; using appLogicService; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.HttpsPolicy; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace appWeb { public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } private IServiceCollection _services; // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.AddControllers(); //services.AddScoped<ILoginRepository, LoginRepository>(); //services.AddScoped<ILoginService, LoginService>(); this._services = services; services.RegisterDI(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseHttpsRedirection(); app.UseRouting(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); app.DIListPage(_services); } } }View Code
startup修改的地方如下:
private IServiceCollection _services;
註釋掉:
//services.AddScoped<ILoginRepository, LoginRepository>(); //services.AddScoped<ILoginService, LoginService>();
增加【需要引用appinit專案】
this._services = services; services.RegisterDI();
增加【用於檢視注入的介面及實現類】
app.DIListPage(_services);
這樣,整個無需逐個寫依賴對映的小工程就構造完成了,執行下,看看效果
1、先檢視注入的介面及實現類,路由參考DIRegister.cs類中的DIListPage方法
再看看我們寫的登入方法:
哈哈,就這樣就可以了。
@天才臥龍的部落格
付婷,你還那麼胖嗎?如果胖,就減肥肥吧。