MEF(可扩展框架)使用总结

一般情况下,我们对系统要求:

1.对扩展开发对修改关闭

2.高层模块不应该依赖于低层模块,应该依赖于抽象

实际上,这是遵循了面向对象的设计原则中的开放封闭原则和依赖倒置原则,其所做的事情就是为了提高系统的可扩展能力和对代码解耦。

为了满足上述的要求,我们引用了微软的MEF框架,让他来帮助我们做这些事情。

一、概念

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

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

二、原理

  将被我们特殊标记的方法,属性,字段,类型,导入到一个容器中,我们再通过MEF内置的反射来使用容器中被我们导入的各种方法,属性,字段,类型。

三、示例

1.类型

public interface IDBConn
{
    void Conn();
}

[Export("SqlConn", typeof(IDBConn))]
 public class SqlConn : IDBConn
 {
    public void Conn()
    {
        Console.WriteLine("this is SqlServer connection");
    }
 }

  我们的数据库连接可能会是多种连接,如果我们直接写死为SqlServer的连接,显然不符合开放关闭原则,所以我们提供了一个数据库连接的接口IDBConn,然后用SqlServer的连接SqlConn ,去实现数据库连接接口。

  然后利用MEF中的Export将该类型导出至MEF的容器中。

  调用:

public string Test()
{
    // 创建一个程序集目录,用于从一个程序集获取所有的组件定义 
    var assemblyCatalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
    //创建容器
    var Container = new CompositionContainer(assemblyCatalog);
    //执行组合
    Container.ComposeParts();
    //获得容器中的Export
    IDBConn _conn = IOCContainer.Container.GetExportedValue<IDBConn>("SqlConn");
    _conn.Conn();
    return "";
}    

  这是我们的一种方式,当我们的导出到容器中的类过于庞大的时候,我们可以换一种方式来获得导出。

  MEF提供Lazy加载,Lazy的好处在于,我们可以先声明,到了需要使用的时候才会实际去加载,代码如下:

public string TestLazy()
        {
            // 创建一个程序集目录,用于从一个程序集获取所有的组件定义 
            var assemblyCatalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
            //创建容器
            var Container = new CompositionContainer(assemblyCatalog);
            //执行组合
            Container.ComposeParts();
            //lazy
            var export =Container.GetExport<IDBConn>("SqlConn");
            IDBConn _conn = export.Value;
            _conn.Conn();
            return "";
        }

  到此,我们类型说的就差不多了,但是似乎有一点小问题,如果我需要根据某些特定的东西来判断加载哪个类型怎么办?

  首先,我们先看下Export的定义:

        //
        // 摘要:
        //     通过在指定协定名称下导出指定类型,初始化 System.ComponentModel.Composition.ExportAttribute 类的新实例。
        //
        // 参数:
        //   contractName:
        //     用于导出使用此特性标记的类型或成员的协定名称,或 null 或空字符串 ("") 以使用默认协定名称。
        //
        //   contractType:
        //     要导出的类型。
        public ExportAttribute(string contractName, Type contractType);   

  使用contractName可以满足这个需求,但是MEF提供了更加优雅的方式----元组。

  代码如下:

public interface IDBConnMetadata
    {
        string Version { get; }
    }

    [Export("SqlConn", typeof(IDBConn))]
    [ExportMetadata("Version", "v1.0.1")]
    public class SqlConn : IDBConn
    {
        public void Conn()
        {
            Console.WriteLine("this is SqlServer connection");
        }
    }

public string TestMetadata()
        {
            // 创建一个程序集目录,用于从一个程序集获取所有的组件定义 
            var assemblyCatalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
            //创建容器
            var Container = new CompositionContainer(assemblyCatalog);
            //执行组合
            Container.ComposeParts();

            var export = Container.GetExports<IDBConn, IDBConnMetadata>();
            //筛选version为v1.0.1的
            IDBConn _conn = export.Where(p => p.Metadata.Version.Equals("v1.0.1")).FirstOrDefault().Value;

            _conn.Conn();
            return "";
        }

  到此类型就完全ok了,接下来看一下方法。

2.方法

  方法需要对匿名委托需要有一些了解,不懂的,可以自行查一下,很简单。

public class MySqlConn : IDBConn
    {
        private MySqlConn()
        {

        }

        [Export("Conn", typeof(Action))]
        public void Conn()
        {
            Console.WriteLine("this is MySqlConn connection");
        }

        [Export("GetStr", typeof(Func<string>))]
        private string GetStr()
        { return "随便试试字符串"; }

        [Export("GetLength", typeof(Func<string, int>))]
        public int GetLength(string str)
        {
            return str.Length;
        }

    }

  等一下,上面代码是不是有问题啊?构造方法是私有的,也没有用单例,这怎么调用啊?

  首先这又不是静态方法,不能用类名.方法名的方式调用,并且不能实例化,这样岂不是很蒙圈。

  那么我们试着用MEF写一下调用,代码如下:

public string TestAction()
        {
            TestActionNoPara();
            TestActionWithPara();
            TestActionWithParaAndReturn();

            return "";
        }


        #region  Import Action

        #region Import 无参无返回方法
        private void TestActionNoPara()
        {
            // 创建一个程序集目录,用于从一个程序集获取所有的组件定义 
            var assemblyCatalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
            //创建容器
            var Container = new CompositionContainer(assemblyCatalog);
            //执行组合
            Container.ComposeParts();
            var action = Container.GetExport<Action>("Conn");
            action.Value.Invoke();
        }
        #endregion

        #region Import 无参有返回方法
        private void TestActionWithPara()
        {
            // 创建一个程序集目录,用于从一个程序集获取所有的组件定义 
            var assemblyCatalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
            //创建容器
            var Container = new CompositionContainer(assemblyCatalog);
            //执行组合
            Container.ComposeParts();
            var action = Container.GetExport<Func<string>>("GetStr");
            string message = action.Value.Invoke();
        }
        #endregion

        #region Import 有参有返回方法
        private void TestActionWithParaAndReturn()
        {
            // 创建一个程序集目录,用于从一个程序集获取所有的组件定义 
            var assemblyCatalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
            //创建容器
            var Container = new CompositionContainer(assemblyCatalog);
            //执行组合
            Container.ComposeParts();
            var action = Container.GetExport<Func<string, int>>("GetLength");

            int length = action.Value.Invoke("GetLength");
        }
        #endregion

        #endregion

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

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

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

3.属性和字段

  研究了好久也不知道这个属性和字段应该应用到哪里,有什么实际的应用场景,不过,还是写下这个怎么用。

[Export("SqlConn", typeof(IDBConn))]
public
class SqlConn : IDBConn { [Export("Description")] public string Description { get { return "this is SqlServer connection"; } } public void Conn() { Console.WriteLine("this is SqlServer connection"); } }
public string TestAttribute()
        {
            // 创建一个程序集目录,用于从一个程序集获取所有的组件定义 
            var assemblyCatalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
            //创建容器
            var Container = new CompositionContainer(assemblyCatalog);
            //执行组合
            Container.ComposeParts();

            var desc = Container.GetExportedValue<string>("Description");

            return desc;
        }

  //字段
  [Import("SqlConn")]
  private IDBConn dbConn;

 

4.构造方法注入

  我们也可以用构造方法的方式完成注入,代码如下:

#region 构造注入
    [Export("TestB")]
    public class TestB
    {
        private IDBConn _conn;
        [ImportingConstructor]
        public TestB([Import("SqlConn")]IDBConn conn)
        {
            _conn = conn;
        }

        public bool IsInject()
        {
            if (_conn == null)
            {
                return false;
            }
            else
            {
                return true;
            }
        }
    }
    #endregion

public string TestInject()
        {
            // 创建一个程序集目录,用于从一个程序集获取所有的组件定义 
            var assemblyCatalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
            //创建容器
            var Container = new CompositionContainer(assemblyCatalog);
            //执行组合
            Container.ComposeParts();
            var x = Container.GetExportedValue<TestB>("TestB");
            bool result = x.IsInject();

            return result.ToString();
        }

四、改进

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

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

  

public class IOCContainer
    {
        public static CompositionContainer Container { get; private set; }

        private static IOCContainer instance = new IOCContainer();

        private IOCContainer()
        {
            if (Container == null)
            {
                //AssemblyCatalog:表示从程序集中搜索部件的目录
                // 创建一个程序集目录,用于从一个程序集获取所有的组件定义
                var assemblyCatalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());

                //指定加载某个程序集
                //var directoryCatalog = new DirectoryCatalog("要加载dll的路径");
                //AggregateCatalog:聚合目录,可以添加上面所说的所有目录,从而进行多方面的部件搜索
                //var aggregateCatalog = new AggregateCatalog(assemblyCatalog, directoryCatalog);

                //创建容器
                Container = new CompositionContainer(assemblyCatalog);
                //container = new CompositionContainer(aggregateCatalog);
                //container.GetExportedValue

                //执行组合
                Container.ComposeParts();
            }
        }

        public static IOCContainer RegisterContainer()
        {
            return instance;
        }

    }

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

  到这里MEF的总结,就全部完成了,欢迎交流。

posted @ 2018-06-19 12:21  Yu2  阅读(649)  评论(0编辑  收藏  举报