在WPF中使用依賴注入的方式建立視圖

在WPF中使用依賴注入的方式建立視圖

0x00 問題的產生

互聯網時代桌面開發真是愈來愈少了,不少應用都轉到了瀏覽器端和移動智能終端,相應的軟件開發上的新技術應用到桌面開發的文章也不多。我以前主要作WPF,今年開始學習Web應用開發,因而就接觸到了.NET Core,其中的不少概念很值得在桌面開發中借鑑。例如在.NET Core MVC中,Controller的依賴是經過構造函數注入的,注入的過程由框架實現,咱們在寫Controller時只要在構造函數參數中羅列出要依賴的服務便可,進一步的,把服務抽象爲接口,那麼核心的業務邏輯就完全解耦出來了,依賴的服務能夠是任意的實現方式(固然前提是要知足需求)。WPF通常都是用MVVM模式開發,那麼是否是可讓ViewModel對其它服務的依賴也經過構造函數自動注入,而不是每次都要new出一個ViewModel呢?這篇文章主要就討論這個問題,並嘗試寫了個View和ViewModel的容器來實現。git

0x01 最初的設計

.NET Core MVC中之因此能作到Controller的依賴自動注入,主要就是由於Controller實例是由MVC框架建立的。咱們要想讓ViewModel中的依賴自動注入,那麼這個ViewModel確定須要自動建立。考慮到View與ViewModel之間的對應也算是一種依賴關係,那麼就能夠把View和ViewModel之間的這種對應關係以及其它服務的依賴關係都放到容器裏,當須要View的時候,根據View的類型從容器中找到對應的ViewModel,而後根據ViewModel的依賴,從容器中獲取服務,而後把View的DataContext設置爲ViewModel的實例,最終返回View,那麼就實現了ViewModel的自動依賴注入了。github

0x02 更進一步的設計

按照上面那個方案我寫了一個簡易的依賴注入容器,證實是能夠用的。不過要想真正在相對嚴肅一點的環境中開發,對依賴注入容器的要求就不是那麼簡單了。我須要花時間去開發一個嚴謹一點的依賴注入容器,這不只須要時間,關鍵水平有限,目前市面上已經存在了不少優秀的依賴注入容器,我不必造輪子(爲了學習或更深刻理解原理而去造輪子的行爲不在此列),但常見的依賴注入容器在配置服務時(例如綁定A和B)通常都限制B對A有繼承關係,因此現有的依賴注入容器沒法配置View和ViewModel的依賴。所以考慮把View和ViewModel的依賴關係單獨存到一個容器中,服務的依賴放到第三方容器,爲了可以適配第三方容器,能夠提供一個接口,經過接口對第三方容器進行簡易的包裝便可使用,這樣就能夠任意選擇本身喜歡的強大的第三方依賴注入容器了。瀏覽器

 

0x03 部分代碼和示例

在開始看代碼以前,先說一下存儲View和ViewModel關係的容器AvalonContainer(後面簡稱View容器),使用這個容器的Wire方法能夠配置View和ViewModel之間的對應關係,GetView方法能夠獲取View,同時給View的DataContext配置好了指定的ViewModel,而且ViewModel注入了依賴。要建立一個AvalonContainer須要在構造函數中傳入IContainer對象,這個接口用於對第三方依賴注入容器實現包裝,以便用於AvalonContainer,第三方依賴注入容器主要做用是從中獲取ViewModel的依賴,以及往容器中添加ViewModel(若是須要的話)。框架

我本身寫的依賴注入容器太簡易了,當時只是用來測試,實際應用中應該都會使用第三方容器,因此示例直接用的第三方容器Ninject。函數

核心的步驟是建立一個Ninject容器,用Ninject容器綁定依賴,而後用Ninject容器建立View容器,配置View和ViewModel依賴。這樣須要時就能夠直接從View容器建立View,得到的View的DataContext已經設置爲ViewModel實例並注入了ViewModel的依賴。學習

 

ViewModel中通常在構造函數參數中注入依賴。對於不一樣的依賴注入容器,也能夠經過給屬性配置相應的Attribute的方式聲明依賴注入,不過這種方式對ViewModel的侵入太強了,並且不一樣的依賴注入容器每每提供不一樣的Attribute,更換時會比較麻煩,仍是構造函數注入比較好,更換依賴注入容器不會產生影響。下面截圖是TestOneView對應的ViewModel,在構造函數中注入了倉儲和日誌的依賴,感受就像.NET Core MVC中的Controller。測試

 

當須要OneTestView窗口時,能夠以下圖所示建立並顯示。spa

 

爲了可以適配任意的第三方依賴注入容器,提供了IContainer接口,在使用第三方依賴注入容器時須要經過這個接口適配一下,這種感受就像電腦輸出接口能夠有HDIM、DVI、VGA,顯示器輸入接口只有VGA,須要轉接頭來轉換一下。設計

 

其中Get方法用於從第三方容器中獲取ViewModel並注入依賴,Wire<T>()方法用於往第三方容器中添加ViewModel。其中token是針對自帶依賴注入容器的,徹底能夠忽略無論。日誌

其實對於Ninject來講是徹底不須要Wire這個方法的,由於即便這個類型沒有添加到容器中,在Get時Ninject也會建立對象並注入其中的依賴,因此對Ninject的包裝以下,Wire方法直接忽略便可。但不能保證全部的第三方依賴注入容器都有這個特性,因此仍是保留了這個接口。

這樣依賴注入容器和View容器經過IContainer解耦,更換依賴注入容器不會影響到業務邏輯。

若是由於某些特殊緣由須要給同一個View綁定不一樣的ViewModel,能夠在Wire時提供token參數,在GetView時使用一樣的token參數便可獲取相應的ViewModel。

0x04 寫在最後

View容器寫好後本身用了下感受還能夠,但由於ViewModel是動態添加的,因此沒法在設計時看到數據,這確實是個問題。另外要說下起名字真的很難,以前大多數都是出於學習/練習的目的,就直接加個Ayx前綴,不過此次想發佈一下,考慮到WPF開發代號是Avalon,就把它叫了AvalonDI。最後關於配置View和ViewModel依賴的方法,在NInject中是用的Bind,這個感受比較好理解。不過我以爲把接口和接口的實現綁定到一塊兒,用裝配/組裝更貼切。想像一下,電視提供了標準輸入接口,咱們能夠接錄像機、遊戲機、電腦。一樣遊戲機提供了接口,能夠插不一樣的卡帶、不一樣的手柄,當把他們連在一塊兒時,用Wire感受更合適一點。

Github:https://github.com/durow/AvalonDI

nuget:Install-Package Ayx.AvalonDI

samples裏面提供了一個WpfSample,用的自帶的依賴注入容器,一個NinjectSample,用的Ninject做爲依賴注入容器。

 


更多內容歡迎訪問個人博客:http://www.durow.vip

相關文章
相關標籤/搜索