本文3.0版本文章
本文是NetCore 2.2的,請不要套用3.0
1、如果看不懂本文,或者比較困難,先別着急問問題,我單寫了一個關於依賴注入的小Demo,可以下載看看,多思考思考注入的原理:
https://github.com/anjoy8/BlogArti/tree/master/Blog.Core_IOC%26DI
2、重要:如果你實現了解耦,也就是 api 層只引用了 IService 和 IRepository 的話,那每次修改 service 層,都需要清理解決方案,重新編譯項目,因為這個時候你的api層的dll,還是之前未修改的代碼。
3、重要+ :請注意,依賴注入的目的不是為了解耦,依賴注入是為了控制反轉,通俗來說,就是不用我們自己去 new 服務實例了,所以大家不需要一定去解耦(比如下文說到的我沒有引用 Service層 和 Repository層),我下一個DDD系列,依賴注入就沒有解耦,因為我用的是自帶的注入,不是Autofac的反射dll ,我解耦的目的,是為了讓大家更好的理解,服務是怎么注入到宿主容器里的。
說接上文,上回說到了《八 || API項目整體搭建 6.3 異步泛型+依賴注入初探》,后來的標題中,我把倉儲兩個字給去掉了,因為好像大家對這個模式很有不同的看法,嗯~可能還是我學藝不精,沒有說到其中的好處,現在在學DDD領域驅動設計相關資料,有了好的靈感再給大家分享吧。
到目前為止我們的項目已經有了基本的雛形,后端其實已經可以搭建自己的接口列表了,框架已經有了規模,原本應該說vue了,但是呢,又聽說近來Vue-cli已經從2.0升級到了3.0了,還變化挺大,前端大佬們,都不停歇呀。當然我還在學習當中,我也需要了解下有關3.0的特性,希望給沒有接觸到,或者剛剛接觸到的朋友們,有一些幫助,當然我這個不是隨波逐流,只是在眾多的博文中,給大家一個入門參考,屆時說3.0的時候,還是會說2.0的相關問題的。
雖然項目整體可以運行了,但是我還有幾個小知識點要說下,主要是1、依賴注入和AOP相關知識;2、跨域代理等問題(因為Vue是基於Node開發的,與后台API接口不在同一個地址);3、實體類的DTO相關小問題;4、Redis緩存等;5、部署服務器中的各種坑;雖然都是很小的知識點,我還是都下給大家說下的,好啦,開始今天的講解;
零、今天完成的綠色部分
一、依賴注入的理解和思考
更新(19-04-17):如果想好好的理解依賴注入,可以從以下幾個方面入手:
1、項目之間引用是如何起作用的,比如為啥 api 層只是引用了 service 層,那為啥也能使用 repository 和 model 等多層的類?
2、項目在啟動的時候,也就是運行時,是如何動態 獲取和訪問 每一個對象的實例的?也就是 new 的原理
3、項目中有 n 個類,對應 m 個實例等,那這些服務,都放在了哪里?肯定每一個項目都有專屬自己的一塊。如果項目不啟動的話,內存里肯定是沒有這些服務的。
4、使用接口(面向抽象)的好處?
5、在項目后期,如何業務需要要全部修改接口的實現類,比如想把 IA = new A();全部 改成 IA = new B();
6、反射的重要性,為什么要用到反射 dll ?
如果這些每一條自己都能說清楚,那肯定就知道依賴注入是干啥的了。
說到依賴,我就想到了網上有一個例子,依賴注入和工廠模式中的相似和不同:
(1)原始社會里,沒有社會分工。須要斧子的人(調用者)僅僅能自己去磨一把斧子(被調用者)。相應的情形為:軟件程序里的調用者自己創建被調用者。
(2)進入工業社會,工廠出現。斧子不再由普通人完畢,而在工廠里被生產出來,此時須要斧子的人(調用者)找到工廠,購買斧子,無須關心斧子的制造過程。相應軟件程序的簡單工廠的設計模式。
(3)進入“按需分配”社會,需要斧子的人不需要找到工廠,坐在家里發出一個簡單指令:須要斧子。斧子就自然出如今他面前。相應Spring的依賴注入。
在上篇文章中,我們已經了解到了,什么是依賴倒置、控制反轉(IOC),什么是依賴注入(DI),網上這個有很多很多的講解,我這里就不說明了,其實主要是見到這樣的,就是存在依賴
public class A : D { public A(B b) { // do something } C c = new C(); }
就比如我們的項目中的BlogController,只要是通過new 實例化的,都是存在依賴
public async Task<List<Advertisement>> Get(int id) { IAdvertisementServices advertisementServices = new AdvertisementServices(); return await advertisementServices.Query(d => d.Id == id); }
使用依賴注入呢,有以下優點:
- 傳統的代碼,每個對象負責管理與自己需要依賴的對象,導致如果需要切換依賴對象的實現類時,需要修改多處地方。同時,過度耦合也使得對象難以進行單元測試。
- 依賴注入把對象的創造交給外部去管理,很好的解決了代碼緊耦合(tight couple)的問題,是一種讓代碼實現松耦合(loose couple)的機制。
- 松耦合讓代碼更具靈活性,能更好地應對需求變動,以及方便單元測試。
舉個栗子,就是關於日志記錄的
日志記錄:有時需要調試分析,需要記錄日志信息,這時可以采用輸出到控制台、文件、數據庫、遠程服務器等;假設最初采用輸出到控制台,直接在程序中實例化ILogger logger = new ConsoleLogger(),但有時又需要輸出到別的文件中,也許關閉日志輸出,就需要更改程序,把ConsoleLogger改成FileLogger或者NoLogger, new FileLogger()或者new SqlLogger() ,此時不斷的更改代碼,就顯得心里不好了,如果采用依賴注入,就顯得特別舒暢。
我有一個個人的理解,不知道恰當與否,比如我們平時食堂吃飯,都是食堂自己炒的菜,就算是配方都一樣,控制者(廚師)還是會有些變化,或者是出錯,但是肯德基這種,由總店提供的,店面就失去了控制,就出現了第三方(總店或者工廠等),這就是實現了控制反轉,我們只需要說一句,奧爾良雞翅,嗯就拿出來一個,而且全部分店的都一樣,我們不用管是否改配方,不管是否依賴哪些調理食材,哈哈。
二、常見的IoC框架有哪些
1、Autofac+原生
我常用的還是原生注入和 Autofac 注入。
Autofac:貌似目前net下用的最多吧
Ninject:目前好像沒多少人用了
Unity:也是較為常見微軟 core 自帶的 DI
其實.Net Core 有自己的輕量級的IoC框架,
ASP.NET Core本身已經集成了一個輕量級的IOC容器,開發者只需要定義好接口后,在Startup.cs的ConfigureServices方法里使用對應生命周期的綁定方法即可,常見方法如下
services.AddTransient<IApplicationService,ApplicationService>//服務在每次請求時被創建,它最好被用於輕量級無狀態服務(如我們的Repository和ApplicationService服務)
services.AddScoped<IApplicationService,ApplicationService>//服務在每次請求時被創建,生命周期橫貫整次請求
services.AddSingleton<IApplicationService,ApplicationService>//Singleton(單例) 服務在第一次請求時被創建(或者當我們在ConfigureServices中指定創建某一實例並運行方法),其后的每次請求將沿用已創建服務。如果開發者的應用需要單例服務情景,請設計成允許服務容器來對服務生命周期進行操作,而不是手動實現單例設計模式然后由開發者在自定義類中進行操作。
當然.Net Core自身的容器還是比較簡單,如果想要更多的功能和擴展,還是需要使用上邊上個框架。
2、三種注入的生命周期
權重:
AddSingleton→AddTransient→AddScoped
AddSingleton的生命周期:
項目啟動-項目關閉 相當於靜態類 只會有一個
AddScoped的生命周期:
請求開始-請求結束 在這次請求中獲取的對象都是同一個
AddTransient的生命周期:
請求獲取-(GC回收-主動釋放) 每一次獲取的對象都不是同一個
這里來個簡單的小DEMO:
1、定義四個接口,並分別對其各自接口實現,目的是測試Singleton,Scope,Transient三種,以及最后的 Service 服務:
public interface ISingTest { int Age { get; set; } string Name { get; set; } } public class SingTest: ISingTest { public int Age { get; set; } public string Name { get; set; } } //-------------------------- public interface ISconTest { int Age { get; set; } string Name { get; set; } } public class SconTest: ISconTest { public int Age { get; set; } public string Name { get; set; } } //-------------------------- public interface ITranTest { int Age { get; set; } string Name { get; set; } } public class TranTest: ITranTest { public int Age { get; set; } public string Name { get; set; } } //----------------------- public interface IAService { void RedisTest(); } public class AService : IAService { private ISingTest sing; ITranTest tran; ISconTest scon; public AService(ISingTest sing, ITranTest tran, ISconTest scon) { this.sing = sing; this.tran = tran; this.scon = scon; } public void RedisTest() { } }
2、項目注入
3、控制器調用
private ISingTest sing; ITranTest tran; ISconTest scon; IAService aService; public ValuesController(ISingTest sing, ITranTest tran, ISconTest scon, IAService aService) { this.sing = sing; this.tran = tran; this.scon = scon; this.aService = aService; } // GET api/values [HttpGet] public ActionResult<IEnumerable<string>> SetTest() { sing.Age = 18; sing.Name = "小紅"; tran.Age = 19; tran.Name = "小明"; scon.Age = 20; scon.Name = "小藍"; aService.RedisTest(); return new string[] { "value1", "value2" }; } // GET api/values/5 [HttpGet("{id}")] public ActionResult<string> Get(int id) { return "value"; }
4、開始測試,三種注入方法出現的情況
請求SetTest // GET api/values
AddSingleton的對象沒有變
AddScoped的對象沒有變化
AddTransient的對象發生變化
------------------------------------------------------------
請求 // GET api/values/5
AddSingleton的對象沒有變
AddScoped的對象發生變化
AddTransient的對象發生變化
注意:
由於AddScoped對象是在請求的時候創建的
所以不能在AddSingleton對象中使用
甚至也不能在AddTransient對象中使用
所以權重為
AddSingleton→AddTransient→AddScoped
不然則會拋如下異常
三、較好用的IoC框架使用——Autofac
首先呢,我們要明白,我們注入是要注入到哪里——Controller API層。然后呢,我們看到了在接口調用的時候,如果需要其中的方法,需要using兩個命名空間
[HttpGet("{id}", Name = "Get")] public async Task<List<Advertisement>> Get(int id) { IAdvertisementServices advertisementServices = new AdvertisementServices();//需要引用兩個命名空間Blog.Core.IServices;Blog.Core.Services; return await advertisementServices.Query(d => d.Id == id); }
接下來我們就需要做處理:
1、引入nuget包
在Nuget中引入兩個:Autofac.Extras.DynamicProxy(Autofac的動態代理,它依賴Autofac,所以可以不用單獨引入Autofac)、Autofac.Extensions.DependencyInjection(Autofac的擴展)
2、接管ConfigureServices
讓Autofac接管Starup中的ConfigureServices方法,記得修改返回類型IServiceProvider
(注意,netcore3.0+以上版本,不能這么寫了,請查看我文章開通的微信公眾號文章,或者看我的升級文檔中的autofac部分 最全的 netcore 3.0 升級實戰方案 )
public IServiceProvider ConfigureServices(IServiceCollection services) { services.AddMvc(); #region 配置信息 //Blog.Core.Repository.BaseDBConfig.ConnectionString = Configuration.GetSection("AppSettings:SqlServerConnection").Value; #endregion #region Swagger services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new Info { Version = "v0.1.0", Title = "Blog.Core API", Description = "框架說明文檔", TermsOfService = "None", Contact = new Swashbuckle.AspNetCore.Swagger.Contact { Name = "Blog.Core", Email = "Blog.Core@xxx.com", Url = "https://www.jianshu.com/u/94102b59cc2a" } }); //就是這里 #region 讀取xml信息 var basePath = PlatformServices.Default.Application.ApplicationBasePath; var xmlPath = Path.Combine(basePath, "Blog.Core.xml");//這個就是剛剛配置的xml文件名 var xmlModelPath = Path.Combine(basePath, "Blog.Core.Model.xml");//這個就是Model層的xml文件名 c.IncludeXmlComments(xmlPath, true);//默認的第二個參數是false,這個是controller的注釋,記得修改 c.IncludeXmlComments(xmlModelPath); #endregion #region Token綁定到ConfigureServices //添加header驗證信息 //c.OperationFilter<SwaggerHeader>(); var security = new Dictionary<string, IEnumerable<string>> { { "Blog.Core", new string[] { } }, }; c.AddSecurityRequirement(security); //方案名稱“Blog.Core”可自定義,上下一致即可 c.AddSecurityDefinition("Blog.Core", new ApiKeyScheme { Description = "JWT授權(數據將在請求頭中進行傳輸) 直接在下框中輸入{token}\"", Name = "Authorization",//jwt默認的參數名稱 In = "header",//jwt默認存放Authorization信息的位置(請求頭中) Type = "apiKey" }); #endregion }); #endregion #region Token服務注冊 services.AddSingleton<IMemoryCache>(factory => { var cache = new MemoryCache(new MemoryCacheOptions()); return cache; }); services.AddAuthorization(options => { options.AddPolicy("Admin", policy => policy.RequireClaim("AdminType").Build());//注冊權限管理,可以自定義多個 }); #endregion #region AutoFac //實例化 AutoFac 容器 var builder = new ContainerBuilder(); //注冊要通過反射創建的組件 builder.RegisterType<AdvertisementServices>().As<IAdvertisementServices>(); //將services填充到Autofac容器生成器中 builder.Populate(services); //使用已進行的組件登記創建新容器 var ApplicationContainer = builder.Build(); #endregion return new AutofacServiceProvider(ApplicationContainer);//第三方IOC接管 core內置DI容器 }
這個時候我們就把AdvertisementServices的new 實例化過程注入到了Autofac容器中,
這個時候要看明白,前邊的是實現類,后邊的是接口,順序不要搞混了。
3、構造函數方式來注入
依賴注入有三種方式(構造方法注入、setter方法注入和接口方式注入),我們平時基本都是使用其中的構造函數方式實現注入,
在BlogController中,添加構造函數,並在方法中,去掉實例化過程;
readonly IAdvertisementServices _advertisementServices;
/// <summary> /// 構造函數 /// </summary> /// <param name="advertisementServices"></param> public BlogController(IAdvertisementServices advertisementServices) { _advertisementServices = advertisementServices; }
[HttpGet("{id}", Name = "Get")] public async Task<List<Advertisement>> Get(int id) { //IAdvertisementServices advertisementServices = new AdvertisementServices();//需要引用兩個命名空間Blog.Core.IServices;Blog.Core.Services; return await _advertisementServices.Query(d => d.Id == id); }
4、效果調試,已經成功
然后運行調試,發現在斷點剛進入的時候,接口已經被實例化了,達到了注入的目的。
注意:這里經常會遇到一個錯誤:None of the constructors found with ........,
查看你的service服務,是不是用了其他的倉儲repository,但是又缺少了構造函數。
如果沒有問題,大家就需要想想,除了 Autofac 還有沒有其他的不用第三方框架的注入方法呢?聰明如你,netcore 還真自帶了注入擴展。
5、NetCore 自帶的注入實現效果
當然,我們用 Asp.net core 自帶的注入方式也是可以的,也挺簡單的,這里先說下使用方法:
// 注入 service services.AddScoped<IAdvertisementServices, AdvertisementServices>(); // 注入 repository services.AddScoped<IAdvertisementRepository, AdvertisementRepository>();
這個時候,我們發現已經成功的注入了,而且特別簡單,那為啥還要使用 Autofac 這種第三方擴展呢,我們想一想,上邊我們僅僅是注入了一個 Service ,但是項目中有那么多的類,都要一個個手動添加么,多累啊,答案當然不是滴~
四、整個 dll 程序集批量注入
1、服務程序集注入方式 —— 未解耦
通過反射將 Blog.Core.Services 和 Blog.Core.Repository 兩個程序集的全部方法注入
修改如下代碼,注意這個時候需要在項目依賴中,右鍵,添加引用 Blog.Core.Services 層和 Repository 層 到項目中,如下圖,這個時候我們的程序依賴了具體的服務:
核心代碼如下,注意這里是 Load 模式(程序集名):
var builder = new ContainerBuilder(); //注冊要通過反射創建的組件 //builder.RegisterType<AdvertisementServices>().As<IAdvertisementServices>(); var assemblysServices = Assembly.Load("Blog.Core.Services");//要記得!!!這個注入的是實現類層,不是接口層!不是 IServices builder.RegisterAssemblyTypes(assemblysServices).AsImplementedInterfaces();//指定已掃描程序集中的類型注冊為提供所有其實現的接口。 var assemblysRepository = Assembly.Load("Blog.Core.Repository");//模式是 Load(解決方案名) builder.RegisterAssemblyTypes(assemblysRepository).AsImplementedInterfaces(); //將services填充到Autofac容器生成器中 builder.Populate(services); //使用已進行的組件登記創建新容器 var ApplicationContainer = builder.Build();
其他不變,運行項目,一切正常,換其他接口也可以
到這里,Autofac依賴注入已經完成,基本的操作就是這樣,不過可能你還沒有真正體會到注入的好處,挑戰下吧,看看下邊的內容,將層級進行解耦試試!
注意,如果你不想多挑戰,直接這么引用這兩個實現層就行(Service 和 Repository),不是一定要用下邊的方案來引用解耦。
2、程序集注入 —— 實現層級引用的解耦
這是一個學習的思路,大家要多想想,可能會感覺無聊或者沒用,但是對理解項目啟動和加載,還是很有必要的。
1、項目最終只依賴抽象
最終的效果是這樣的:工程只依賴抽象,把兩個實現層刪掉,引用這兩個接口層。
2、配置倉儲和服務層的程序集輸出
將 Blog.Repository 層和 Service 層項目生成地址改成相對路徑,這樣大家就不用手動拷貝這兩個 dll 了,F6編譯的時候就直接生成到了 api 層 bin 下了:
“..\Blog.Core\bin\Debug\”
3、使用 LoadFile 加載服務層的程序集
var basePath = Microsoft.DotNet.PlatformAbstractions.ApplicationEnvironment.ApplicationBasePath;//獲取項目路徑 var servicesDllFile = Path.Combine(basePath, "Blog.Core.Services.dll");//獲取注入項目絕對路徑 var assemblysServices = Assembly.LoadFile(servicesDllFile);//直接采用加載文件的方法
3、其他依賴的接口也要注入
上邊的代碼,我們是把服務和倉儲批量注入進去了,但是如果你看我的代碼,最新的代碼中,用到了事務,工作單元,並且用到了 ISqlSugarClient
這個接口不存在於我們的service.dll 和 Repository.dll 中,所以我們上邊的批量注入,並不能夠注入進去,那我們就需要單獨對其進行注冊,可以在api層的擴展文件夾中,找到:
public static class SqlsugarSetup { public static void AddSqlsugarSetup(this IServiceCollection services) { if (services == null) throw new ArgumentNullException(nameof(services)); // 默認添加主數據庫連接 MainDb.CurrentDbConnId = Appsettings.app(new string[] { "MainDB" }); // 把多個連接對象注入服務,這里必須采用Scope,因為有事務操作 services.AddScoped<ISqlSugarClient>(o => { var listConfig = new List<ConnectionConfig>(); BaseDBConfig.MutiConnectionString.ForEach(m => { listConfig.Add(new ConnectionConfig() { ConfigId = m.ConnId.ObjToString().ToLower(), ConnectionString = m.Conn, DbType = (DbType)m.DbType, IsAutoCloseConnection = true, IsShardSameThread = false, AopEvents = new AopEvents { OnLogExecuting = (sql, p) => { // 多庫操作的話,此處暫時無效果,在另一個地方有效,具體請查看BaseRepository.cs } }, MoreSettings = new ConnMoreSettings() { IsAutoRemoveDataCache = true } //InitKeyType = InitKeyType.SystemTable } ); }); return new SqlSugarClient(listConfig); }); } }
這一塊內容涉及的比較多,還涉及到了 common 層中的 BaseDBConfig.cs 這個類,自己多多嘗試吧。
五、注入中的問題和其他說明
如果按照上邊的引用解耦的形式的話,可能我們編譯成功后,頁面能正常啟動,證明我們已經把 Service 和 Repository 兩個服務層的所有服務給注冊上了,但是訪問某一個接口,還是會出現錯誤:
這個錯誤表示,我們的 SqlSugar 服務,沒有被注冊成功,那肯定就是我們的 Sqlsugar 程序集沒有正常的引用,怎么辦呢,這里有兩種方法,請往下看。
1、Sqlsugar 加載失敗,方式一:api層引用 sugar
你可以直接在 api 層,添加對 sqlsugar 的使用,也可以直接不用修改,比如我的項目,多級之間存在級聯的關系,api >> IService >> Model >> sqlSugar :
原因:在 Api層,引入 sqlSugarCore nuget 包,因為存在層級是存在依賴的
注意!這個地方很多人不理解為啥:
解耦,並不是啥直接不用了!
解耦僅僅是去掉引用耦合,目的是不用在修改了service.dll 層的某一個方法的時候,而停到api.dll這個整個服務,
當項目啟動的時候,還是需要將所有的服務都注冊到主機里,
autofac依賴注入,僅僅是用反射的方法,將service.dll 和 repository.dll 項目服務進行注冊,這個過程和引用是一樣的,因為如果你引用,當項目編譯或啟動的時候,也是把層服務全部注入到主機的過程,
所以sqlsugar還是需要引用,因為需要這個組件的服務。
2、方式二:不引用Sugar包!但是需要拷貝 .dll 文件
如果你就想要 api 層干凈,就是不想引用 sqlsugar 層的話,那就除非是把 sugar下的所有dll文件都拷貝進去,其實這樣也是可以的,只要把第三方的nuget包生成的dll文件全部拷貝就行,你可以看下,sqlsugar依賴了很多dll
但是這個時候我們需要使用 LoadFrom 模式,因為我們上邊使用的是 LoadFile 作為反射加載,這樣的話,有一個問題,就是Repository層引用的 sqlsugar 會提示找不到,那我們就換一個反射加載方式 —— LoadFrom:
//二者的區別
Assembly.LoadFile只載入相應的dll文件,比如Assembly.LoadFile("a.dll"),則載入a.dll,假如a.dll中引用了b.dll的話,b.dll並不會被載入。 Assembly.LoadFrom則不一樣,它會載入dll文件及其引用的其他dll,比如上面的例子,b.dll也會被載入。
哦!是不是瞬間想到了什么,沒錯就是 Sqlsugar 加載問題,我們換成 LoadFrom ,並且刪除引用 sqlsugar 來看看,但是!一定要把 sqlsugar.dll 和 它依賴的 MySql.Data.dll 給拷貝進來api層;
所以總體來說有三個情況:
1、loadfile 模式 + 引用 SqlSugar2、loadfile 模式+ 引用3、loadfrom 模式+ 拷貝 .dll 文件
3、連接字符串問題
注意:如果采用上邊的方法,把 service 和 Repository 層雖然解耦了,但是必須采用 LoadFile( dll 文件) 的形式,這樣就導致了,在 startup.cs 啟動中,無法給其他類庫中的靜態屬性賦值的能力,比如:
BaseDBConfig.ConnectionString = "數據庫連接字符串";
這個在 startup.cs 的ConfigureServices 方法中,是無法生效的。解決辦法:
1、不解耦,還是采用普通辦法,引用兩個層,用 Assembly.Load("Blog.Core.Services") 方式;
2、按照上邊解耦,但是數據庫連接字符串配置,需要在 Repostory 層
// public static string ConnectionString { get; set; }
public static string ConnectionString = Appsettings.app(new string[] { "AppSettings", "RedisCaching", "ConnectionString" });//獲取連接字符串
//這個Appsettings是一個封裝的操作類,用來獲取配置文件的數據
/// <summary> /// appsettings.json操作類 /// </summary> public class Appsettings { static IConfiguration Configuration { get; set; } static string contentPath { get; set; } public Appsettings(string contentPath) { string Path = "appsettings.json"; //如果你把配置文件 是 根據環境變量來分開了,可以這樣寫 //Path = $"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")}.json"; Configuration = new ConfigurationBuilder() .SetBasePath(contentPath) .Add(new JsonConfigurationSource { Path = Path, Optional = false, ReloadOnChange = true })//這樣的話,可以直接讀目錄里的json文件,而不是 bin 文件夾下的,所以不用修改復制屬性 .Build(); } /// <summary> /// 封裝要操作的字符 /// </summary> /// <param name="sections">節點配置</param> /// <returns></returns> public static string app(params string[] sections) { try { if (sections.Any()) { return Configuration[string.Join(":", sections)]; } } catch (Exception) { } return ""; } }
4、解除Service層和Repository層之間的耦合
還記得Blog.Core.Services中的BaseServices.cs么,它還是通過new 實例化的方式在創建,仿照contrller,修改BaseServices並在全部子類的構造函數中注入:
public class BaseServices<TEntity> : IBaseServices<TEntity> where TEntity : class, new() { //public IBaseRepository<TEntity> baseDal = new BaseRepository<TEntity>(); public IBaseRepository<TEntity> baseDal;//通過在子類的構造函數中注入,這里是基類,不用構造函數 //... } public class AdvertisementServices : BaseServices<Advertisement>, IAdvertisementServices { IAdvertisementRepository dal; public AdvertisementServices(IAdvertisementRepository dal) { this.dal = dal; base.baseDal = dal; } }
好啦,現在整個項目已經完成了相互直接解耦的功能,以后就算是Repository和Service如何變化,接口層都不用修改,因為已經完成了注入,第三方Autofac會做實例化的過程。
5、容器內查看注入的服務數據
如果你想看看是否注入到容器里成功了,可以直接看看容器 ApplicationContainer 的內容:
六、 無接口項目注入
1、接口形式的注入
上邊我們討論了很多,但是都是接口框架的,
比如:Service.dll 和與之對應的 IService.dll,Repository.dll和與之對應的 IRepository.dll,
這樣,我們在多層之間使用服務的話,直接將我們需要使用的 new 對象,注入到容器里,然后我們就可以使用相應的接口了,
比如:我們想在 controller 里使用AdvertisementServices 類,那我們就可以直接使用它的接口 IAdvertisementServices,這樣就很好的達到了解耦的目的,這樣我們就可以在API層,就輕松的把 Service.dll 給解耦了;
如果我們需要在 Service類里,使用 AdvertisementRepository ,我們就直接使用對應的接口 IAdvertisementRepository,這樣,我們就從 Service 層中,把倉儲層給解耦了。
2、如果沒有接口
案例是這樣的:
如果我們的項目是這樣的,沒有接口,會怎么辦:
// 服務層類 public class StudentService { StudentRepository _studentRepository; public StudentService(StudentRepository studentRepository) { _studentRepository = studentRepository; } public string Hello() { return _studentRepository.Hello(); } } // 倉儲層類 public class StudentRepository { public StudentRepository() { } public string Hello() { return "hello world!!!"; } } // controller 接口調用 StudentService _studentService; public ValuesController(StudentService studentService) { _studentService = studentService; }
這樣的話,我們就不能使用上邊的接口注入模式了,因為我們上邊是把注入的服務,對應注冊給了接口了 .AsImplementedInterfaces() ,我們就無法實現解耦了,因為根本沒有了接口層,所以我們只能引用實現類層,這樣注入:
通過 builder.RegisterAssemblyTypes(assemblysRepository); 方法直接注入服務,沒有其他的東西。
如果你需要這個小demo,可以從我的Github里下載:https://github.com/anjoy8/Blog.Core/blob/master/Blog.Core/wwwroot/NoInterAutofacIOC.rar
3、如果是沒有接口的單獨實體類
public class Love { // 一定要是虛方法 public virtual string SayLoveU() { return "I ♥ U"; } } //--------------------------- ////只能注入該類中的虛方法 builder.RegisterAssemblyTypes(Assembly.GetAssembly(typeof(Love))) .EnableClassInterceptors() .InterceptedBy(typeof(BlogLogAOP));
七、同一接口多實現類注入
這里暫時沒有實例代碼,如果你正好需要,可以看看這個博友的栗子:https://www.cnblogs.com/fuyujian/p/4115474.html
我會在之后的時間寫個栗子放到這里。
一種:動態方式
// 定義一個接口,兩個實現類 public interface IDemoService { string Get(); } public class DemoServiceA : IDemoService { public string Get() { return "Service A"; } } public class DemoServiceB : IDemoService { public string Get() { return "Service B"; } } // 依賴注入 services.AddSingleton(factory => { Func<string, IDemoService> accesor = key => { if (key.Equals("ServiceA")) { return factory.GetService<DemoServiceA>(); } else if (key.Equals("ServiceB")) { return factory.GetService<DemoServiceB>(); } else { throw new ArgumentException($"Not Support key : {key}"); } }; return accesor; }); // 使用 private IDemoService _serviceA; private IDemoService _serviceB; private readonly Func<string, IDemoService> _serviceAccessor; public ValuesController(Func<string, IDemoService> serviceAccessor) { this._serviceAccessor = serviceAccessor; _serviceA = _serviceAccessor("ServiceA"); _serviceB = _serviceAccessor("ServiceB"); }
二種,工廠模式
// 設計工廠 public class SingletonFactory { Dictionary<Type, Dictionary<string, object>> serviceDict; public SingletonFactory() { serviceDict = new Dictionary<Type, Dictionary<string, object>>(); } public TService GetService<TService>(string id) where TService : class { var serviceType = typeof(TService); return GetService<TService>(serviceType, id); } public TService GetService<TService>(Type serviceType, string id) where TService : class { if (serviceDict.TryGetValue(serviceType, out Dictionary<string, object> implDict)) { if (implDict.TryGetValue(id, out object service)) { return service as TService; } } return null; } public void AddService<TService>(TService service, string id) where TService : class { AddService(typeof(TService), service, id); } public void AddService(Type serviceType, object service, string id) { if (service != null) { if (serviceDict.TryGetValue(serviceType, out Dictionary<string, object> implDict)) { implDict[id] = service; } else { implDict = new Dictionary<string, object>(); implDict[id] = service; serviceDict[serviceType] = implDict; } } } } // 注入工廠 SingletonFactory singletonFactory = new SingletonFactory(); singletonFactory.AddService<IServiceA>(new ImplA1(), "impla1"); singletonFactory.AddService<IServiceA>(new ImplA2(), "impla2"); services.AddSingleton(singletonFactory); // 使用 private readonly IServiceA serviceA; public HomeController(SingletonFactory singletonFactory) { this.serviceA = singletonFactory.GetService<IServiceA>("impla2"); //使用標識從SingletonFactory獲取自己想要的服務實現 }
八、簡單了解通過AOP切面實現日志記錄
什么是AOP?引用百度百科:AOP為Aspect Oriented Programming的縮寫,意為:面向切面編程,通過預編譯方式和運行期動態代理實現程序功能的統一維護的一種技術。實現AOP主要由兩種方式,
一種是編譯時靜態植入,優點是效率高,缺點是缺乏靈活性,.net下postsharp為代表者(好像是付費了。。)。
另一種方式是動態代理,優點是靈活性強,但是會影響部分效率,動態為目標類型創建代理,通過代理調用實現攔截。
AOP能做什么,常見的用例是事務處理、日志記錄等等。
常見的AOP都是配合在Ioc的基礎上進行操作,上邊咱們講了Autofac這個簡單強大的Ioc框架,下面就講講Autofac怎么實現AOP。Autofac的AOP是通過Castle(也是一個容器)項目的核心部分實現的,名為Autofac.Extras.DynamicProxy,顧名思義,其實現方式為動態代理。當然AOP並不一定要和依賴注入在一起使用,自身也可以單獨使用。
網上有一個博友的圖片,大概講了AOP切面編程
九、結語
今天的文章呢,主要說了依賴注入IoC在項目中的使用,從上邊的說明中,可以看到,最大的一個優點就是實現解耦的作用,最明顯的就是,在Controller中,不用在實例服務層或者業務邏輯層了,不過還是有些缺點的,缺點之一就是會占用一定的時間資源,效率稍稍受影響,不過和整體框架相比,這個影響應該也是值得的。
明天,我們繼續將面向切面編程AOP中的,日志記錄和面向AOP的緩存使用。
好文參考:https://www.cnblogs.com/stulzq/p/6880394.html