• MEF框架使用总结


    一、为什么用MEF

        首先看一下,我们如何在控制器中调用业务层的接口:

    1.  
      public class ValuesController : ApiController
    2.  
      {
    3.  
      private IDBConn dbConn = new SqlConn();
    4.  
       
    5.  
      public string DBConnection()
    6.  
      {
    7.  
      dbConn.Conn();
    8.  
       
    9.  
      return "Success";
    10.  
      }
    11.  
      }

        我们首先声明了一个IDBConn接口,然后用SqlConn来实例化该接口,似乎没有什么不妥的地方。

        可是想一下我们为什么使用接口呢?

        接口和实现分离了,适于团队的协作开发,主要为了实现松散耦合的系统,便于以后升级,扩展。

        我们使用接口的实现直接去实例化接口,似乎并没有起到松耦合的目的,白白增加了接口,并没有任何用处,与下图的方式没有任何的区别。

    1.  
      public class ValuesController : ApiController
    2.  
      {
    3.  
      private SqlConn dbConn = new SqlConn();
    4.  
       
    5.  
      public string DBConnection()
    6.  
      {
    7.  
      dbConn.Conn();
    8.  
       
    9.  
      return "Success";
    10.  
      }
    11.  
      }

        那么问题来了,我们直接使用第二种方式不就可以了吗?答案是也不好,因为这样违背了依赖倒置原则:

        高层模板不应该依赖底层模板,两者应该依赖于抽象,而抽象不应该依赖于细节。

        在模块编程中要依赖抽象编程,不要依赖具体的细节编程,即针对接口编程,不要针对具体的实现编程。

        到这里我们就可以引入我们的MEF框架,来解决上述的问题。

    二、概念

        MEF (Managed Extensibility Framework) 使开发人员能够在其 .NET 应用程序中提供挂钩,以供用户和第三方扩展。可以将 MEF 看成一个通用应用程序扩展实用工具。

        MEF 使开发人员能够动态创建扩展,无需扩展应用程序,也不需要任何特定于扩展内容的知识。这可以确保在编译时两者之间不存在耦合,使应用程序能够在运行时扩展,不需要重新编译。MEF 还可以在将扩展加载到应用程序之前,查看扩展程序集的元数据,这是一种速度更快的方法。

        还没几个需要我们了解的名词:

        Contract:契约,即一种约定

        Part:部件,即契约的实现

        Catalog:目录(理解意义),存放部件的地方,当需要某个部件时,会在目录中寻找

        Container:容器,存放目录并进行部件管理,如导出、导入等

        Compose:组装,通过容器在目录中寻找到实现了相应契约的部件,进行部件的组装

        Export:导出,是部件向容器中提供的一个值, 可修饰类、字段、属性或方法

        Import:导入,从容器中获得可用的导出, 可修饰字段、属性或构造函数参数

    三、示例

        添加System.ComponentModel.Composition引用

        修改被调用方法

    1.  
      public interface IDBConn
    2.  
      {
    3.  
      void Conn();
    4.  
      }
    5.  
       
    6.  
       
    7.  
      [Export("SqlConn", typeof(IDBConn))]
    8.  
      public class SqlConn : IDBConn
    9.  
      {
    10.  
      public void Conn()
    11.  
      {
    12.  
      Console.WriteLine("this is SqlServer connection");
    13.  
      }
    14.  
      }

        修改调用方法

    1.  
      public ActionResult Index()
    2.  
      {
    3.  
      // 创建一个程序集目录,用于从一个程序集获取所有的组件定义
    4.  
      var assemblyCatalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
    5.  
      //创建容器
    6.  
      var Container = new CompositionContainer(assemblyCatalog);
    7.  
      //获得容器中的Export
    8.  
      IDBConn _conn = Container.GetExportedValue<IDBConn>("SqlConn");
    9.  
      //调用方法 SqlConn的Conn方法
    10.  
      _conn.Conn();
    11.  
      return View();
    12.  
      }

        这样就完全符合依赖倒置原则了,控制器层并没有直接依赖到SqlConn类,而是根据我们的ContractName找到了SqlConn,从而解决了上层依赖下层的强耦合问题。

        现在,有了新的需求了,我们的系统需要连接MySql数据库了。

    1.  
      [Export("MySqlConn", typeof(IDBConn))]
    2.  
      public class MySqlConn : IDBConn
    3.  
      {
    4.  
      public void Conn()
    5.  
      {
    6.  
      Console.WriteLine("this is MySqlConn connection");
    7.  
      }
    8.  
      }

        这样我们原来的调用方法似乎还是可以用,但是有一点小问题,每次切换数据库,我们都要修改代码,这样不是很好,我们可以把ContractName配置到配置文件中每次通过修改配置文件,实现动态切换数据库。

    现在我们又遇到了一个问题,接口要根据版本还会有不同的实现,这样的话,原来的方法也可以,我们可以把原来的ContractName后面加上版本号,这样就能区别开不同版本的实现了。

        我们还有更好的办法来解决这个问题,在这里我们引入这个元组这个概念,我们可以在导出时,添加一个特殊字段,供我们来筛选实现。

    1.  
      [ExportMetadata("Version", "v1.0.1")]
    2.  
      [Export("SqlConn", typeof(IDBConn))]
    3.  
      public class SqlConn : IDBConn
    4.  
      {
    5.  
      public void Conn()
    6.  
      {
    7.  
      Console.WriteLine("this is SqlServer connection");
    8.  
      }
    9.  
      }

        然后我们定义一个元组的接口:

    1.  
      public interface IDBConnMetadata
    2.  
      {
    3.  
      string Version { get; }
    4.  
      }

        在调用的时候,进行筛选:

    1.  
      public string TestMetadata()
    2.  
      {
    3.  
      // 创建一个程序集目录,用于从一个程序集获取所有的组件定义
    4.  
      var assemblyCatalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
    5.  
      //创建容器
    6.  
      var Container = new CompositionContainer(assemblyCatalog);
    7.  
       
    8.  
      var export = Container.GetExports<IDBConn, IDBConnMetadata>();
    9.  
      //筛选version为v1.0.1的
    10.  
      IDBConn _conn = export.Where(p => p.Metadata.Version.Equals("v1.0.1")).FirstOrDefault().Value;
    11.  
       
    12.  
      _conn.Conn();
    13.  
      return "";
    14.  
      }

    四、扩展

    1.构造方法注入

        我们也可以采用构造方法注入的方式:

    1.  
      [Export("TestB")]
    2.  
      public class TestB
    3.  
      {
    4.  
      private IDBConn _conn;
    5.  
      [ImportingConstructor]
    6.  
      public TestB([Import("SqlConn")]IDBConn conn)
    7.  
      {
    8.  
      _conn = conn;
    9.  
      }
    10.  
       
    11.  
      public bool IsInject()
    12.  
      {
    13.  
      if (_conn == null)
    14.  
      {
    15.  
      return false;
    16.  
      }
    17.  
      else
    18.  
      {
    19.  
      return true;
    20.  
      }
    21.  
      }
    22.  
      }

        调用时也与其他方式的调用完全一致,不需要特殊处理:

    1.  
      public string TestInject()
    2.  
      {
    3.  
      // 创建一个程序集目录,用于从一个程序集获取所有的组件定义
    4.  
      var assemblyCatalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
    5.  
      //创建容器
    6.  
      var Container = new CompositionContainer(assemblyCatalog);
    7.  
       
    8.  
      var x = Container.GetExportedValue<TestB>("TestB");
    9.  
       
    10.  
      bool result = x.IsInject();
    11.  
       
    12.  
      return result.ToString();
    13.  
      }

    2.导入导出方法

        MEF的导出支持类型,属性,方法和字段,在这里我们说一下导出方法,导出方法需要了解一些匿名委托的知识,自行百度一下,很简单。

    1.  
      public class TestClass
    2.  
      {
    3.  
      private TestClass() { }
    4.  
       
    5.  
      [Export("GetStr", typeof(Func<string>))]
    6.  
      private string GetStr()
    7.  
      { return "随便试试字符串"; }
    8.  
       
    9.  
      [Export("GetLength", typeof(Func<string, int>))]
    10.  
      public int GetLength(string str)
    11.  
      {
    12.  
      return str.Length;
    13.  
      }
    14.  
      }

        我们把TestClass类的构造方法的访问修饰符设置为private级别。私有构造方法,又不是单例,我们如何调用这个类里的方法就是个问题了,那么我们尝试用MEF调用:

    1.  
      private void TestActionWithPara()
    2.  
      {
    3.  
      // 创建一个程序集目录,用于从一个程序集获取所有的组件定义
    4.  
      var assemblyCatalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
    5.  
      //创建容器
    6.  
      var Container = new CompositionContainer(assemblyCatalog);
    7.  
      //执行组合
    8.  
      Container.ComposeParts();
    9.  
      var action = Container.GetExport<Func<string>>("GetStr");
    10.  
      string message = action.Value.Invoke();
    11.  
      }
    12.  
      private void TestActionWithParaAndReturn()
    13.  
      {
    14.  
      // 创建一个程序集目录,用于从一个程序集获取所有的组件定义
    15.  
      var assemblyCatalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
    16.  
      //创建容器
    17.  
      var Container = new CompositionContainer(assemblyCatalog);
    18.  
      //执行组合
    19.  
      Container.ComposeParts();
    20.  
      var action = Container.GetExport<Func<string, int>>("GetLength");
    21.  
       
    22.  
      int length = action.Value.Invoke("GetLength");
    23.  
      }

        完全可以访问,并没有因为私有构造方法而造成不能访问,并且上面的GetStr方法也是私有的,依旧可以访问。

     这就恶心了,方法的访问修饰符完全被MEF破坏了。。。

     感觉这应该算一个bug吧,不过MEF提供了一个属性,可以让方法或者类不能被使用[PartNotDiscoverable]

    五、改进

        

        上面就是MEF的一些基本使用方法,但是写了这么久发现了似乎有一点问题。

        每次调用都去创建目录,创建容器,执行组合,似乎太繁琐了,而且重复这些操作,明显也会浪费资源,所以我们可以做一下改进,将这些重复步骤提炼出来一个单独的方法,供其他方法调用。

    1.  
      public class IOCContainer
    2.  
      {
    3.  
      public static CompositionContainer Container { get; private set; }
    4.  
       
    5.  
      private static IOCContainer instance = new IOCContainer();
    6.  
       
    7.  
      private IOCContainer()
    8.  
      {
    9.  
      if (Container == null)
    10.  
      {
    11.  
      //AssemblyCatalog:表示从程序集中搜索部件的目录
    12.  
      // 创建一个程序集目录,用于从一个程序集获取所有的组件定义
    13.  
      var assemblyCatalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
    14.  
       
    15.  
      //指定加载某个程序集
    16.  
      //var directoryCatalog = new DirectoryCatalog("要加载dll的路径");
    17.  
      //AggregateCatalog:聚合目录,可以添加上面所说的所有目录,从而进行多方面的部件搜索
    18.  
      //var aggregateCatalog = new AggregateCatalog(assemblyCatalog, directoryCatalog);
    19.  
       
    20.  
      //创建容器
    21.  
      Container = new CompositionContainer(assemblyCatalog);
    22.  
      //container = new CompositionContainer(aggregateCatalog);
    23.  
      //container.GetExportedValue
    24.  
       
    25.  
      //执行组合 【组合这一行代码,并不影响我们的使用,有没有皆可】
    26.  
      Container.ComposeParts();
    27.  
      }
    28.  
      }
    29.  
       
    30.  
      public static IOCContainer RegisterContainer()
    31.  
      {
    32.  
      return instance;
    33.  
      }
    34.  
       
    35.  
      }

      写好了容器,我们可以在Global.asax中调用RegisterContainer方法,其他使用容器的地方,改为IOCContainer.Container.GetExportok了,既简化了代码,也节约了资源。

     
    ************转摘:https://blog.csdn.net/yu2222222/article/details/80744761
  • 相关阅读:
    个人网站一步一步搭建——(11)使用python爬取博客园数据
    个人网站一步一步搭建——(10)后台登陆dome
    个人网站一步一步搭建——(9)路漫漫其修远矣
    个人网站一步一步搭建——(8)小小节
    个人网站一步一步搭建——(7)微动态页面前端
    个人网站一步一步搭建——(6)留言板页面前端
    Codeforces 1327D Infinite Path
    Codeforces 1316E Team Building
    Codeforces 1316D Nash Matrix
    Codeforces 1325E Ehab's REAL Number Theory Problem
  • 原文地址:https://www.cnblogs.com/linybo/p/13790291.html
Copyright © 2020-2023  润新知