從壹開始先後端分離【 .NET Core2.2/3.0 +Vue2.0 】框架之九 || 依賴注入IoC學習 + AOP界面編程初探

本文3.0版本文章

 
 

本文是NetCore 2.2的,請不要套用3.0

一、若是看不懂本文,或者比較困難,先彆着急問問題,我單寫了一個關於依賴注入的小Demo,能夠下載看看,多思考思考注入的原理:html

https://github.com/anjoy8/BlogArti/tree/master/Blog.Core_IOC%26DI前端

 

二、重要:若是你實現瞭解耦,也就是 api 層只引用了 IService 和 IRepository 的話,那每次修改 service 層,都須要清理解決方案,從新編譯項目,由於這個時候你的api層的dll,仍是以前未修改的代碼。vue

 

三、重要+ :請注意,依賴注入的目的不是爲了解耦,依賴注入是爲了控制反轉,通俗來講,就是不用咱們本身去 new 服務實例了,因此你們不須要必定去解耦(好比下文說到的我沒有引用 Service層 和 Repository層),我下一個DDD系列,依賴注入就沒有解耦,由於我用的是自帶的注入,不是Autofac的反射dll ,我解耦的目的,是爲了讓你們更好的理解,服務是怎麼注入到宿主容器裏的。git

 

  說接上文,上回說到了《八 || API項目總體搭建 6.3 異步泛型+依賴注入初探》,後來的標題中,我把倉儲兩個字給去掉了,由於好像你們對這個模式頗有不一樣的見解,嗯~可能仍是我學藝不精,沒有說到其中的好處,如今在學DDD領域驅動設計相關資料,有了好的靈感再給你們分享吧。github

  到目前爲止咱們的項目已經有了基本的雛形,後端其實已經能夠搭建本身的接口列表了,框架已經有了規模,本來應該說vue了,可是呢,又據說近來Vue-cli已經從2.0升級到了3.0了,還變化挺大,前端大佬們,都不停歇呀。固然我還在學習當中,我也須要了解下有關3.0的特性,但願給沒有接觸到,或者剛剛接觸到的朋友們,有一些幫助,固然我這個不是隨波逐流,只是在衆多的博文中,給你們一個入門參考,屆時說3.0的時候,仍是會說2.0的相關問題的。sql

  雖然項目總體能夠運行了,可是我還有幾個小知識點要說下,主要是一、依賴注入和AOP相關知識;二、跨域代理等問題(由於Vue是基於Node開發的,與後臺API接口不在同一個地址);三、實體類的DTO相關小問題;四、Redis緩存等;五、部署服務器中的各類坑;雖然都是很小的知識點,我仍是都下給你們說下的,好啦,開始今天的講解;數據庫

零、今天完成的綠色部分

 

 

1、依賴注入的理解和思考

 

更新(19-04-17):若是想好好的理解依賴注入,能夠從如下幾個方面入手:
一、項目之間引用是如何起做用的,好比爲啥 api 層只是引用了 service 層,那爲啥也能使用 repository 和 model 等多層的類?編程

二、項目在啓動的時候,也就是運行時,是如何動態 獲取和訪問 每個對象的實例的?也就是 new 的原理json

三、項目中有 n 個類,對應 m 個實例等,那這些服務,都放在了哪裏?確定每個項目都有專屬本身的一塊。若是項目不啓動的話,內存裏確定是沒有這些服務的。後端

四、使用接口(面向抽象)的好處?

五、在項目後期,如何業務須要要所有修改接口的實現類,好比想把 IA = new A();所有  改爲 IA = new B();

六、反射的重要性,爲何要用到反射 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() ,此時不斷的更改代碼,就顯得內心很差了,若是採用依賴注入,就顯得特別舒暢。

我有一個我的的理解,不知道恰當與否,好比咱們平時食堂吃飯,都是食堂本身炒的菜,就算是配方都同樣,控制者(廚師)仍是會有些變化,或者是出錯,可是肯德基這種,由總店提供的,店面就失去了控制,就出現了第三方(總店或者工廠等),這就是實現了控制反轉,咱們只須要說一句,奧爾良雞翅,嗯就拿出來一個,並且所有分店的都同樣,咱們不用管是否改配方,無論是否依賴哪些調理食材,哈哈。

2、常見的IoC框架有哪些

一、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自身的容器仍是比較簡單,若是想要更多的功能和擴展,仍是須要使用上邊上個框架。

 

二、三種注入的生命週期 

權重:

AddSingleton→AddTransient→AddScoped

AddSingleton的生命週期:

項目啓動-項目關閉   至關於靜態類  只會有一個  

AddScoped的生命週期:

請求開始-請求結束  在此次請求中獲取的對象都是同一個 

AddTransient的生命週期:

請求獲取-(GC回收-主動釋放) 每一次獲取的對象都不是同一個

 

這裏來個簡單的小DEMO:

一、定義四個接口,並分別對其各自接口實現,目的是測試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() { } }

 

二、項目注入

 

 

三、控制器調用

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"; }

 

四、開始測試,三種注入方法出現的狀況 

請求SetTest // GET api/values

 

AddSingleton的對象沒有變

AddScoped的對象沒有變化

AddTransient的對象發生變化

------------------------------------------------------------

請求 // GET api/values/5

 

AddSingleton的對象沒有變

AddScoped的對象發生變化

AddTransient的對象發生變化

 

注意:

因爲AddScoped對象是在請求的時候建立的

因此不能在AddSingleton對象中使用

甚至也不能在AddTransient對象中使用

 

因此權重爲

AddSingleton→AddTransient→AddScoped

 

否則則會拋以下異常

 

3、較好用的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); }

 

接下來咱們就須要作處理:

 

一、引入nuget包

在Nuget中引入兩個:Autofac.Extras.DynamicProxy(Autofac的動態代理,它依賴Autofac,因此能夠不用單獨引入Autofac)、Autofac.Extensions.DependencyInjection(Autofac的擴展)

 

 

二、接管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容器中,

這個時候要看明白,前邊的是實現類,後邊的是接口,順序不要搞混了。

 

三、構造函數方式來注入

依賴注入有三種方式(構造方法注入、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); }

 

 

四、效果調試,已經成功

而後運行調試,發如今斷點剛進入的時候,接口已經被實例化了,達到了注入的目的。

 

注意:這裏常常會遇到一個錯誤:None of the constructors found with ........,

查看你的service服務,是否是用了其餘的倉儲repository,可是又缺乏了構造函數。

 

 

若是沒有問題,你們就須要想一想,除了 Autofac 還有沒有其餘的不用第三方框架的注入方法呢?聰明如你,netcore 還真自帶了注入擴展。

 

五、NetCore 自帶的注入實現效果

固然,咱們用 Asp.net core 自帶的注入方式也是能夠的,也挺簡單的,這裏先說下使用方法:

// 注入 service
 services.AddScoped<IAdvertisementServices, AdvertisementServices>(); // 注入 repository
 services.AddScoped<IAdvertisementRepository, AdvertisementRepository>();

 

這個時候,咱們發現已經成功的注入了,並且特別簡單,那爲啥還要使用 Autofac 這種第三方擴展呢,咱們想想,上邊咱們僅僅是注入了一個 Service ,可是項目中有那麼多的類,都要一個個手動添加麼,多累啊,答案固然不是滴~

 

 

4、整個 dll 程序集批量注入

一、服務程序集註入方式 —— 未解耦

經過反射將 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),不是必定要用下邊的方案來引用解耦。

 


 

二、程序集註入 —— 實現層級引用的解耦

 

這是一個學習的思路,你們要多想一想,可能會感受無聊或者沒用,可是對理解項目啓動和加載,仍是頗有必要的。

一、項目最終只依賴抽象

最終的效果是這樣的:工程只依賴抽象,把兩個實現層刪掉,引用這兩個接口層。

 

 

二、配置倉儲和服務層的程序集輸出

將 Blog.Repository 層和 Service 層項目生成地址改爲相對路徑,這樣你們就不用手動拷貝這兩個 dll 了,F6編譯的時候就直接生成到了 api 層 bin 下了:


「..\Blog.Core\bin\Debug\」

 

 

三、使用 LoadFile 加載服務層的程序集

var basePath = Microsoft.DotNet.PlatformAbstractions.ApplicationEnvironment.ApplicationBasePath;//獲取項目路徑
 var servicesDllFile = Path.Combine(basePath, "Blog.Core.Services.dll");//獲取注入項目絕對路徑
 var assemblysServices = Assembly.LoadFile(servicesDllFile);//直接採用加載文件的方法

 

三、其餘依賴的接口也要注入

上邊的代碼,咱們是把服務和倉儲批量注入進去了,可是若是你看個人代碼,最新的代碼中,用到了事務,工做單元,而且用到了 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 這個類,本身多多嘗試吧。

 

 

 

5、注入中的問題和其餘說明

若是按照上邊的引用解耦的形式的話,可能咱們編譯成功後,頁面能正常啓動,證實咱們已經把 Service 和 Repository 兩個服務層的全部服務給註冊上了,可是訪問某一個接口,仍是會出現錯誤:

 

 

這個錯誤表示,咱們的 SqlSugar 服務,沒有被註冊成功,那確定就是咱們的 Sqlsugar 程序集沒有正常的引用,怎麼辦呢,這裏有兩種方法,請往下看。

 

一、Sqlsugar 加載失敗,方式一:api層引用 sugar 

你能夠直接在 api 層,添加對 sqlsugar 的使用,也能夠直接不用修改,好比個人項目,多級之間存在級聯的關係,api >> IService >> Model >> sqlSugar :

 

緣由:在 Api層,引入 sqlSugarCore nuget 包,由於存在層級是存在依賴的

注意!這個地方不少人不理解爲啥:

解耦,並非啥直接不用了!
解耦僅僅是去掉引用耦合,目的是不用在修改了service.dll 層的某一個方法的時候,而停到api.dll這個整個服務,
當項目啓動的時候,仍是須要將全部的服務都註冊到主機裏,
autofac依賴注入,僅僅是用反射的方法,將service.dll 和 repository.dll 項目服務進行註冊,這個過程和引用是同樣的,由於若是你引用,當項目編譯或啓動的時候,也是把層服務所有注入到主機的過程,
因此sqlsugar仍是須要引用,由於須要這個組件的服務。

 

二、方式二:不引用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層;

因此整體來講有三個狀況:

一、loadfile 模式 + 引用 SqlSugar
二、loadfile 模式+ 引用 
三、loadfrom 模式+ 拷貝 .dll 文件

 

 

 

 

三、鏈接字符串問題

注意:若是採用上邊的方法,把 service 和 Repository 層雖然解耦了,可是必須採用 LoadFile( dll 文件) 的形式,這樣就致使了,在 startup.cs 啓動中,沒法給其餘類庫中的靜態屬性賦值的能力,好比:

            BaseDBConfig.ConnectionString = "數據庫鏈接字符串";

這個在 startup.cs 的ConfigureServices 方法中,是沒法生效的。解決辦法:

一、不解耦,仍是採用普通辦法,引用兩個層,用 Assembly.Load("Blog.Core.Services") 方式;

二、按照上邊解耦,可是數據庫鏈接字符串配置,須要在 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 ""; } }

 

 

 

四、解除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會作實例化的過程。

 

五、容器內查看注入的服務數據

若是你想看看是否注入到容器裏成功了,能夠直接看看容器 ApplicationContainer 的內容:

 

 

6、 無接口項目注入

一、接口形式的注入

上邊咱們討論了不少,可是都是接口框架的,

好比:Service.dll 和與之對應的 IService.dll,Repository.dll和與之對應的 IRepository.dll,

這樣,咱們在多層之間使用服務的話,直接將咱們須要使用的 new 對象,注入到容器裏,而後咱們就可使用相應的接口了,

好比:咱們想在 controller 裏使用AdvertisementServices 類,那咱們就能夠直接使用它的接口 IAdvertisementServices,這樣就很好的達到了解耦的目的,這樣咱們就能夠在API層,就輕鬆的把 Service.dll 給解耦了;

若是咱們須要在 Service類裏,使用 AdvertisementRepository ,咱們就直接使用對應的接口 IAdvertisementRepository,這樣,咱們就從 Service 層中,把倉儲層給解耦了。

 

 

二、若是沒有接口

案例是這樣的:

 若是咱們的項目是這樣的,沒有接口,會怎麼辦:

// 服務層類 
   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

 

三、若是是沒有接口的單獨實體類

 

public class Love { // 必定要是虛方法
        public virtual string SayLoveU() { return "I ♥ U"; } } //---------------------------

////只能注入該類中的虛方法
builder.RegisterAssemblyTypes(Assembly.GetAssembly(typeof(Love))) .EnableClassInterceptors() .InterceptedBy(typeof(BlogLogAOP));

 

7、同一接口多實現類注入

這裏暫時沒有實例代碼,若是你正好須要,能夠看看這個博友的栗子: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獲取本身想要的服務實現
        }

 

 

 

 

8、簡單瞭解經過AOP切面實現日誌記錄

什麼是AOP?引用百度百科:AOP爲Aspect Oriented Programming的縮寫,意爲:面向切面編程,經過預編譯方式和運行期動態代理實現程序功能的統一維護的一種技術。實現AOP主要由兩種方式,

一種是編譯時靜態植入,優勢是效率高,缺點是缺少靈活性,.net下postsharp爲表明者(好像是付費了。。)。

另外一種方式是動態代理,優勢是靈活性強,可是會影響部分效率,動態爲目標類型建立代理,經過代理調用實現攔截。

AOP能作什麼,常見的用例是事務處理、日誌記錄等等。

常見的AOP都是配合在Ioc的基礎上進行操做,上邊我們講了Autofac這個簡單強大的Ioc框架,下面就講講Autofac怎麼實現AOP。Autofac的AOP是經過Castle(也是一個容器)項目的核心部分實現的,名爲Autofac.Extras.DynamicProxy,顧名思義,其實現方式爲動態代理。固然AOP並不必定要和依賴注入在一塊兒使用,自身也能夠單獨使用。

網上有一個博友的圖片,大概講了AOP切面編程

 

 

9、結語

  今天的文章呢,主要說了依賴注入IoC在項目中的使用,從上邊的說明中,能夠看到,最大的一個優勢就是實現解耦的做用,最明顯的就是,在Controller中,不用在實例服務層或者業務邏輯層了,不過仍是有些缺點的,缺點之一就是會佔用必定的時間資源,效率稍稍受影響,不過和總體框架相比,這個影響應該也是值得的。

  明天,咱們繼續將面向切面編程AOP中的,日誌記錄和麪向AOP的緩存使用。

 

好文參考:http://www.javashuo.com/article/p-ohmxlmzc-dq.html

 

 

10、CODE

 https://github.com/anjoy8/Blog.Core

https://gitee.com/laozhangIsPhi/Blog.Core

相關文章
相關標籤/搜索