IOC(Inversion of Control)控制反轉:控制反正是一種設計思想,旨在將程式中的控制權從程式設計師轉移到了容器中。容器負責管理物件之間的依賴關係,使得物件不再直接依賴於其他物件,而是通過依賴注入的方式來獲取所需的資源。
ID(Dependency Injection)依賴注入:他是IOC的具體實現方式之一,使用最為廣泛,DI通過在執行時動態地將某個依賴關係抽象為獨立的元件,提交到容器之中,需要使用時再由容器注入,提升元件重用的頻率,為系統搭建一個靈活,可延伸的平臺。
IOC/DI是一種設計模式,用於解耦元件之間的依賴關係。在傳統的程式設計模式中,元件之間的依賴關係是寫死在程式碼中的,這樣會導致程式碼的耦合度很高,難以維護和發展。而IOC/DI模式則是通過將元件之間的依賴關係交給容器來管理,元件不再直接依賴其他元件,而是通過容器來獲取所依賴的物件。這樣可以使元件之間的依賴關係更加靈活,容器可以根據需要動態地建立和管理元件,從而實現更好的可維護性和可延伸性。
首先我們定義一個服務介面及對應的實現
public interface ITestServices { int return123(); }
public class TestServices : ITestServices { public int return123() { return 123; } }
然後我們在Program.cs注入服務實現
var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddControllers(); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); builder.Services.AddTransient<ITestServices, TestServices>(); var app = builder.Build(); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseHttpsRedirection(); app.UseAuthorization(); app.MapControllers(); app.Run();
值得注意的是依賴注入有三種生命週期
最後在需要使用的控制器中建構函式注入就可以使用了
[Route("[controller]/[action]")] [ApiController] public class TestController : ControllerBase { private readonly ITestServices _testServices; public TestController(ITestServices testServices) { _testServices= testServices; } [HttpGet] public int Get123() => _testServices.return123(); }
依賴注入好歸好,就是每個服務都得在Program.cs注入服務實現,一但服務多起來,麻煩不說,Program.cs中的程式碼更是會變得凌亂不堪,可能會有小夥伴說,可以開一個擴充套件函數單獨做注入,但私以為,既然有一種方法可以一勞永逸,何樂而不為呢?
其實現便是利用.net的高階特性之一,反射
首先我們定義三個生命週期介面,其對應依賴注入的三種生命週期
//瞬時注入服務介面 public interface ITransient { } //作用域注入服務介面 public interface IScoped { } //單例注入服務介面 public interface ISingleton { }
然後我們定義自動注入的擴充套件方法,其為核心實現
public static IServiceCollection RegisterAllServices(this IServiceCollection services) { //獲取當前程式集 var entryAssembly = Assembly.GetEntryAssembly(); //獲取所有型別 //!. null包容運運算元,當你明確知道表示式的值不為null 使用!.(即null包容運運算元)可以告知編譯器這是預期行為,不應發出警告 //例: entryAssembly!.GetReferencedAssemblies() 正常 //entryAssembly.GetReferencedAssemblies() 編譯器判斷entryAssembly有可能為null,變數下方出現綠色波浪線警告 var types = entryAssembly!.GetReferencedAssemblies()//獲取當前程式集所參照的外部程式集 .Select(Assembly.Load)//裝載 .Concat(new List<Assembly>() { entryAssembly })//與本程式集合並 .SelectMany(x => x.GetTypes())//獲取所有類 .Distinct();//排重 //三種生命週期分別註冊 Register<ITransient>(types, services.AddTransient, services.AddTransient); Register<IScoped>(types, services.AddScoped, services.AddScoped); Register<ISingleton>(types, services.AddSingleton, services.AddSingleton); return services; } /// <summary> /// 根據服務標記的生命週期interface,不同生命週期註冊到容器裡面 /// </summary> /// <typeparam name="TLifetime">註冊的生命週期</typeparam> /// <param name="types">集合型別</param> /// <param name="register">委託:成對註冊</param> /// <param name="registerDirectly">委託:直接註冊服務實現</param> private static void Register<TLifetime>(IEnumerable<Type> types, Func<Type, Type, IServiceCollection> register, Func<Type, IServiceCollection> registerDirectly) { //找到所有標記了Tlifetime生命週期介面的實現類 var tImplements = types.Where(x => x.IsClass && !x.IsAbstract && x.GetInterfaces().Any(tinterface => tinterface == typeof(TLifetime))); //遍歷,挨個以其他所有介面為key,當前實現為value註冊到容器中 foreach (var t in tImplements) { //獲取除生命週期介面外的所有其他介面 var interfaces = t.GetInterfaces().Where(x => x != typeof(TLifetime)); if (interfaces.Any()) { foreach (var i in interfaces) { register(i, t); } } //有時需要直接注入實現類本身 registerDirectly(t); } }
其核心邏輯便是通過反射掃描程式集,當掃描到實現了我們定義的生命週期介面時,為其實現對應的生命週期注入。
註冊這個服務
builder.Services.RegisterAllServices();
然後我們就可以通過繼承生命週期介面來實現自動服務注入
public interface ITestServices { int return123(); }
public class TestServices : ITestServices, ITransient { public int return123() { return 123; } }
接下來無需在Program.cs注入服務實現
呼叫成功。
自動注入程式碼參考自:【NetCore】依賴注入的一些理解與分享 - wosperry - 部落格園 (cnblogs.com)