Asp.Net Core 提供了默認的依賴注入容器 IServiceCollection,它是一個輕量級的依賴注入容器,所以功能不多,只是提供了基礎的一些功能,要實現AOP就有點麻煩,因此在實際工作當中,我們常常會使用第三方依賴注入容器替換掉Asp.Net Core自帶的依賴注入容器。
我們先來看下Asp.Net Core自帶依賴注入容器IServiceCollection的主要用法,雖然說在工作中經常會被替換掉,但有些情況下使用該自帶的就已經足夠了,所以自然也就需要先了解它的使用方法。
IServiceCollection依賴注入生命周期和其他大多數依賴注入容器一樣,分為 瞬時生命周期、單例和請求單例。我們可以在Startup.cs文件中的ConfigureServices方法中直接使用它。這里我們單獨把它拿出來看一下具體怎么使用,我們定義ITestService1,ITestService2,ITestService3,ITestService4以及他們的4個實現類。
IServiceCollection container = new ServiceCollection(); container.AddTransient<ITestService1, TestService1>();//瞬時生命周期 container.AddSingleton<ITestService2, TestService2>();//單例:全容器都是一個 container.AddScoped<ITestService3, TestService3>();//請求單例:一個請求作用域是一個實例 container.AddSingleton<ITestService4>(new TestService4()); var provider = container.BuildServiceProvider(); ITestService1 testService1 = provider.GetService<ITestService1>(); ITestService1 testService2 = provider.GetService<ITestService2>(); Console.WriteLine(object.ReferenceEquals(testService1, testService2));//輸出 false ITestService2 testService2_1 = provider.GetService<ITestService2>(); ITestService2 testService2_2 = provider.GetService<ITestService2>(); Console.WriteLine(object.ReferenceEquals(testService2_1, testService2_2));//輸出 true ITestService3 testService3_1 = provider.GetService<ITestService3>(); ITestService3 testService3_2 = provider.GetService<ITestService3>(); Console.WriteLine(object.ReferenceEquals(testService3_1, testService3_2));//輸出 true var scope1 = provider.CreateScope(); var scope2 = provider.CreateScope(); ITestService3 testService3_3 = scope1.ServiceProvider.GetService<ITestService3>(); ITestService3 testService3_4 = scope2.ServiceProvider.GetService<ITestService3>(); Console.WriteLine(object.ReferenceEquals(testService3_3, testService3_4)); //輸出 false ITestService4 testService4_1 = provider.GetService<ITestService4>(); ITestService4 testService4_2 = provider.GetService<ITestService4>(); Console.WriteLine(object.ReferenceEquals(testService4_1, testService4_2)); //輸出 true
上述代碼已經可以很好的闡述了IServiceCollection的用法,但是這些也只是基本的功能,接下來我們就來看下使用Autofac如何替換掉IServiceCollection。
Autofac是一個Microsoft .NET的IoC容器。 它管理類與類之間的依賴關系,使得應用程序層級之間實現了解耦,不至於在應用程序變得越來越復雜的情況下難以修改。
那么現在就一起來看看怎么使用Autofac來替換掉Asp.Net Core自帶的依賴注入容器吧,首先先來介紹最常用也是被推薦使用的構造函數注入方式。在Autofac官方文檔中有例子可參考。要使用Autofac替換IServiceCollection,我們需要在Startup.cs文件中將ConfigureServices方法的返回值從void修改為 IServiceProvider。
在開始之前,我們需要先從Nuget下載安裝Autofac,可以使用如下命令進行安裝
Install-Package Autofac.Extensions.DependencyInjection -Version 4.4.0
接下來隨便定義兩個測試接口和實現類
public interface ITestServiceA { void Show(); } public interface ITestServiceB { void Show(); } public class TestServiceA : ITestServiceA { public void Show() { Console.WriteLine("This is TestServiceA...."); } } public class TestServiceB : ITestServiceB { public void Show() { Console.WriteLine("This is TestServiceB...."); } }
接下來我們修改Startup.cs的ConfigureServices方法
// This method gets called by the runtime. Use this method to add services to the container. public IServiceProvider ConfigureServices(IServiceCollection services) { services.Configure<CookiePolicyOptions>(options => { // This lambda determines whether user consent for non-essential cookies is needed for a given request. options.CheckConsentNeeded = context => true; options.MinimumSameSitePolicy = SameSiteMode.None; }); services.AddSession(); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); var containerBuilder = new ContainerBuilder();
containerBuilder.RegisterModule<AutofacInitModule>();
// Populate 方法是最重要的,如果沒有調用這個方法,則無法將注入到 IServiceCollection的內置對象填充到autofac中,像控制器注入,Log注入等,程序會報錯。
containerBuilder.Populate(services); var container = containerBuilder.Build(); return new AutofacServiceProvider(container); }
AutofacInitModule類是繼承了Autofac.Module類的子類,我們可以重寫Load方法進行Autofac的初始化注入,當然也可以直接寫在Startup文件的ConfigureServices方法中,單獨抽出來會顯得不那么臃腫,優雅一些。
using Autofac; namespace WebApplication2 { internal class AutofacInitModule : Module { protected override void Load(ContainerBuilder builder) { builder.RegisterType<TestServiceA>().As<ITestServiceA>(); builder.RegisterType<TestServiceB>().As<ITestServiceB>(); // builder.RegisterType<TestServiceA>().As<ITestServiceA>().SingleInstance(); //單例 } } }
現在我們就可以在Controller中使用了
public class HomeController : Controller { private readonly ILogger<HomeController> _logger; private readonly ITestServiceA _serviceA; private readonly ITestServiceB _serviceB; public HomeController(ILogger<HomeController> logger, ITestServiceA serviceA, ITestServiceB serviceB) { this._logger = logger; this._serviceA = serviceA; this._serviceB = serviceB; } public IActionResult Index() { this._serviceA.Show(); this._serviceB.Show(); this._logger.LogWarning("this is logger...."); return View(); } }
運行程序,可以看到_logger、_serviceA、_serviceB都成功的被創建了。
那么上述是使用的autofac的構造函數注入,雖然是最被推薦的也是最常用的注入方式,但是autofac也提供了屬性注入的方式,下面我們也了解一下。
修改Startup.cs的ConfigureServices方法和AutofaceInitModule類
// This method gets called by the runtime. Use this method to add services to the container. public IServiceProvider ConfigureServices(IServiceCollection services) { services.Configure<CookiePolicyOptions>(options => { // This lambda determines whether user consent for non-essential cookies is needed for a given request. options.CheckConsentNeeded = context => true; options.MinimumSameSitePolicy = SameSiteMode.None; }); services.AddSession(); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2).AddControllersAsServices(); var containerBuilder = new ContainerBuilder(); // Populate 方法是最重要的,如果沒有調用這個方法,則無法將注入到 IServiceCollection的內置對象填充到autofac中,像控制器注入,Log注入等,程序會報錯。 containerBuilder.Populate(services); containerBuilder.RegisterModule<AutofacInitModule>(); var container = containerBuilder.Build(); return new AutofacServiceProvider(container); }
using Autofac; using Microsoft.AspNetCore.Mvc; using System.Linq; namespace WebApplication2 { internal class AutofacInitModule : Module { protected override void Load(ContainerBuilder builder) { //注冊服務 builder.RegisterType<TestServiceA>().As<ITestServiceA>().PropertiesAutowired(); builder.RegisterType<TestServiceB>().As<ITestServiceB>().PropertiesAutowired(); //注冊所有控制器 var controllersTypesInAssembly = typeof(Startup).Assembly.GetExportedTypes() .Where(type => typeof(ControllerBase).IsAssignableFrom(type)).ToArray(); builder.RegisterTypes(controllersTypesInAssembly).PropertiesAutowired(); // builder.RegisterType<TestServiceA>().As<ITestServiceA>().SingleInstance(); //單例 } } }
需要注意的是 services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2).AddControllersAsServices();和
containerBuilder.Populate(services);需要寫在注入服務之前,屬性注入才能成功。
public class HomeController : Controller { //private readonly ILogger<HomeController> _logger; //private readonly ITestServiceA _serviceA; //private readonly ITestServiceB _serviceB; public ILogger<HomeController> Logger { get; set; } public ITestServiceA ServiceA { get; set; } public ITestServiceB ServiceB { get; set; } //public HomeController(ILogger<HomeController> logger, ITestServiceA serviceA, ITestServiceB serviceB) //{ // this._logger = logger; // this._serviceA = serviceA; // this._serviceB = serviceB; //} public IActionResult Index() { //this._serviceA.Show(); //this._serviceB.Show(); //this._logger.LogWarning("this is logger...."); Logger.LogWarning(ServiceA.Show()); Logger.LogWarning(ServiceB.Show()); return View(); } }
運行可以看到成功的結果
最后,在一開始之前,我們提到Asp.Net Core自帶的依賴注入容器在實現AOP的功能很麻煩,在工作中常常會替換成第三方的依賴注入容器,那么現在我們再來看一下autofac怎么實現AOP。
Autofac實現AOP需要引入Autofac.Extras.DynamicProxy庫,通過Nuget添加該庫
Install-Package Autofac.Extras.DynamicProxy -Version 4.5.0
接着為了實現AOP,我們定義如下類和接口
using Autofac.Extras.DynamicProxy; using Castle.DynamicProxy; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace WebApplication2 { public class AutofacTestAop : IInterceptor { public void Intercept(IInvocation invocation) { Console.WriteLine($"invocation.Methond={invocation.Method}, invocation.Arguments={string.Join(",", invocation.Arguments)}"); invocation.Proceed(); //繼續執行TestAop.Show Console.WriteLine($"Method {invocation.Method} Excuted"); } } public interface ITestAop { void Show(); } [Intercept(typeof(AutofacTestAop))] public class TestAop : ITestAop { private ILogger<TestAop> _logger; public TestAop(ILogger<TestAop> logger) { this._logger = logger; } public void Show() { this._logger.LogWarning("this is TestAop ....."); } } }
AutofacTestAop為實現了IInterceptor的類,它的Intercept方法就是用來做AOP的調用的。除了實現這個方法外,在需要添加AOP功能的類上需要添加特性 [Intercept(typeof(AutofacTestAop))] 。最后需要在AutofacInitModule中配置一下啟用 EnableInterfaceInterceptors。
using Autofac; using Autofac.Extras.DynamicProxy; using Microsoft.AspNetCore.Mvc; using System.Linq; namespace WebApplication2 { internal class AutofacInitModule : Module { protected override void Load(ContainerBuilder builder) { //注冊服務 builder.RegisterType<TestServiceA>().As<ITestServiceA>(); builder.RegisterType<TestServiceB>().As<ITestServiceB>(); builder.Register(c => new AutofacTestAop()); builder.RegisterType<TestAop>().As<ITestAop>().EnableInterfaceInterceptors(); // builder.RegisterType<TestServiceA>().As<ITestServiceA>().SingleInstance(); //單例 } } }
最后在HomeController中調用ITestAop的Show方法就會先執行AutofacTestAop里的Intercept方法了。