(給DotNet加星标,提升.Net技能)
轉自:JerryMouseLi cnblogs.com/JerryMouseLi/p/12080618.html
一、背景
.NET Core 3.1是微軟LTS長期3年支援版本,正式釋出于2019-12-03,并且在Windows平台上支援了WinFrom跟WPF桌面應用。
本文介紹了使用WinForm時的第一步,将應用層以及ORM涉及到的DBconfig,倉儲層等依賴注入到容器中,并通過構造函數法從容器中調用執行個體,供給各窗體控件使用。
備注:本文的依賴注入講解基于微軟原生自帶的DI,通過Ninject或者AutoFac可自行仿照操作,原理相通。
二、依賴注入
2.1、依賴注入是什麼?
依賴注入是通過反轉控制(IOC),設計模式屬于代理模式+工廠模式,由serviceProvider根據執行個體接口或者執行個體類型調用,注入時生命周期的設定,控制執行個體化及配置執行個體生命周期,并傳回執行個體給程式員調用,進而達到解放程式員的生産力,不用再去new 一個個執行個體,也不用去考慮執行個體之間的依賴關系,也不用去考慮執行個體的生命周期。實作,分為三個階段,第一,程式員将服務注入服務容器階段,第二程式員DI執行個體調用階段,第三serviceProvider服務管理者根據注入時的配置傳回給程式對應的執行個體以及配置好執行個體的生命周期。
一張圖就可以了解依賴注入執行個體調用過程
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbw5SN5MWM4QGZiZGO0cDN4QmNjN2M3cDZygDM5QTYyMmZj9CX0JXZ252bj91Ztl2Lc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
這裡再向讀者做個說明ServiceCollection是服務容器,serviceProvider是服務管理者,管理着服務容器,當程式發送抽象接口,或者類型時,serviceProvider會根據設定好的生命周期,傳回需要的執行個體配置好執行個體的生命周期給程式員使用。
2.2、依賴注入的目的
通過代理模式serviceProvider控制反轉,他将持有控制權,将所有需要用到的接口,類型,反射出對應的執行個體,執行個體化以及設定好執行個體的生命周期,然後将控制權返還給程式員,不用再去new 一個個執行個體,也不用去考慮執行個體之間的依賴關系,也不用去考慮執行個體的生命周期,最終目的就是解放程式員的生産力,讓程式員更輕松地寫程式。
2.3、依賴注入帶來的好處
2.3.1、生命周期的控制
在注入的同時可以設定如下三種生命周期:
-
Transient
每次注入時,都重新 new 一個新的執行個體。
-
Scoped
每個 Request 都重新 new 一個新的執行個體,同一個 Request 不管經過多少個 Pipeline 都是用同一個執行個體。
-
Singleton
被執行個體化後就不會消失,程式運作期間隻會有一個執行個體。
2.3.1.1、生命周期測試舉例
定義同一個例子對應三個不同生命周期的接口
public interface ISample
{
int Id { get; }
}
public interface ISampleTransient : ISample
{
}
public interface ISampleScoped : ISample
{
}
public interface ISampleSingleton : ISample
{
}
public class Sample : ISampleTransient, ISampleScoped, ISampleSingleton
{
private static int _counter;
private int _id;
public Sample(){
_id = ++_counter;
}
public int Id => _id;
}
将對應的服務與接口注冊到容器中
public class Startup
{
public void ConfigureServices(IServiceCollection services){
services.AddTransient();
services.AddScoped();
services.AddSingleton();// Singleton 也可以用以下方法注冊// services.AddSingleton(new Sample());
}
}
Controller中擷取對應DI執行個體的HashCode
public class HomeController : Controller
{
private readonly ISample _transient;
private readonly ISample _scoped;
private readonly ISample _singleton;
public HomeController(
ISampleTransient transient,
ISampleScoped scoped,
ISampleSingleton singleton){
_transient = transient;
_scoped = scoped;
_singleton = singleton;
}
public IActionResult Index() {
ViewBag.TransientId = _transient.Id;
ViewBag.TransientHashCode = _transient.GetHashCode();
ViewBag.ScopedId = _scoped.Id;
ViewBag.ScopedHashCode = _scoped.GetHashCode();
ViewBag.SingletonId = _singleton.Id;
ViewBag.SingletonHashCode = _singleton.GetHashCode();
return View();
}
}
VewBag 顯示元件
"1"><tr><td colspan="3">Cotrollertd>tr><td>Lifetimestd><td>Idtd><td>Hash Codetd>tr><td>Transienttd><td>@ViewBag.TransientIdtd><td>@ViewBag.TransientHashCodetd>tr><td>Scopedtd><td>@ViewBag.ScopedIdtd><td>@ViewBag.ScopedHashCodetd>tr><td>Singletontd><td>@ViewBag.SingletonIdtd><td>@ViewBag.SingletonHashCodetd>tr>
</table>
可自行做測試,具體可參考https://blog.johnwu.cc/article/ironman-day04-asp-net-core-dependency-injection.html
2.2.2、實作了展現層(調用者)與服務類之間的解耦
如上,執行個體是在HomeController中通過接口來調用執行個體的,是以修改程式隻需要在執行個體中需改,而不需要在調用層修改。
這符合了6大程式設計原則中的依賴倒置原則:
1、高層子產品不應該依賴于低層子產品,兩者都應該依賴其抽象
展現層Controller沒有依賴Model層Sample類,兩者都依賴了Sample的接口抽象ISample,ISampleTransient,ISampleScoped,ISampleSingleton.
2、抽象不應該依賴于細節
接口層隻定義規範,沒有定義細節。
3、細節應該依賴于抽象
DI中取執行個體依賴于接口:
服務類的實作也依賴于接口:
2.2.3、開發者不用再去考慮依賴之間的關系
使程式員不用再去考慮各個DI執行個體之間的依賴,以及new很多個互相依賴的執行個體。
2.3 依賴注入使用的設計模式
2.3.1、代理模式
在依賴注入的服務調用的地方,容器管理者serviceProvider從程式員手中取得控制權,控制所需服務執行個體化以及設定好他的生命周期,然後傳回給程式員。
2.3.2、工廠模式
根據DI的生命周期設定,根據接口或者類型,生産出各種生命周期的執行個體,需要注意的是這裡有可能是同一執行個體(scope的單次請求中,或者Transient生命周期),Transient每次産生的都是新的執行個體。
三、在.NET Core 3.1上基于WinForm實作依賴注入
3.1、NET Core 3.1中對WinForm的支援
筆者發現在最新的VS發行版中,能建立winform工程,但卻無法打開設計器,也無法打開winform的工具箱。怎麼辦?
在微軟官方部落格中提到在VS16.5預覽版中支援了winform設計器,根據部落格中提到,需要在此下載下傳連結下載下傳VS16.5預覽版。
NET Core 3.1 Winform截圖如下:
.net winform panel 不重新整理_.NET Core 3.1上基于WinForm實作依賴注入執行個體
可以看到控件明顯比基于dot Net Framework的好看很多,同時,工具箱中的控件很少,微軟把一些老的已經有替代的控件删除了,并且以後會慢慢加入一些必要的控件。
3.2、WinForm依賴注入與NET Core MVC的不同?
.NET Core MVC容器是自動建立好的,隻需要在ConfigureServices方法裡配置服務即可。而在Net Core3.1上建立了winform工程之後窗體是new執行個體,以單例的形式跑的。容器的配置建立,都需要自己來做。
那如果需要向Form窗體中注入服務就需要在new執行個體的時候就傳入實參。
調用的時候用窗體的構造函數調用服務接口即可。
本方法摘自此文
這樣至少有兩個缺點:
1、Form1中構造函數的依賴注入執行個體調用洩露在了他的調用層,這不符合6大程式設計原則中的依賴倒置原則;
2、當Form1中需要從DI中增加接口執行個體調用時,也需要在如下調用代碼中增加對應實參。而且實參多了,會很冗長。
3.3、解決3.2的思路
把form的類型也以單例的形式注入到容器中,調用時,擷取MainForm類型的服務。這樣此服務執行個體依賴于其他的服務。ServiceProvider容器管理者會自動解決好服務之間的依賴關系,并将對應的服務執行個體化并根據生命周期設定好,交給程式員去使用。問題完美解決。
此思路有借鑒于以下兩篇文章
微軟MSDN
stackoverflow
這裡向大家重點推薦下stackoverflow,這個基于世界級的程式員論壇,在我遇到很多的疑難雜症,孤立無援的時候,他都會給予我解決問題的思路,方向甚至方案,再次緻敬感謝stackoverflow,同時也感謝谷歌。
3.4、代碼實作
3.4.1、在Program.cs中建立服務注冊靜态方法
這裡需要說明的是,筆者這裡的IoC是應用層,展現層,倉儲層分層注入了,每層都寫了ServiceCollection服務容器的靜态方法,是以服務可以在各層注入,讀者可以不去追究,将自己的服務注入在此即可。
分層注入:
.net winform panel 不重新整理_.NET Core 3.1上基于WinForm實作依賴注入執行個體
分層注入簡單實作
CameraDM_Service注冊在了ApplicationServiceIoC,ApplicationServiceIoC注冊在了ConfigureServices。這就是我剛說的分層注入每層的依賴。
重點關注
将窗體類型注入,當然後續加入其它窗體也可用同樣方法進行注入。
3.4.2、建立服務容器對象
3.4.3、添加服務注冊
此步驟調用的就是3.4.1中的方法。
3.4.4、建構ServiceProvider對象
3.4.5、運作MainForm服務
向服務管理者請求MainForm類型的執行個體服務,具體調用過程詳見2.1。
這一步是重點,也是winform跟MVC使用上的差別,但是本質卻是相同的,都是由serviceProvider管理着WPF,winform或者MVC這些執行個體以及他們對應的類型,隻不過MVC容器已經建立好了,容器管理者serviceProvider也已經建立好了,直接往容器裡Add服務即可,而winform,WPF,net core控制台程式需要我們自己去往容器裡添加注冊服務,并且建立容器管理者serviceProvider。因為ServiceCollection容器是死的,隻有建立了serviceProvider容器管理者這個代理角色,容器才能展現出他的價值。而隻有serviceProvider,沒有ServiceCollection裡的服務也是毫無意義的。
3.4.1到3.4.5整體代碼如下:
3.4.6、構造函數法調用DI執行個體
3.5、示範效果
點選按鈕之後從攝像頭服務中擷取到了攝像頭的數量。
.net winform panel 不重新整理_.NET Core 3.1上基于WinForm實作依賴注入執行個體
點選确定之後從攝像頭服務中擷取到了3号攝像頭的安裝時間。
.net winform panel 不重新整理_.NET Core 3.1上基于WinForm實作依賴注入執行個體
3.6、如何調用用依賴注入在母窗體中調用子窗體
問題如下:
假設在MainForm中的button1_Click,還需要打開其他視窗,要如何實作?(隻能往 MainForm 中傳遞serviceProvider嗎?)
把serviceProvider設計成全局靜态的,可設計成單例模式或直接放在Main的屬性中,供全局任意子窗體通路擷取DI執行個體即可,當然同時,其他窗體也需要注入到容器中。
3.6.1、注入子窗體
注入生命周期為瞬時的Form1類型。
因為Form1是MainForm的子窗體,而MainForm設定成了單例模式,是以在MainForm中打開Form1是屬于同一次請求,姑不能用AddSingleton跟AddScope模式。如果使用以上兩種模式,會報如下異常:
比如設定Form1生命周期為單例模式
第一次調用正常,
.net winform panel 不重新整理_.NET Core 3.1上基于WinForm實作依賴注入執行個體
關閉Form1第二次點選MainForm的button1時,報如下異常:
.net winform panel 不重新整理_.NET Core 3.1上基于WinForm實作依賴注入執行個體
因為是單例模式,我們關閉了Form1,MainForm還在,再次點選button1,會找不到生命周期是單例模式的Form1執行個體,就會報如上異常。
修改成
問題得到完美解決,無論關閉多少次Form1,都能通過MainForm的button1調用打開Form1。
3.6.2、設定全局serviceProvider容器服務管理者
修改serviceProvider為Program靜态類的公用屬性(全局),以給子窗體或其他winform中的元件來容器服務者擷取DI執行個體
Program.cs全部代碼如下。
3.6.3、MainForm中調用Form1
到Program服務管理者屬性手中拿到對應所需類型的設定好生命周期的Form1執行個體。顯示Form1。
3.6.4、構造函數法調用DI執行個體
Form1調用camera服務
此處同3.5
3.6.5、效果
.net winform panel 不重新整理_.NET Core 3.1上基于WinForm實作依賴注入執行個體
四、最後
本來就想寫篇短文,誰知道洋洋灑灑還寫得有點長。本文如果大家讀了有疑惑,請提出來,我會耐心解答;如果知識點上有不妥當不正确或者不同見解的地方,也懇請指出,我同時也很渴望進步。
推薦閱讀 點選标題可跳轉開源APM系統HttpReports在.NET Core的應用深入了解ASP.NET Core 依賴注入【5min+】 巨大的争議?C# 8中的接口
看完本文有收獲?請轉發分享給更多人
關注「DotNet」加星标,提升.Net技能
.net winform panel 不重新整理_.NET Core 3.1上基于WinForm實作依賴注入執行個體
好文章,我在看❤️