.Net Core中使用Autofac替換自帶的DI容器的示例
為什麼叫淺談呢?就是字面上的意思,講得比較淺,又不是不能用(這樣是不對的)!!!
Aufofac大傢都不陌生瞭,說是.Net生態下最優秀的IOC框架那是一點都過分。用的人多瞭,使用教程也十分豐富,官網教程也比較詳細(如果英文功底還不錯的話)。
那我為什麼還要寫這樣一篇博客呢,一是用作學習筆記,二就是閑的。
廢話不多說,開始正文
項目創建
雲創建一個.Net Core Api項目,然後再添加一個類庫,大概就是下面這樣的結構:
新建一個類庫項目,分別添加一個接口文件與類文件:
就這樣,我們的演示方案就搭建完成瞭,下面就到瞭演示階段。
方案演示
原始方案
俗話說的好,沒有對象 new 一個就對瞭:
[HttpGet] public string Original() { IUserService userService = new UserService(); return userService.GetName("Original"); }
結果當然是沒問題的:
.Net Core自帶DI
微軟給我們提供的 DI 解決方案。如果是小項目,需要註入的服務不多,簡直無敵好用,缺點就是不能批量註入,下面我們來復習一下:
先在 Startup 裡面的 ConfigureServices 方法內註入(默認且隻能構造函數註入)
services.AddScoped<IUserService, UserService>();
然後在控制器中拿到剛才註入的服務:
public class DefaultController : ControllerBase { private readonly IUserService userService; public DefaultController(IUserService _userService) { this.userService = _userService; } [HttpGet] public string CoreDI() { return userService.GetName("CoreDI"); } }
很顯然,一點問題都沒有:
Autofac
註意事項說在前面:
在 .Net Core2 中一般是把Startup
的ConfigureServices
方法返回值類型改為IServiceProvider,然後通過構建Autofac容器並註入服務後返回。
在 .Net Core3.0之後,集成方式做瞭部分調整
下面演示的版本是.Net Core 3.1,也就是調整後的版本。
1、先引用 Autofac 的包,看看這下載次數
2、在 Program 中改用 Autofac 來實現依賴註入
public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) // 就是這句 .UseServiceProviderFactory(new AutofacServiceProviderFactory()) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); });
3、添加我們自定義的 Autofac 註冊類,並註冊我們需要的服務(默認構造函數註入,支持屬性註入)
public class AutofacModuleRegister : Autofac.Module { //重寫Autofac管道Load方法,在這裡註冊註入 protected override void Load(ContainerBuilder builder) { builder.RegisterType<UserService>().As<IUserService>(); } }
4、在 Startup 類中添加方法:ConfigureContainer,
public void ConfigureContainer(ContainerBuilder builder) { // 直接用Autofac註冊我們自定義的 builder.RegisterModule(new AutofacModuleRegister()); }
5、大功告成,控制器內的方法甚至不用去改
public class DefaultController : ControllerBase { private readonly IUserService userService; public DefaultController(IUserService _userService) { this.userService = _userService; } [HttpGet] public string Autofac() { return userService.GetName("Autofac"); } }
演示到這裡就結束瞭,是不是感覺 Autofac 比自帶的 DI 還要麻煩。其實不然,下面我們就來看看 Autofac 對比自帶 DI 的一些特有特性。
不同的特性
批量註入
之前的項目我們有瞭用戶 UserService,需求更新,加入瞭商品(ProductService),有瞭商品那又怎麼能少得瞭訂單(OrderService),那後面是不是還得有售後、物流、倉庫、營銷……
如果是.Net Core 自帶的註入框架,那就隻能不停的:
services.AddScoped<IProductService, ProductService>(); services.AddScoped<IOrderService, OrderService>(); ......
這時候,Autofac 的好處就體現出來瞭:批量註入。
我們先回到上面的:AutofacModuleRegister 類,加入下面這段代碼:
// 服務項目程序集 Assembly service = Assembly.Load("XXX.Service"); // 服務接口項目程序集 Assembly iservice = Assembly.Load("XXX.IService"); builder.RegisterAssemblyTypes(service, iservice) .Where(t => t.FullName.EndsWith("Service") && !t.IsAbstract) .InstancePerLifetimeScope() .AsImplementedInterfaces();
上面的代碼就是批量註入 XXX.Service 與 XXX.IService 項目下的服務與接口。
註意:如果需要註入的服務沒有 interfac ,那麼builder.RegisterAssemblyTypes 就隻需要傳一個程序集就OK瞭。如果服務與接口同在一個項目,那也是要傳兩個程序集的哦。
然後我們在控制器去通過構造函數獲取註入的實例:
private readonly IUserService userService; private readonly IProductService productService; public DefaultController(IUserService _userService, IProductService _productService) { this.userService = _userService; this.productService = _productService; }
再對之前的 Autofac 接口添油加醋:
[HttpGet] public string Autofac() { var name = userService.GetName("Autofac"); return productService.Buy(name, "批量註入"); }
結果自然是沒有問題的,如果後續需要加入其它服務都不用再單獨註入瞭,是不是優點就體現出來瞭。批量註入還有一些其它的玩法,比如篩選類名,篩選父類等。
屬性註入
.Net Core 自帶的 DI 框架與 Autofac 默認都是構造函數註入,官方建議也是構造函數註入。
但是有些同學可能就不喜歡構造函數註入,再加上有些場景確實不適合構造函數註入(比如基類實體),所以 Autofac 也支持屬性註入,下面我們來看看使用方法,在之前批量註入的基礎上,我們簡單改造一下:
Assembly service = Assembly.Load("Autofac.Service"); Assembly iservice = Assembly.Load("Autofac.Service"); builder.RegisterAssemblyTypes(service, iservice) .Where(t => t.FullName.EndsWith("Service") && !t.IsAbstract) .InstancePerLifetimeScope() .AsImplementedInterfaces() .PropertiesAutowired(); // 屬性註入
對比構造函數註入,屬性註入就多追加瞭PropertiesAutowired() 函數,控制器內修改:
public IUserService userService { get; set; } public IProductService productService { get; set; }
註意:屬性註入記得將屬性的訪問修飾符改為註冊類可訪問的修飾符,否則會註入失敗。
下面我們來看看使用效果:
咦,怎麼會空引用呢?原因大概就是 Controller 是由Mvc 模塊管理的,不在 IOC 容器內,所以在 Controller 中無法使用 Autofac 註入的實例。
那為什麼構造函數註入的時候又可以呢?大概或許可能他們都是構造函數註入吧…
為什麼是大概呢?因為我暫時也沒有具體去深入研究到底是什麼原因導致的,如果有一天,我想起來去研究瞭並且有結果瞭,我會在這裡補上。
我們先解決上面的問題先,在 Startup 的 ConfigureServices 方法底部加入如下代碼:
// 使用 ServiceBasedControllerActivator 替換 DefaultControllerActivator; // Controller 默認是由 Mvc 模塊管理的,不在 Ioc 容器中。替換之後,將放在 Ioc 容器中。 services.Replace(ServiceDescriptor.Transient<IControllerActivator, ServiceBasedControllerActivator>());
然後回到我們的AutofacModuleRegister 註入 Controller:
builder.RegisterTypes(GetAssemblyTypes<Startup>(type => typeof(ControllerBase).IsAssignableFrom(type))) .PropertiesAutowired();
這樣處理完後,屬性註入就Ok瞭。
存儲並提取容器實例
我們在之前項目的基礎上添加兩個項目 Common 與 Entities,存放公共類與實體類。
我們需要在實體類裡面使用到 Common 項目中的某個類,結構如下:
// 基類實體 public class BaseEntity { public Class1 common_Class1 { get; set; } public string CreateId { get; set; } public void Create() { this.CreateId = common_Class1.getCurrentUserId(); } } // 公共類 public class Class1 { public string getCurrentUserId() { return Guid.NewGuid().ToString(); } }
從上面的接口中我們可以看到,我需要將 Class1 通過屬性註入到容器中:
builder.RegisterType<Class1>().PropertiesAutowired().InstancePerLifetimeScope();
我們先在 Controller 中看看效果:
public Class1 class1 { get; set; } [HttpGet] public string Autofac() { return class1.getCurrentUserId(); }
很顯然結果是沒問題的:
那我們再到 BaseEntity 中去試試看:
咦,又出現空引用,註入失敗瞭。其實這個問題很明顯,我們使用的是 new 來實例化的 BaseEntity對象,沒有遵循容器實例使用規則,自然就無法使用容器中的實例瞭。
大傢可以自己試一下,將 new 改為屬性註入就沒問題瞭,但是這種方案並不友好,下面要說的是另一種方案。
我們再新添加一個公共類:ContainerHelper,並聲明一個屬性用來存儲容器的實例:
public static class ContainerHelper { public static ILifetimeScope ContainerBuilder { get; set; } }
然後回到 Startup 中,在Configure 方法的底部加入如下代碼:
ContainerHelper.ContainerBuilder = app.ApplicationServices.CreateScope().ServiceProvider.GetAutofacRoot();
再回到實體類中去使用:
public void Create() { if (common_Class1 == null) { using (var scope = ContainerHelper.ContainerBuilder.BeginLifetimeScope()) { common_Class1 = scope.Resolve<Class1>(); } } this.CreateId = common_Class1.getCurrentUserId(); }
Autofac 的替換方案暫時就寫到這裡瞭,後續如果有新的理解或心得會再做修改,淺談嘛就真的是淺談,有錯誤或補充的地方請大傢不吝賜教。
源碼這裡就不提供瞭,大傢有耐心的可以跟著手敲一遍,雖然對理解沒啥作用,但能使記憶更深刻一點。
到此這篇關於.Net Core中使用Autofac替換自帶的DI容器的示例的文章就介紹到這瞭,更多相關.Net Core Autofac替換DI容器內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- ASP.NET Core 依賴註入框架的使用
- .NET中IoC框架Autofac用法講解
- 理解ASP.NET Core 依賴註入(Dependency Injection)
- .NET Core使用Autofac容器的DI依賴註入,IOC控制反轉及AOP切面編程
- 使用.NET6實現動態API