.Net插件编程模型:MEF和MAF

Posted Jeff Xiong

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了.Net插件编程模型:MEF和MAF相关的知识,希望对你有一定的参考价值。

.Net插件编程模型:MEF和MAF

       MEF和MAF都是C#下的插件编程框架,我们通过它们只需简单的配置下源代码就能轻松的实现插件编程概念,设计出可扩展的程序。这真是件美妙的事情!

 

MEF(Managed Extensibility Framework)

             MEF的工作原理大概是这样的:首先定义一个接口,用这个接口来约束插件需要具备的职责;然后在实现接口的程序方法上面添加反射标记“[Export()]”将实现的内容导出;最后在接口的调用程序中通过属性将插件加载进来。我们还是用代码来描述吧:

 

1.  定义一个接口:

/*

    作者:GhostBear
    博客:http://blog.csdn.net/ghostbear  
    简介:该节主要学习.net下的插件编程框架MEF(managed extensibility framework)

 */

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

 

using System.Windows;

 

 

namespace chapter28_simplecontract



    public interface ICalculator

    

        IList<IOperation> GetOperations();

        double Operate(IOperation operation, double[] operands);

    

 

    public interface IOperation

    

        string Name  get; 

        int NumberOperands  get; 

    

 

    public interface ICaculatorExtension

    

        string Title  get; 

        string Description  get; 

        FrameworkElement GetUI();

    



 

 

 

2.  实现定义的接口(部分一)

    /*
       作者:GhostBear
       博客:http://blog.csdn.net/ghostbear
   */
    [Export(typeof(ICalculator))]

    public class Caculator:ICalculator

    

 

        public IList<IOperation> GetOperations()

        

            return new List<IOperation>()

                new Operation Name="+",NumberOperands=2,

                new OperationName="-",NumberOperands=2,

                new OperationName="*",NumberOperands=2,

                new OperationName="/",NumberOperands=2

            ;

        

 

        public double Operate(IOperation operation, double[] operands)

        

            double result=0;

            switch (operation.Name)

             

                case "+":

                    result = operands[0] + operands[1];

                    break;

                case "-":

                    result = operands[0] - operands[1];

                    break;

                case "*":

                    result = operands[0] * operands[1];

                    break;

                case "/":

                    result = operands[0] / operands[1];

                    break;

                default:

                    throw new Exception("not provide this method");

            

            return result;

        



 

    public class Operation:IOperation

    

        public string Name

        

            get;

            internal set;

        

 

        public int NumberOperands

        

            get;

            internal set;

        

    


 

 

实现定义的接口(部分二)

/*
    作者:GhostBear
    博客:http://blog.csdn.net/ghostbear
*/
[Export(typeof(ICalculator))]

        public class Caculator : ICalculator

        

 

            public IList<IOperation> GetOperations()

            

                return new List<IOperation>()

                new Operation Name="+",NumberOperands=2,

                new OperationName="-",NumberOperands=2,

                new OperationName="*",NumberOperands=2,

                new OperationName="/",NumberOperands=2,

                new OperationName="%",NumberOperands=2,

                new OperationName="**",NumberOperands=1,

                

            ;

            

 

            public double Operate(IOperation operation, double[] operands)

            

                double result = 0;

                switch (operation.Name)

                

                    case "+":

                        result = operands[0] + operands[1];

                        break;

                    case "-":

                        result = operands[0] - operands[1];

                        break;

                    case "*":

                        result = operands[0] * operands[1];

                        break;

                    case "/":

                        result = operands[0] / operands[1];

                        break;

                    case "%":

                        result=operands[0]%operands[1];

                        break;

                    case "**":

                        result=operands[0]*operands[0];

                        break;

                    default:

                        throw new Exception("not provide this method");

                

                return result;

            

        

 

        public class Operation : IOperation

        

            public string Name

            

                get;

                internal set;

            

 

            public int NumberOperands

            

                get;

                internal set;

            

        

 


 

分析:

     标记“[Export(typeof(ICalculator))]”声明表达的意思是:这个类可以编译为插件,并能放入插件容器“ICalculator”中。这里需要注意的是:部分一和部分二的代码分布在不同的程序集中。导出的插件不一定必须是以类的形式,也可以是方法。

 

 

通过导出方法来生成插件:

    /*
    作者:GhostBear
    博客:http://blog.csdn.net/ghostbear
   */
public class Bomb

    

        [Export("Bomb")]

        public void Fire()

        

            Console.WriteLine("you are dead!!!");

        

    


 

 

 

插件的调用者:

/*


    作者:GhostBear
    博客:http://blog.csdn.net/ghostbear

 *  简介:该节主要学习.net下的插件编程框架MEF(managed extensibility framework)

 */

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

 

using System.ComponentModel.Composition;

using System.ComponentModel.Composition.Hosting;

 

using chapter28_simplecontract;

 

namespace chapter28



    class Program

    

 

        [ImportMany(typeof(ICalculator))]

        public IEnumerable<ICalculator> Calculators  get; set; 

 

        [Import("Bomb")]

        public Action Bomb  get; set; 

 

 

        

        static void Main(string[] args)

        

            Program pro = new Program();

            pro.Run();

            pro.Run2();

        

 

        public void Run()

        

            var catalog = new DirectoryCatalog("c:\\\\plugins");

            var container = new CompositionContainer(catalog);

            try

            

                container.ComposeParts(this);

            

            catch (Exception ex)

            

                Console.WriteLine(ex.Message);

                return;

            

            ICalculator myCalculator = Calculators.ToList<ICalculator>()[1];

            

 

            var operations = myCalculator.GetOperations();

            var operationsDict = new SortedList<string, IOperation>();

            foreach(IOperation item in operations)

            

                Console.WriteLine("Name:0,number operands:1"

, item.Name, item.NumberOperands);

                operationsDict.Add(item.Name, item);

            

            Console.WriteLine();

            string selectedOp = null;

 

            do

            

                try

                

                    Console.Write("Operation?");

                    selectedOp = Console.ReadLine();

                    if (selectedOp.ToLower() == "exit"

                                     || !operationsDict.ContainsKey(selectedOp))

                    

                        continue;

                    

                    var operation = operationsDict[selectedOp];

                    double[] operands = new double[operation.NumberOperands];

                    for (int i = 0; i < operation.NumberOperands; i++)

                    

                        Console.WriteLine("\\t operand 0?", i + 1);

                        string selectedOperand = Console.ReadLine();

                        operands[i] = double.Parse(selectedOperand);

                    

                    Console.WriteLine("calling calculator");

                    double result = myCalculator.Operate(operation, operands);

                    Console.WriteLine("result:0", result);

                

                catch (Exception ex)

                

                    Console.WriteLine(ex.Message);

                    Console.WriteLine();

                    continue;

                

             while (selectedOp != "exit");

        

 

        public void Run2()

        

            var catalog = new DirectoryCatalog("c:\\\\plugins");

            var container = new CompositionContainer(catalog);

 

            container.ComposeParts(this);

Bomb.Invoke();

            Console.ReadKey();

        

    



 

 

分析:

   标记“[ImportMany(typeof(ICalculator))]”,该声明表达的意图是:将所有声明了标记“[Export(typeof(ICalculator))]”的程序集加载进容器。这里“[ImportMany]和”[Import]”的区别就是:前者的容器可以存放多个插件,而后者只能存放一个。

   光声明“[Import()]”和”[Export()]”标记是不行的,还必须通过下面的代码将这两个标记的功能联合起来:

           

 //DirectoryCatalog表示这类插件会存放在系统的哪个文件夹下

var catalog = new DirectoryCatalog("c:\\\\plugins");

            var container = new CompositionContainer(catalog);

            try

            

//将存放在目录中的插件按“[Export()]和[Import()]”规则装载进当前

//类中。

                container.ComposeParts(this);

            

            catch (Exception ex)

            

                Console.WriteLine(ex.Message);

                return;

            


 

 

执行结果

 Name:+,number operands:2

Name:-,number operands:2

Name:*,number operands:2

Name:/,number operands:2

 

Operation?+

         operand 1?

1

         operand 2?

1

calling calculator

result:2

Operation?exit

 

 

you are dead!!!

 

 

 

MAF(Managed Addin Framework)

          MAF也是.Net为我们提供的一个“插件编程”解决方案。它比MEF复杂,需要配置很多元素。但它也有些优点:1.宿主程序和插件程序可以进行隔离,以此降低运行插件所带来的风险;2。MAF的设计是基于7个程序集组成的管道,这些管道部分可以单独更换,这些管道的详细情况见下图。

图1

  

   使用MAF是需要有些细节需要注意:组成管道的7个程序集在系统中的保存路径有格式要求,并且没个保存它的文件夹内只运行同时出现一个程序集。具体情况如下图所示:

图2

 

图3

 

图4

 

图5

 

下面我们来看一个小Demo吧,这个demo一共有7个项目,它们分别对应图1描述的管道中的7个部分。具体情况见下图。

 

图6

 

 插件:Addin_1,Addin_2

插件视图:AddinSideView

插件适配器:AddinSideAdapter

协定:IContract

宿主视图:HostSideView

宿主适配器:HostSideAdapter

宿主程序:Host

 

程序代码

 

Addin_1

/*


    作者:GhostBear
    博客:http://blog.csdn.net/ghostbear
    简介:测试MAF,这段代码是用来定义一个插件的。这个插件可以在宿主程序
     中动态加载。

 */

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

 

using System.AddIn;

using System.AddIn.Pipeline;

 

namespace Addin_1



[AddIn("Helloworld",Description="this is helloworld addin"

,Publisher="GhostBear",Version="1.0")]

    public class Addin_1:AddinSideView.AddinSideView

    

 

        public string Say()

        

            return "Helloworld";

        

    




 

 

Addin_2

/*
    作者:GhostBear
    博客:http://blog.csdn.net/ghostbear
*/
using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

 

using System.AddIn;

 

namespace Addin_2



[AddIn("SuperBomb",Description="This is a bigger bomb"

,Publisher="SuperWorker",Version="1.0.0.0")]

    public class Addin_2:AddinSideView.AddinSideView

    

        public string Say()

        

            return "B--O--M--B";

        

    



 

 

 

AddinSideView

 

/*


    作者:GhostBear
    博客:http://blog.csdn.net/ghostbear

*   简介:测试MAF,这段代码是定义插件端的视图类,该视图类的方法和属性必须与协定一致。

 */

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

 

 

using System.AddIn.Pipeline;

namespace AddinSideView



    [AddInBase()]

    public interface AddinSideView

    

        string Say();

    




 

AddinSideAdapter

/*


    作者:GhostBear
    博客:http://blog.csdn.net/ghostbear

*   简介:测试MAF,这段代码是插件端的适配器类,它用来实现插件端视图类。

 *         并组合协定。这样就能让插件和协定解耦,如果插件有所修改就换掉

 *         该适配器类就可以了。

 */

 

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

 

using System.AddIn.Pipeline;

 

 

namespace AddinSideAdapter



    [AddInAdapter]

    public class AddinSideAdapter : ContractBase,IContract.IMyContract

    

        private AddinSideView.AddinSideView _handler;

        public AddinSideAdapter(AddinSideView.AddinSideView handler)

        

            this._handler = handler;

        

 

        public string Say()

        

            return this._handler.Say();

        

    



 


 

 

IContract

/*


    作者:GhostBear
    博客:http://blog.csdn.net/ghostbear

     简介:测试MAF,这段代码是定义协定。

 */

 

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

 

using System.AddIn.Pipeline;

using System.AddIn.Contract;

 

namespace IContract



    [AddInContract]

    public interface IMyContract:System.AddIn.Contract.IContract

    

        string Say();

    



 

 

 

HostSideView

/*


    作者:GhostBear
    博客:http://blog.csdn.net/ghostbear

    简介:测试MAF,这段代码用来定义宿主段的视图类,该类的所有方法和属性需与协定类一致。

 */

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

 

namespace HostSideView



    public interface HostSideView

    

        string Say();

    




 

 

HostSideAdapter

/*

    作者:GhostBear
    博客:http://blog.csdn.net/ghostbear
    简介:测试MAF,这段代码用来定义宿主端的适配器类。该类实现宿主端的
    视图类并组合协定。

 */

 

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

 

using System.AddIn.Pipeline;

 

 

 

namespace HostSideAdapter



    [HostAdapter()]

    public class HostSideAdapter:HostSideView.HostSideView

    

 

        private IContract.IMyContract _contract;

        //这行代码重要

        private System.AddIn.Pipeline.ContractHandle _handle;

        public HostSideAdapter(IContract.IMyContract contract)

        

            this._contract = contract;

            this._handle = new ContractHandle(contract);

        

        

 

 

        public string Say()

        

            return this._contract.Say();

        

    



 


 

 

Host

/*
    作者:GhostBear
    博客:http://blog.csdn.net/ghostbear
    简介:测试MAF,这段代码是宿主程序。该程序可以针对保存在某个目录下的插件来进行选择性调用。

 */

 

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

 

 

using System.Collections;

using System.Collections.ObjectModel;

 

using System.AddIn.Hosting;

 

using HostSideView;

 

namespace Host



    class Program

    

        static void Main(string[] args)

        

            string path = @"D:\\学习文档\\c#\\c#高级编程7\\MAF\\MAF";

            string[] warnings = AddInStore.Update(path);

 

            foreach (var tmp in warnings)

            

                Console.WriteLine(tmp);

            

      //发现

      var tokens = AddInStore.FindAddIns(typeof(HostSideView.HostSideView), path);

 

            Console.WriteLine("当前共有0个插件可以选择。它们分别为:",tokens.Count);

 

            var index = 1;

            foreach (var tmp in tokens)

            

                Console.WriteLine(string.Format("[4]名称:0,描述:1,版本:2,发布者:3", tmp.Name, tmp.Description, tmp.Version, tmp.Publisher,index++));

            

 

 

            var token = ChooseCalculator(tokens);

            

//隔离和激活插件

            AddInProcess process=new AddInProcess(Platform.X64);

            process.Start();

            var addin = token.Activate<HostSideView.HostSideView>(process, AddInSecurityLevel.FullTrust);

            Console.WriteLine("PID:0",process.ProcessId);

            //调用插件

Console.WriteLine(addin.Say());

 

            Console.ReadKey();

        

 

        private static AddInToken ChooseCalculator(Collection<AddInToken> tokens)

        

            if (tokens.Count == 0)

            

                Console.WriteLine("No calculators are available");

                return null;

            

            Console.WriteLine("Available Calculators: ");

            // Show the token properties for each token in the AddInToken collection 

            // (tokens), preceded by the add-in number in [] brackets.

            int tokNumber = 1;

            foreach (AddInToken tok in tokens)

            

                Console.WriteLine(String.Format("\\t[0]: 1 - 2\\n\\t3\\n\\t\\t 4\\n\\t\\t 5 - 6",

                    tokNumber.ToString(),

                    tok.Name,

                    tok.AddInFullName,

                    tok.AssemblyName,

                    tok.Description,

                    tok.Version,

                    tok.Publisher));

                tokNumber++;

            

            Console.WriteLine("Which calculator do you want to use?");

            String line = Console.ReadLine();

            int selection;

            if (Int32.TryParse(line, out selection))

            

                if (selection <= tokens.Count)

                

                    return tokens[selection - 1];

                

            

            Console.WriteLine("Invalid selection: 0. Please choose again.", line);

            return ChooseCalculator(tokens);

        

    



 

分析

    在上面的7个程序集,起解耦作用的关键还是2个适配器类。调用程序不直接调用协定,而是通过通过调用这2个适配器来间接调用协定。

 

 

 

 

 

 

 

小结

    MEF和MAF为我们实现“插件编程”提供了2中选择,它们设计的出发点也是完全不同的。在使用它们的时候还是需要更加具体需求来权衡使用。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

以上是关于.Net插件编程模型:MEF和MAF的主要内容,如果未能解决你的问题,请参考以下文章

什么是 MEF?

每个 MAF 插件的 UI 线程

在.NET Core中使用MEF

使用 MEF 构建具有 n 层松散耦合的 MVC ASP.NET 应用程序

Oracle MAF:使用 cordova 插件时出现部署错误

MEF - 插件及其引用的程序集