序:忙碌多事的八月帶着些許的倦意早已步入尾聲,金秋九月承載着抗戰勝利70周年的喜慶撲面而來。沒來得及任何準備,似乎也不需要任何準備,因為生活不需要太多将來時。每天忙着上班、加班、白加班,忘了去憤,忘了去算計所謂的價值。天津爆炸事故時刻警示着我們生命的無常,逝者安息,活着的人生活還得繼續,珍惜生命,遠離傷害。武漢,這座炙熱的城市,雖值金秋,卻依然經受着“秋老虎”的烘烤,馬路上蒸騰的熱氣迎面襲來,全身毛孔張開,汗流不止,在這般高溫下,似乎汗水都要被榨幹,其實,被榨幹的何止是汗水!!!籲!籲!籲!說好的MEF呢?說好的面向接口程式設計呢?都快奔三張的人了,還學着小年輕玩無病呻吟,有點裝嫩的味道。沒辦法,思想脫缰了,有點野性難馴的意思了。好啦,不扯啦,進入今天的正題吧。
前面兩篇分别介紹了下MEF的簡單用法和MEF與倉儲模式的結合使用,這章來個終結吧。毛爺爺教導我們,做事要有始有終。本篇,部落客打算通過分享一個面向接口程式設計的架構來說明使用MEF的靈活性。
1、面向接口程式設計:有一定程式設計經驗的博友應該都熟悉或者了解這種程式設計思想,層和層之間通過接口依賴,下層不是直接給上層提供服務,而是定義一組接口供上層調用。至于具體的業務實作,那是開發中需要做的事情,在項目架構階段,隻需要定義好層與層之間的接口依賴,将架構搭起來,編譯可以直接通過。為什麼要有這麼一種設計?既然是架構設計,當然是為了提高架構的靈活性,降低層和層之間的依賴(耦合)。這個并非一句兩句講得清楚的,更多詳細可以參看:面向接口程式設計詳解(一)——思想基礎。此文我覺得分析比較到位。好了,不說廢話,來看代碼。
2、部落客本着“不講清楚誓不罷休”的原則,自己從零開始搭了一個簡單的架構Demo,當然,可能對于大牛們來說是沒太大價值的,但請不要笑話部落客不斷探索的勇氣。先來看看架構大概的結構吧。

首先說明下各層次的意思:
一、ESTM.Client
ESTM.Client.Winform:Winform項目,使用者UI展現,這個沒什麼好說的。
ESTM.Client.IBLL:用戶端IBLL接口層,用于定義用戶端的業務接口,記住這裡僅僅是向UI層提供接口功能。
ESTM.Client.BLL:用戶端BLL實作層,用于用戶端IBLL接口層的實作,提供UI層真是業務邏輯。
二、ESTM.Common
ESTM.Common.Model:通用DTOModel層,注意,這裡不是EF的實體Model,而是另外定義的一個資料轉換的Model層。
三、ESTM.Service
ESTM.Service.WCF:WCF宿主項目,用于提供WCF的接口契約和實作。這裡用WCF的目的是為了隔離用戶端和服務端的代碼。
ESTM.Service.IBLL:服務端IBLL接口層,用于定義WCF層的業務接口,和ESTM.Client.IBLL層的功能類似。
ESTM.Service.BLL:服務端BLL實作層,實作服務端IBLL接口層。
ESTM.Service.DAL:服務端DAL資料通路層,裡面使用EF建立資料庫連接配接。
再來看看各層次之間的調用關系:
最後說說這樣設計的好處:
(1)整個架構采用面向接口程式設計模式,每個層次不是直接向其上層提供服務(即不是直接執行個體化在上層中),而是通過定義一組接口,僅向上層暴露其接口功能,上層對下層僅僅是接口依賴,而不依賴具體實作。如是說,用戶端IBLL接口層僅僅提供一套接口供UI層調用,對于UI層來說,它根本感覺不到用戶端BLL實作層的存在,極端點說,即使不寫BLL實作層,項目也可以編譯通過,因為接口的功能已經定義好了。至于具體的實作,那就是業務的問題了。當我們需要更改業務邏輯時,隻需要更改BLL實作層的代碼就好了,對于IBLL接口層和上層UI不用做任何的改變,更進一步說,甚至将用戶端BLL實作層全部重寫或者整個替換掉,IBLL和UI層都可以不做任何改變。這也正是面向接口程式設計最大的優勢。
(2)上張圖裡面也提到了DTOModel層,為什麼要有DTOModel這麼一個對象,而不是直接将EF的實體Model傳到前端來呢?個人覺得原因有兩點:一是上文提到的安全性問題,用戶端永遠隻能操作DTOmodel,當用戶端送出資料到背景來時,永遠都是先将DTOmodel轉換位EF的model,然後去操作資料庫,試想,如果UI表現層能直接操作EF的model,是否會造成操作資料庫的入口的不唯一的問題;二是,比如資料庫裡面有A和B兩張表,我們前端需要展示A表的A.1、A.2兩字段,還需要展示B表的B.3、B.4字段,當我們使用DTOmodel的時候,隻需要構造好一個DTO_Model,裡面有4個字段,前端可以直接拿來用就好了,如果不用DTO,要麼直接傳object,要麼将A、B兩張表的模型傳過來在前端構造,無論哪種方式應該都沒有使用DTO友善吧。
當然這些都是部落客自己的了解,如果博友們覺得有問題可以指出~~
好了,說了這麼多架構,下面進入今天的正題。看看MEF是如何在項目中飛的吧~~先來看看各層的代碼:
(1)ESTM.Service.DAL裡面通過EF建立資料庫的連接配接 :部落客為了測試随便拖了一張使用者表進來。
Base.cs裡面通過MEF導入EF的上下文對象:
對應在Export在edmx檔案下面的MyModel.Context.cs裡面
(2)ESTM.Service.IBLL服務端IBLL接口層定義服務端接口:
(3)ESTM.Service.BLL服務端BLL實作層定義接口實作:
注意在BLL實作層裡面有EF的Model和DTOmodel之間的轉換,因為在DAL裡面取到的是EF的實體模型,而需要傳到前端的是DTOmodel的模型,項目中一般用AutoMapper等第三方工具轉換對象,我這裡為了簡單自己手動通過反射轉了下。
(4)ESTM.Service.WCF服務端WCF宿主層,定義WCF的接口契約。
代碼沒什麼複雜的邏輯,就是先注冊MEF執行個體化變量,然後取值。[Import("Users")]這裡有導入,根據我們前兩篇的講解,那麼肯定是存在一個[Export("Users")]這樣的導出,于是乎,我們可以根據IServiceUser 接口往下找,最後可以找到在ESTM.Service.BLL這個裡面有一個如下的導出:
(5)ESTM.Client.IBLL用戶端IBLL接口層
(6)ESTM.Client.BLL用戶端BLL實作層
在這個層裡面是通過WCF服務去調用資料的,是以需要添加WCF的服務引用。
(7)ESTM.Client.Winform用戶端UI層:定義一個DataGridView展示清單:
得到結果:
前面MEF的第一篇中已經說過使用MEF的優勢之一就是降低層與層之間的耦合,我們現在來結合架構說說它是如何作業的。首先我們來看看ESTM.Client.Winform這個項目的引用:
它是沒有添加ESTM.Client.BLL這一層的引用的,可是我們在Form1.cs裡面有如下代碼:
程式運作起來,走完注冊MEF以後可以看到Manager的變量值就是ESTM.Client.BLL裡面的ManagerUser對象。這就是MEF的功勞,當調用regisgterAll()這個方法的時候,MEF會根據導入導出自動去尋找比對,并且自動執行個體化。如果是沒有MEF,我們UI層就必須要添加ESTM.Client.BLL的引用了。當然有一點需要注意的地方,雖然UI層不用添加ESTM.Client.BLL的引用,但是由于在UI裡面使用了ManagerUser這個對象,是以UI層bin目錄下面必須要有ESTM.Client.BLL.dll這個檔案以及ESTM.Client.BLL項目所必須的dll,你可以手動拷貝這些dll到UI的bin目錄下面。甚至為了簡單,你也可以在UI層上面添加ESTM.Client.BLL這個的引用,但是部落客覺得,這樣貌似違背了面向接口程式設計的原則,不爽,奈何沒想到更好的解決方案。
對于上面UI層必須要添加BLL實作層這一問題找到解決方案了,在此記錄下:
ESTM.Client.BLL項目右鍵→屬性
輸出路徑改成UI層的bin目錄下面即可。2015年9月16日加。
在搭建這個小架構過程中,部落客遇到幾個問題在此和博友分享下:
1.添加服務引用在Client.Bll裡面,由于Client.BLL是一個内庫,最終它會生成一個dll,是以,WCF連接配接的配置要拷貝到Winform項目下面的App.Config裡面。
2.DAL裡面的連接配接字元串也要拷貝到WCF的App.Config裡面,原因同上。
3.注冊MEF的方法
可以抽到一個公共的地方,不用每個地方都寫。注意由于MEF的導入導出涉及到多個内庫,是以這裡要周遊bin目錄下面所有的dll去尋找比對。
4.DAL層可以還做一下封裝,部落客的項目是用的倉儲模式封裝EF,然後在Service.BLL裡面調用倉儲的服務去通路資料庫。
附上源碼,有興趣可以研究下!