如何將 Autofac 整合進 Net6.0 Core MVC 專案中

2023-02-27 15:02:09

一、前言
    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容器。不學不知道,一學嚇一跳,東西還有很多不知道了。平凡的我,只能繼續努力,蒼天不負有心人,努力就會有回報。不忘初心,繼續努力吧。