一、前言
1、簡而言之
Asp.Net Core Mvc,我也用了很長一段時間了,它現在的程式設計模型和方式還是特別棒的,都是元件開發,什麼都可以替換,當然了,您別擡槓,有些還是不能替換的。自從我們進入了跨平臺開發的時代,IOC容器也成了一個不可或缺的東西了。微軟為我們提供了一個預設實現,那就是 IServiceCollection,當時我們可以替換掉它,今天我就試試,替換一下,把我的一些經驗也寫出來,以防以後忘記,不知道去哪裡找了。
當然了,這個不是很難,也希望高手不要見笑,對於我來說,好記性不如爛筆頭,好的東西我就記錄下來,有使用的地方,可以直接來找。
2、開發環境。
我的開發環境沒有發生變化,具體如下:
作業系統:Windows10 Professional
開發平臺:Asp.Net Core Mvc 6.0
開發語言:C#
開發工具:Visual Studio 2022
二、操作步驟
1、第一,我們當然要新建一個 Asp.Net Core MVC 的專案,專案都沒有,其他的都是胡扯了,我專案的名稱是:PatrickLiu.Autofac.MvcConcordance。
2、我們為我們的專案增加相應的程式包。分別是:Autofac、Autofac.Extensions.DependencyInjection、Autofac.Extras.DynamicProxy,Castle.Core
1】、Autofac 提供最基礎、最核心的功能。
2】、Autofac.Extensions.DependencyInjection 提供和 Asp.Net Core MVC 整合的介面。
3】、Autofac.Extras.DynamicProxy 提供對AOP的支援,通過動態代理實現。
4】、Castle.Core 實現針對 Core 版本的支援,也是支援 AOP 的必需元件。
3、這部分是重點,在 Program 程式中設定。具體程式碼在裡面,很簡單,就不多說了。
1 using Autofac; 2 using Autofac.Extensions.DependencyInjection; 3 using Autofac.Extras.DynamicProxy; 4 using Castle.DynamicProxy; 5 using Microsoft.AspNetCore.Mvc; 6 using Microsoft.AspNetCore.Mvc.Controllers; 7 using PatrickLiu.Autofac.Contracts; 8 using PatrickLiu.Autofac.Extensions; 9 using PatrickLiu.Autofac.Extensions.Aops; 10 using PatrickLiu.Autofac.Models; 11 12 var builder = WebApplication.CreateBuilder(args); 13 builder.Services.AddControllersWithViews(); 14 15 #region 整合 Autofac 16 17 builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory()); 18 19 builder.Host.ConfigureContainer<ContainerBuilder>(builder => 20 { 21 builder.RegisterType<ChiesePerson>().As<IPerson>(); 22 builder.RegisterType<SinglePerson>(); 23 24 #region 服務型別支援屬性注入,紅色表示是對屬性注入的支援,哪個型別需要屬性注入,在註冊的時候使用 PropertiesAutowired()方法,裡面引數是屬性選擇器。 ,25 26 builder.RegisterType<PropertyPerson>().As<IPropertyPerson>().PropertiesAutowired(new CustomPropertySelector()); 27 builder.RegisterType<PropertyTwoPerson>().As<IPropertyTwoPerson>(); 28 builder.RegisterType<PropertyThreePerson>().As<IPropertyThreePerson>(); 29 builder.RegisterType<SinglePerson>(); 30 31 #endregion 32 33 #region AOP支援,紅色標註的是關鍵實現。 34 35 builder.RegisterType<AOPPerson>().As<IAOPPerson>().EnableInterfaceInterceptors(); 36 builder.RegisterType<AOPClassPerson>().As<IAOPClassPerson>().EnableClassInterceptors(new ProxyGenerationOptions() 37 { 38 Selector = new CustomInterceptorSelector() 39 }); 40 builder.RegisterType<AOPCachePerson>().As<IAOPCachePerson>().EnableClassInterceptors(); 41 42 builder.RegisterType<CustomClassInterceptor>(); 43 builder.RegisterType<CustomInterfaceInterceptor>(); 44 builder.RegisterType<CustomCacheInterceptor>(); 45 46 #endregion 47 48 #region 單介面多範例 49 50 builder.RegisterType<MultiPerson>().Keyed<IMultiPerson>("MultiPerson"); 51 builder.RegisterType<MultiTwoPerson>().Keyed<IMultiPerson>("MultiTwoPerson"); 52 builder.RegisterType<MultiThreePerson>().Keyed<IMultiPerson>("MultiThreePerson"); 53 54 #endregion 55 56 #region 讓控制器支援屬性注入 57 58 var controllerBaseType = typeof(ControllerBase); 59 builder.RegisterAssemblyTypes(typeof(Program).Assembly) 60 .Where(t => controllerBaseType.IsAssignableFrom(t) && controllerBaseType != t) 61 .PropertiesAutowired(new CustomPropertySelector()); 62 63 builder.RegisterType<ServiceBasedControllerActivator>().As<IControllerActivator>(); 64 65 #endregion 66 }); 67 68 #region 支援 Autofac 屬性注入,該方法可以使用,也可以不使用。作用是我們的控制器要使用 Autofac 容器來建立,替換原始的 Controller 啟用器。 69 70 //builder.Services.AddTransient<IControllerActivator, ServiceBasedControllerActivator>(); 71 //builder.Services.Replace(ServiceDescriptor.Transient<IControllerActivator, ServiceBasedControllerActivator>()); 72 73 #endregion 74 75 #endregion 76 77 var app = builder.Build(); 78 79 app.UseStaticFiles(); 80 81 app.UseRouting(); 82 83 app.MapControllerRoute("default", "{controller=AOP}/{action=index}/{id?}"); 84 85 app.Run();
4、Autofac 支援屬性注入,預設是所有屬性的型別如果是註冊的服務型別,就會全部賦值,但是,我們也可以實現 IPropertySelector 介面,自定義哪個屬性需要注入。
1 using Autofac.Core; 2 using System.Reflection; 3 4 namespace PatrickLiu.Autofac.Extensions 5 { 6 public sealed class CustomPropertySelector : IPropertySelector 7 { 8 public bool InjectProperty(PropertyInfo propertyInfo, object instance) 9 { 10 return propertyInfo.IsDefined(typeof(CustomPropertySelectorAttribute), false); 11 } 12 } 13 }
有了選擇器,我們也需要定義特性(Attribute),標註屬性(Property)就可以。
1 namespace PatrickLiu.Autofac.Extensions 2 { 3 /// <summary> 4 /// 5 /// </summary> 6 [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)] 7 public sealed class CustomPropertySelectorAttribute : Attribute 8 { 9 } 10 }
有了這兩個,我們在把我們自定義的屬性選擇器 CustomPropertySelector 作為引數,傳遞給 PropertiesAutowired(new CustomPropertySelector())方法,就完成操作了。
1 using Microsoft.AspNetCore.Mvc; 2 using PatrickLiu.Autofac.Contracts; 3 using PatrickLiu.Autofac.Extensions; 4 using PatrickLiu.Autofac.Models; 5 6 namespace PatrickLiu.Autofac.MvcConcordance.Controllers 7 { 8 /// <summary> 9 /// 10 /// </summary> 11 public class PropertyInjectionController : Controller 12 { 13 private readonly IPropertyPerson _propertyPerson; 14 15 /// <summary> 16 /// 17 /// </summary> 18 /// <param name="propertyPerson"></param> 19 public PropertyInjectionController(IPropertyPerson propertyPerson) 20 { 21 _propertyPerson = propertyPerson; 22 } 23 24 /// <summary> 25 /// 這裡就是控制器的屬性,需要自動初始化。 26 /// </summary> 27 [CustomPropertySelector] 28 public SinglePerson? SinglePerson { get; set; } 29 30 /// <summary> 31 /// 32 /// </summary> 33 /// <returns></returns> 34 public IActionResult Index() 35 { 36 _propertyPerson.Process(); 37 38 return View(); 39 } 40 } 41 }
5、Autofac 支援兩種型別 AOP,分別是:EnableClassInterceptors 和 EnableInterfaceInterceptors ,類攔截器必須使用 [Intercept(typeof(CustomClassInterceptor))]標註需要實現 AOP 的實現類上,如果是介面攔截器,就必須將 [Intercept(typeof(CustomInterfaceInterceptor))] 標註在需要實現 AOP 的介面型別上。
1】、EnableClassInterceptors:類攔截器,它的方法必須是 virtual 虛方法,才可以支援 AOP。
2】、EnableInterfaceInterceptors:介面攔截器,沒有相關限制,該介面的實現類的方法都會實現 AOP。
這就是介面攔截器。
1 using Autofac.Extras.DynamicProxy; 2 using PatrickLiu.Autofac.Extensions.Aops; 3 4 namespace PatrickLiu.Autofac.Contracts 5 { 6 /// <summary> 7 /// 8 /// </summary> 9 [Intercept(typeof(CustomInterfaceInterceptor))] 10 public interface IAOPPerson 11 { 12 /// <summary> 13 /// 14 /// </summary> 15 void Process(); 16 17 /// <summary> 18 /// 19 /// </summary> 20 void ProcessTwo(); 21 22 /// <summary> 23 /// 24 /// </summary> 25 void ProcessThree(); 26 } 27 }
以下是類攔截器。
1 using Autofac.Extras.DynamicProxy; 2 using PatrickLiu.Autofac.Contracts; 3 using PatrickLiu.Autofac.Extensions.Aops; 4 5 namespace PatrickLiu.Autofac.Models 6 { 7 /// <summary> 8 /// 9 /// </summary> 10 [Intercept(typeof(CustomClassInterceptor))] 11 public class AOPClassPerson : IAOPClassPerson 12 { 13 /// <summary> 14 /// 15 /// </summary> 16 public virtual void ProcessAOP() 17 { 18 Console.WriteLine("AOPClassPerson.ProcessAOP()"); 19 } 20 21 /// <summary> 22 /// 23 /// </summary> 24 public void Process() 25 { 26 Console.WriteLine("AOPClassPerson.Process()"); 27 } 28 } 29 }
6、我們要自定義我們的攔截器,然後再標註的型別標註 [Intercept(typeof(自定義攔截器))],就可以使用,我分別定義了兩個攔截器,一個用於類,一個用於介面,其實沒有本質區別,實現的介面都是一樣的,該介面就是:IInterceptor。
1 using Castle.DynamicProxy; 2 3 namespace PatrickLiu.Autofac.Extensions.Aops 4 { 5 /// <summary> 6 /// 類級別的攔截器,標註在要實現AOP功能的型別上。 7 /// </summary> 8 public sealed class CustomClassInterceptor : IInterceptor 9 { 10 /// <summary> 11 /// 12 /// </summary> 13 /// <param name="invocation"></param> 14 public void Intercept(IInvocation invocation) 15 { 16 { 17 Console.WriteLine("CustomClassInterceptor.Before"); 18 } 19 invocation.Proceed(); 20 { 21 Console.WriteLine("CustomClassInterceptor.After"); 22 } 23 } 24 } 25 }
1 using Castle.DynamicProxy; 2 3 namespace PatrickLiu.Autofac.Extensions.Aops 4 { 5 /// <summary> 6 /// 介面級別的攔截器,標註在要實現AOP功能的介面型別上。 7 /// </summary> 8 public sealed class CustomInterfaceInterceptor : IInterceptor 9 { 10 /// <summary> 11 /// 12 /// </summary> 13 /// <param name="invocation"></param> 14 public void Intercept(IInvocation invocation) 15 { 16 { 17 Console.WriteLine("CustomInterfaceInterceptor.Before"); 18 } 19 invocation.Proceed(); 20 { 21 Console.WriteLine("CustomInterfaceInterceptor.After"); 22 } 23 } 24 } 25 }
7、當然,我們也可以不標註 [Intercept(typeof(自定義攔截器))],我們可以實現 IInterceptorSelector 介面,實現自定義使用哪些攔截器。程式碼如下:
1 using Castle.DynamicProxy; 2 using System.Reflection; 3 4 namespace PatrickLiu.Autofac.Extensions.Aops 5 { 6 /// <summary> 7 /// 8 /// </summary> 9 public class CustomInterceptorSelector : IInterceptorSelector 10 { 11 /// <summary> 12 /// 13 /// </summary> 14 /// <param name="type"></param> 15 /// <param name="method"></param> 16 /// <param name="interceptors">如果型別有標註攔截器,這裡會獲取所有攔截器。</param> 17 /// <returns></returns> 18 public IInterceptor[] SelectInterceptors(Type type, MethodInfo method, IInterceptor[] interceptors) 19 { 20 IList<IInterceptor> interceptorsList = new List<IInterceptor>(); 21 interceptorsList.Add(new CustomInterfaceInterceptor()); 22 在這個方法裡面,我們可以過濾攔截器,想是哪個起作用哪個就起作用。返回的攔截器,就是起作用的攔截器。 23 return interceptorsList.ToArray(); 24 } 25 } 26 }
我們可以選擇攔截器,也需要在Program 裡體現,builder.RegisterType<AOPClassPerson>().As<IAOPClassPerson>().EnableClassInterceptors(new ProxyGenerationOptions() { Selector = new CustomInterceptorSelector() });
8、Autofac 預設支援建構函式注入,如果有多個建構函式,如果建構函式的引數都是需要注入的服務型別,預設選擇依賴註冊服務引數最多的建構函式會被執行。當然我們也可以選擇指定的建構函式來初始化型別範例,該方法就是 .UsingConstructor(typeof(引數型別))。
builder.RegisterType<ChiesePerson>().As<IPerson>().UsingConstructor(typeof(int));
1 using Autofac; 2 using Microsoft.AspNetCore.Mvc; 3 using PatrickLiu.Autofac.Contracts; 4 using PatrickLiu.Autofac.Models; 5 6 namespace PatrickLiu.Autofac.MvcConcordance.Controllers 7 { 8 /// <summary> 9 /// 10 /// </summary> 11 public class HomeController : Controller 12 { 13 private readonly IPerson _person; 14 private readonly IServiceProvider _serviceProvider; 15 private readonly IComponentContext _componentContext; 16 17 /// <summary> 18 /// 19 /// </summary> 20 /// <param name="person"></param> 21 /// <param name="serviceProvider">服務提供器,型別是 AutofacServiceProvider,獲取型別。</param> 22 /// <param name="componentContext">autofac 的上下文物件,可以獲取容器中的服務。</param> 23 public HomeController(IPerson person, IServiceProvider serviceProvider, IComponentContext componentContext) 24 { 25 _person = person; 26 _serviceProvider = serviceProvider; 27 _componentContext = componentContext; 28 } 29 30 public IActionResult Index() 31 { 32 _person.Eat("炸醬麵"); 33 34 var single=_serviceProvider.GetService<SinglePerson>(); 35 single!.Eat("殘羹冷炙"); 36 37 var singleTwo=_componentContext.Resolve<SinglePerson>(); 38 singleTwo.Eat("殘羹剩飯"); 39 40 return View(); 41 } 42 } 43 }
三、結束語
平臺本身提供了自己的容器實現,當然,我們也可以替換它,使用其他的 IOC容器。不學不知道,一學嚇一跳,東西還有很多不知道了。平凡的我,只能繼續努力,蒼天不負有心人,努力就會有回報。不忘初心,繼續努力吧。