C#進階系列——MEF實現設計上的「鬆耦合」(終結篇:面向接口編程)

序:忙碌多事的八月帶着些許的倦意早已步入尾聲,金秋九月承載着抗打敗利70週年的喜慶撲面而來。沒來得及任何準備,彷佛也不須要任何準備,由於生活不須要太多未來時。天天忙着上班、加班、白加班,忘了去憤,忘了去算計所謂的價值。天津爆炸事故時刻警示着咱們生命的無常,逝者安息,活着的人生活還得繼續,珍惜生命,遠離傷害。武漢,這座炙熱的城市,雖值金秋,卻依然經受着「秋老虎」的烘烤,馬路上蒸騰的熱氣迎面襲來,全身毛孔張開,汗流不止,在這般高溫下,彷佛汗水都要被榨乾,其實,被榨乾的何止是汗水!!!籲!籲!籲!說好的MEF呢?說好的面向接口編程呢?都快奔三張的人了,還學着小年輕玩無病呻吟,有點裝嫩的味道。沒辦法,思想脫繮了,有點野性難馴的意思了。好啦,不扯啦,進入今天的正題吧。html

  前面兩篇分別介紹了下MEF的簡單用法和MEF與倉儲模式的結合使用,這章來個終結吧。毛爺爺教導咱們,作事要善始善終。本篇,博主打算經過分享一個面向接口編程的框架來講明使用MEF的靈活性。前端

一、面向接口編程:有必定編程經驗的博友應該都熟悉或者瞭解這種編程思想,層和層之間經過接口依賴,下層不是直接給上層提供服務,而是定義一組接口供上層調用。至於具體的業務實現,那是開發中須要作的事情,在項目架構階段,只須要定義好層與層之間的接口依賴,將框架搭起來,編譯能夠直接經過。爲何要有這麼一種設計?既然是架構設計,固然是爲了提升架構的靈活性,下降層和層之間的依賴(耦合)。這個並不是一句兩句講得清楚的,更多詳細能夠參看:面向接口編程詳解(一)——思想基礎。此文我以爲分析比較到位。好了,不說廢話,來看代碼。數據庫

 

二、博主本着「不講清楚誓不罷休」的原則,本身從零開始搭了一個簡單的框架Demo,固然,可能對於大牛們來講是沒太大價值的,但請不要笑話博主不斷探索的勇氣。先來看看框架大概的結構吧。編程

首先說明下各層次的意思:安全

1、ESTM.Client架構

  ESTM.Client.Winform:Winform項目,用戶UI展示,這個沒什麼好說的。app

  ESTM.Client.IBLL:客戶端IBLL接口層,用於定義客戶端的業務接口,記住這裏僅僅是向UI層提供接口功能。框架

  ESTM.Client.BLL:客戶端BLL實現層,用於客戶端IBLL接口層的實現,提供UI層真是業務邏輯。ide

2、ESTM.Common工具

  ESTM.Common.Model:通用DTOModel層,注意,這裏不是EF的實體Model,而是另外定義的一個數據轉換的Model層。

3、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.一、A.2兩字段,還須要展現B表的B.三、B.4字段,當咱們使用DTOmodel的時候,只須要構造好一個DTO_Model,裏面有4個字段,前端能夠直接拿來用就行了,若是不用DTO,要麼直接傳object,要麼將A、B兩張表的模型傳過來在前端構造,不管哪一種方式應該都沒有使用DTO方便吧。

 固然這些都是博主本身的理解,若是博友們以爲有問題能夠指出~~

 

好了,說了這麼多框架,下面進入今天的正題。看看MEF是如何在項目中飛的吧~~先來看看各層的代碼:

(1)ESTM.Service.DAL裏面經過EF創建數據庫的鏈接 :博主爲了測試隨便拖了一張用戶表進來。

Base.cs裏面經過MEF導入EF的上下文對象:

    public class Base
    {
        [Import]
        public DbContext EntityFramework { set; get; }

        public Base()
        {
            //由於這裏有Import,因此須要裝配MEF
            regisgter().ComposeParts(this);
        }

        public CompositionContainer regisgter()
        {
            var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
            var container = new CompositionContainer(catalog);
            return container;
        }
    }

 

對應在Export在edmx文件下面的MyModel.Context.cs裏面

  [Export(typeof(DbContext))]
    public partial class Entities : DbContext
    {
        public Entities()
            : base("name=Entities")
        {
        }
    
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            throw new UnintentionalCodeFirstException();
        }
    
        public DbSet<TB_USERS> TB_USERS { get; set; }
    }

 

(2)ESTM.Service.IBLL服務端IBLL接口層定義服務端接口:

  public interface IServiceUser
    {
        List<DTO_USERS> GetAllUser();

        void AddUser(DTO_USERS oUser);
    }

 

(3)ESTM.Service.BLL服務端BLL實現層定義接口實現:

  [Export("Users",typeof(IServiceUser))]
    public class ServiceUser : IServiceUser
    {
        //須要注意:1.添加服務引用在Client.Bll裏面,因此,WCF鏈接的配置要拷貝到Winform項目下面的App.Config裏面
        //2.DAL裏面的鏈接字符串也要拷貝到WCF裏面,緣由同上
        public List<DTO_USERS> GetAllUser()
        {
            var lstRes = new List<DTO_USERS>();
            var oService = new DAL.ServiceUser();
            var lstEFModel = oService.GetAllUsers();

            //通常用AutoMapper將EF的Model轉換成DTO的Model.z這裏爲了測試,咱們暫且手動轉換。使用反射轉換
            var lstEFModelProp = typeof(TB_USERS).GetProperties();
            var lstDTOModelProp = typeof(DTO_USERS).GetProperties();
            foreach (var oEFModel in lstEFModel)
            {
                var oResUser = new DTO_USERS();
                foreach (var oProp in lstEFModelProp)
                {
                    var oDTOMOdelProp = lstDTOModelProp.FirstOrDefault(x => x.Name == oProp.Name);
                    if (oDTOMOdelProp == null)
                    {
                        continue;
                    }

                    oDTOMOdelProp.SetValue(oResUser, oProp.GetValue(oEFModel));
                }
                lstRes.Add(oResUser);
            }


            return lstRes;
        }

        public void AddUser(DTO_USERS oUser)
        {
            
        }

注意在BLL實現層裏面有EF的Model和DTOmodel之間的轉換,由於在DAL裏面取到的是EF的實體模型,而須要傳到前端的是DTOmodel的模型,項目中通常用AutoMapper等第三方工具轉換對象,我這裏爲了簡單本身手動經過反射轉了下。

 

(4)ESTM.Service.WCF服務端WCF宿主層,定義WCF的接口契約。

     static void Main(string[] args)
        {
            var strUri = "http://127.0.0.1:1234/MyWCF.Server";

            Uri httpAddress = new Uri(strUri);
            using (ServiceHost host = new ServiceHost(typeof(CSOAService)))//須要添加System.SystemModel這個dll。。。。CSOAService這個爲實現ICSOAService的實現類,WCF真正的實現方法再這個類裏面
            {
                ///////////////////////////////////////添加服務節點///////////////////////////////////////////////////
                host.AddServiceEndpoint(typeof(ICSOAService), new WSHttpBinding(), httpAddress);//ICSOAService這個爲向外暴露的接口
                if (host.Description.Behaviors.Find<ServiceMetadataBehavior>() == null)
                {
                    ServiceMetadataBehavior behavior = new ServiceMetadataBehavior();
                    behavior.HttpGetEnabled = true;
                    behavior.HttpGetUrl = httpAddress;
                    host.Description.Behaviors.Add(behavior);
                }
                host.Opened += delegate
                {
                    Console.ForegroundColor = ConsoleColor.Green;
                    Console.WriteLine("MyWCF.Server服務已經啓動成功。" + strUri);
                };

                host.Open();
                while (true)
                {
                    Console.ReadLine();
                }
            }

            
        }
  [ServiceContract]
    public interface ICSOAService
    {
        [OperationContract]
        List<DTO_USERS> GetAllUsers();
    }
  public class CSOAService:ICSOAService
    {
        [Import("Users")]
        public IServiceUser Service { set; get; }

        public CSOAService()
        {
            regisgterAll().ComposeParts(this);
        }

        public List<DTO_USERS> GetAllUsers()
        {
            return Service.GetAllUser();
        }

        public CompositionContainer regisgterAll()
        {
            AggregateCatalog aggregateCatalog = new AggregateCatalog();
            var thisAssembly = new DirectoryCatalog(AppDomain.CurrentDomain.BaseDirectory, "*.dll");
            aggregateCatalog.Catalogs.Add(thisAssembly);
            var _container = new CompositionContainer(aggregateCatalog);

            return _container;
        }
    }

代碼沒什麼複雜的邏輯,就是先註冊MEF實例化變量,而後取值。[Import("Users")]這裏有導入,根據咱們前兩篇的講解,那麼確定是存在一個[Export("Users")]這樣的導出,因而乎,咱們能夠根據IServiceUser 接口往下找,最後能夠找到在ESTM.Service.BLL這個裏面有一個以下的導出:

    [Export("Users",typeof(IServiceUser))]
    public class ServiceUser : IServiceUser
    {
       //........  
    }

 

(5)ESTM.Client.IBLL客戶端IBLL接口層

    public interface IManagerUser
    {
        List<DTO_USERS> GetAllUser();
    }

 

(6)ESTM.Client.BLL客戶端BLL實現層

  [Export("Users",typeof(IManagerUser))]
    public class ManagerUser : IManagerUser
    {

        public List<Common.Model.DTO_USERS> GetAllUser()
        {
       //WCF服務對象
var oWCFService = new ServiceReference_MyWCF.CSOAServiceClient(); return oWCFService.GetAllUsers().ToList(); } }

在這個層裏面是經過WCF服務去調用數據的,因此須要添加WCF的服務引用。

 

(7)ESTM.Client.Winform客戶端UI層:定義一個DataGridView展現列表:

    public partial class Form1 : Form
    {

        [Import("Users")]
        public IManagerUser Manager { set; get; }
       
        public Form1()
        {
            InitializeComponent();
            regisgterAll().ComposeParts(this);

            this.dataGridView1.DataSource = Manager.GetAllUser();
        }

        public CompositionContainer regisgterAll()
        {
            AggregateCatalog aggregateCatalog = new AggregateCatalog();
            var thisAssembly = new DirectoryCatalog(AppDomain.CurrentDomain.BaseDirectory, "*.dll");
            aggregateCatalog.Catalogs.Add(thisAssembly);
            var _container = new CompositionContainer(aggregateCatalog);

            return _container;
        }
    }

獲得結果:

 

前面MEF的第一篇中已經說過使用MEF的優點之一就是下降層與層之間的耦合,咱們如今來結合框架說說它是如何做業的。首先咱們來看看ESTM.Client.Winform這個項目的引用:

它是沒有添加ESTM.Client.BLL這一層的引用的,但是咱們在Form1.cs裏面有以下代碼:

  public partial class Form1 : Form
    {

        [Import("Users")]
        public IManagerUser Manager { set; get; }
       
        public Form1()
        {
            InitializeComponent();
            regisgterAll().ComposeParts(this);

            this.dataGridView1.DataSource = Manager.GetAllUser();
        }

        public CompositionContainer regisgterAll()
        {
            AggregateCatalog aggregateCatalog = new AggregateCatalog();
            var thisAssembly = new DirectoryCatalog(AppDomain.CurrentDomain.BaseDirectory, "*.dll");
            aggregateCatalog.Catalogs.Add(thisAssembly);
            var _container = new CompositionContainer(aggregateCatalog);

            return _container;
        }
    }

程序運行起來,走完註冊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的方法

    public CompositionContainer regisgterAll()
        {
            AggregateCatalog aggregateCatalog = new AggregateCatalog();
            var thisAssembly = new DirectoryCatalog(AppDomain.CurrentDomain.BaseDirectory, "*.dll");
            aggregateCatalog.Catalogs.Add(thisAssembly);
            var _container = new CompositionContainer(aggregateCatalog);

            return _container;
        }

能夠抽到一個公共的地方,不用每一個地方都寫。注意因爲MEF的導入導出涉及到多個內庫,因此這裏要遍歷bin目錄下面全部的dll去尋找匹配。

4.DAL層能夠還作一下封裝,博主的項目是用的倉儲模式封裝EF,而後在Service.BLL裏面調用倉儲的服務去訪問數據庫。

 

附上源碼,有興趣能夠研究下!

相關文章
相關標籤/搜索