.Net插件编程模型:MEF和MAF[转载]

时间:2023-12-10 14:44:02

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

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

今天抽了一点时间,看了看MEF的例子,比较简单,有时间会整理一个简单的例子出来。
简单的说,MEF使用了两个标注实现依赖注入。

[Export]可以指定输出的类或者属性
[Import]可以指定引入

通过Export,可以很轻松地将类或者属性输出到依赖注入的容器,再通过Export引入,对象的创建和组装,完全可以通过容器实现,并且可以通过[PartCreationPolicy(CreationPolicy.Shared)]标注,指定为单例模式。

通过依赖注入,可以不必编写单例模式的代码。

MEF(Managed Extensibility Framework)

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

1.  定义一个接口:

  1. /*
  2. 作者:GhostBear
  3. 博客:http://blog.csdn.net/ghostbear
  4. 简介:该节主要学习.net下的插件编程框架MEF(managed extensibility framework)
  5. */
  6. using System;
  7. using System.Collections.Generic;
  8. using System.Linq;
  9. using System.Text;
  10. using System.Windows;
  11. namespace chapter28_simplecontract
  12. {
  13. public interface ICalculator
  14. {
  15. IList<IOperation> GetOperations();
  16. double Operate(IOperation operation, double[] operands);
  17. }
  18. public interface IOperation
  19. {
  20. string Name { get; }
  21. int NumberOperands { get; }
  22. }
  23. public interface ICaculatorExtension
  24. {
  25. string Title { get; }
  26. string Description { get; }
  27. FrameworkElement GetUI();
  28. }
  29. }

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

  1. /*
  2. 作者:GhostBear
  3. 博客:http://blog.csdn.net/ghostbear
  4. */
  1. [Export(typeof(ICalculator))]
  2. public class Caculator:ICalculator
  3. {
  4. public IList<IOperation> GetOperations()
  5. {
  6. return new List<IOperation>(){
  7. new Operation{ Name="+",NumberOperands=2},
  8. new Operation{Name="-",NumberOperands=2},
  9. new Operation{Name="*",NumberOperands=2},
  10. new Operation{Name="/",NumberOperands=2}
  11. };
  12. }
  13. public double Operate(IOperation operation, double[] operands)
  14. {
  15. double result=0;
  16. switch (operation.Name)
  17. {
  18. case "+":
  19. result = operands[0] + operands[1];
  20. break;
  21. case "-":
  22. result = operands[0] - operands[1];
  23. break;
  24. case "*":
  25. result = operands[0] * operands[1];
  26. break;
  27. case "/":
  28. result = operands[0] / operands[1];
  29. break;
  30. default:
  31. throw new Exception("not provide this method");
  32. }
  33. return result;
  34. }
  35. }
  36. public class Operation:IOperation
  37. {
  38. public string Name
  39. {
  40. get;
  41. internal set;
  42. }
  43. public int NumberOperands
  44. {
  45. get;
  46. internal set;
  47. }
  48. }

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

  1. /*
  2. 作者:GhostBear
  3. 博客:http://blog.csdn.net/ghostbear
  4. */
  5. [Export(typeof(ICalculator))]
  6. public class Caculator : ICalculator
  7. {
  8. public IList<IOperation> GetOperations()
  9. {
  10. return new List<IOperation>(){
  11. new Operation{ Name="+",NumberOperands=2},
  12. new Operation{Name="-",NumberOperands=2},
  13. new Operation{Name="*",NumberOperands=2},
  14. new Operation{Name="/",NumberOperands=2},
  15. new Operation{Name="%",NumberOperands=2},
  16. new Operation{Name="**",NumberOperands=1},
  17. };
  18. }
  19. public double Operate(IOperation operation, double[] operands)
  20. {
  21. double result = 0;
  22. switch (operation.Name)
  23. {
  24. case "+":
  25. result = operands[0] + operands[1];
  26. break;
  27. case "-":
  28. result = operands[0] - operands[1];
  29. break;
  30. case "*":
  31. result = operands[0] * operands[1];
  32. break;
  33. case "/":
  34. result = operands[0] / operands[1];
  35. break;
  36. case "%":
  37. result=operands[0]%operands[1];
  38. break;
  39. case "**":
  40. result=operands[0]*operands[0];
  41. break;
  42. default:
  43. throw new Exception("not provide this method");
  44. }
  45. return result;
  46. }
  47. }
  48. public class Operation : IOperation
  49. {
  50. public string Name
  51. {
  52. get;
  53. internal set;
  54. }
  55. public int NumberOperands
  56. {
  57. get;
  58. internal set;
  59. }
  60. }

分析:

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

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

  1. /*
  2. 作者:GhostBear
  3. 博客:http://blog.csdn.net/ghostbear
  4. */
  5. public class Bomb
  6. {
  7. [Export("Bomb")]
  8. public void Fire()
  9. {
  10. Console.WriteLine("you are dead!!!");
  11. }
  12. }

插件的调用者:

  1. /*
  2. 作者:GhostBear
  3. 博客:http://blog.csdn.net/ghostbear
  4. *  简介:该节主要学习.net下的插件编程框架MEF(managed extensibility framework)
  5. */
  6. using System;
  7. using System.Collections.Generic;
  8. using System.Linq;
  9. using System.Text;
  10. using System.ComponentModel.Composition;
  11. using System.ComponentModel.Composition.Hosting;
  12. using chapter28_simplecontract;
  13. namespace chapter28
  14. {
  15. class Program
  16. {
  17. [ImportMany(typeof(ICalculator))]
  18. public IEnumerable<ICalculator> Calculators { get; set; }
  19. [Import("Bomb")]
  20. public Action Bomb { get; set; }
  21. static void Main(string[] args)
  22. {
  23. Program pro = new Program();
  24. pro.Run();
  25. pro.Run2();
  26. }
  27. public void Run()
  28. {
  29. var catalog = new DirectoryCatalog("c:\\plugins");
  30. var container = new CompositionContainer(catalog);
  31. try
  32. {
  33. container.ComposeParts(this);
  34. }
  35. catch (Exception ex)
  36. {
  37. Console.WriteLine(ex.Message);
  38. return;
  39. }
  40. ICalculator myCalculator = Calculators.ToList<ICalculator>()[1];
  41. var operations = myCalculator.GetOperations();
  42. var operationsDict = new SortedList<string, IOperation>();
  43. foreach(IOperation item in operations)
  44. {
  45. Console.WriteLine("Name:{0},number operands:{1}"
  46. , item.Name, item.NumberOperands);
  47. operationsDict.Add(item.Name, item);
  48. }
  49. Console.WriteLine();
  50. string selectedOp = null;
  51. do
  52. {
  53. try
  54. {
  55. Console.Write("Operation?");
  56. selectedOp = Console.ReadLine();
  57. if (selectedOp.ToLower() == "exit"
  58. || !operationsDict.ContainsKey(selectedOp))
  59. {
  60. continue;
  61. }
  62. var operation = operationsDict[selectedOp];
  63. double[] operands = new double[operation.NumberOperands];
  64. for (int i = 0; i < operation.NumberOperands; i++)
  65. {
  66. Console.WriteLine("\t operand {0}?", i + 1);
  67. string selectedOperand = Console.ReadLine();
  68. operands[i] = double.Parse(selectedOperand);
  69. }
  70. Console.WriteLine("calling calculator");
  71. double result = myCalculator.Operate(operation, operands);
  72. Console.WriteLine("result:{0}", result);
  73. }
  74. catch (Exception ex)
  75. {
  76. Console.WriteLine(ex.Message);
  77. Console.WriteLine();
  78. continue;
  79. }
  80. } while (selectedOp != "exit");
  81. }
  82. public void Run2()
  83. {
  84. var catalog = new DirectoryCatalog("c:\\plugins");
  85. var container = new CompositionContainer(catalog);
  86. container.ComposeParts(this);
  87. Bomb.Invoke();
  88. Console.ReadKey();
  89. }
  90. }
  91. }

分析:

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

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

  1. //DirectoryCatalog表示这类插件会存放在系统的哪个文件夹下
  2. var catalog = new DirectoryCatalog("c:\\plugins");
  3. var container = new CompositionContainer(catalog);
  4. try
  5. {
  6. //将存放在目录中的插件按“[Export()]和[Import()]”规则装载进当前
  7. //类中。
  8. container.ComposeParts(this);
  9. }
  10. catch (Exception ex)
  11. {
  12. Console.WriteLine(ex.Message);
  13. return;
  14. }

执行结果

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个程序集组成的管道,这些管道部分可以单独更换,这些管道的详细情况见下图。

.Net插件编程模型:MEF和MAF[转载]

图1

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

.Net插件编程模型:MEF和MAF[转载]

图2

.Net插件编程模型:MEF和MAF[转载]

图3

.Net插件编程模型:MEF和MAF[转载]

图4

.Net插件编程模型:MEF和MAF[转载]

图5

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

.Net插件编程模型:MEF和MAF[转载]

图6

插件:Addin_1,Addin_2

插件视图:AddinSideView

插件适配器:AddinSideAdapter

协定:IContract

宿主视图:HostSideView

宿主适配器:HostSideAdapter

宿主程序:Host

程序代码

Addin_1

  1. /*
  2. 作者:GhostBear
  3. 博客:http://blog.csdn.net/ghostbear
  4. 简介:测试MAF,这段代码是用来定义一个插件的。这个插件可以在宿主程序
  5. 中动态加载。
  6. */
  7. using System;
  8. using System.Collections.Generic;
  9. using System.Linq;
  10. using System.Text;
  11. using System.AddIn;
  12. using System.AddIn.Pipeline;
  13. namespace Addin_1
  14. {
  15. [AddIn("Helloworld",Description="this is helloworld addin"
  16. ,Publisher="GhostBear",Version="1.0")]
  17. public class Addin_1:AddinSideView.AddinSideView
  18. {
  19. public string Say()
  20. {
  21. return "Helloworld";
  22. }
  23. }
  24. }

Addin_2

  1. /*
  2. 作者:GhostBear
  3. 博客:http://blog.csdn.net/ghostbear
  4. */
  5. using System;
  6. using System.Collections.Generic;
  7. using System.Linq;
  8. using System.Text;
  9. using System.AddIn;
  10. namespace Addin_2
  11. {
  12. [AddIn("SuperBomb",Description="This is a bigger bomb"
  13. ,Publisher="SuperWorker",Version="1.0.0.0")]
  14. public class Addin_2:AddinSideView.AddinSideView
  15. {
  16. public string Say()
  17. {
  18. return "B--O--M--B";
  19. }
  20. }
  21. }

AddinSideView

  1. /*
  2. 作者:GhostBear
  3. 博客:http://blog.csdn.net/ghostbear
  4. *   简介:测试MAF,这段代码是定义插件端的视图类,该视图类的方法和属性必须与协定一致。
  5. */
  6. using System;
  7. using System.Collections.Generic;
  8. using System.Linq;
  9. using System.Text;
  10. using System.AddIn.Pipeline;
  11. namespace AddinSideView
  12. {
  13. [AddInBase()]
  14. public interface AddinSideView
  15. {
  16. string Say();
  17. }
  18. }

AddinSideAdapter

  1. /*
  2. 作者:GhostBear
  3. 博客:http://blog.csdn.net/ghostbear
  4. *   简介:测试MAF,这段代码是插件端的适配器类,它用来实现插件端视图类。
  5. *         并组合协定。这样就能让插件和协定解耦,如果插件有所修改就换掉
  6. *         该适配器类就可以了。
  7. */
  8. using System;
  9. using System.Collections.Generic;
  10. using System.Linq;
  11. using System.Text;
  12. using System.AddIn.Pipeline;
  13. namespace AddinSideAdapter
  14. {
  15. [AddInAdapter]
  16. public class AddinSideAdapter : ContractBase,IContract.IMyContract
  17. {
  18. private AddinSideView.AddinSideView _handler;
  19. public AddinSideAdapter(AddinSideView.AddinSideView handler)
  20. {
  21. this._handler = handler;
  22. }
  23. public string Say()
  24. {
  25. return this._handler.Say();
  26. }
  27. }
  28. }

IContract

  1. /*
  2. 作者:GhostBear
  3. 博客:http://blog.csdn.net/ghostbear
  4. 简介:测试MAF,这段代码是定义协定。
  5. */
  6. using System;
  7. using System.Collections.Generic;
  8. using System.Linq;
  9. using System.Text;
  10. using System.AddIn.Pipeline;
  11. using System.AddIn.Contract;
  12. namespace IContract
  13. {
  14. [AddInContract]
  15. public interface IMyContract:System.AddIn.Contract.IContract
  16. {
  17. string Say();
  18. }
  19. }

HostSideView

  1. /*
  2. 作者:GhostBear
  3. 博客:http://blog.csdn.net/ghostbear
  4. 简介:测试MAF,这段代码用来定义宿主段的视图类,该类的所有方法和属性需与协定类一致。
  5. */
  6. using System;
  7. using System.Collections.Generic;
  8. using System.Linq;
  9. using System.Text;
  10. namespace HostSideView
  11. {
  12. public interface HostSideView
  13. {
  14. string Say();
  15. }
  16. }

HostSideAdapter

  1. /*
  2. 作者:GhostBear
  3. 博客:http://blog.csdn.net/ghostbear
  4. 简介:测试MAF,这段代码用来定义宿主端的适配器类。该类实现宿主端的
  5. 视图类并组合协定。
  6. */
  7. using System;
  8. using System.Collections.Generic;
  9. using System.Linq;
  10. using System.Text;
  11. using System.AddIn.Pipeline;
  12. namespace HostSideAdapter
  13. {
  14. [HostAdapter()]
  15. public class HostSideAdapter:HostSideView.HostSideView
  16. {
  17. private IContract.IMyContract _contract;
  18. //这行代码重要
  19. private System.AddIn.Pipeline.ContractHandle _handle;
  20. public HostSideAdapter(IContract.IMyContract contract)
  21. {
  22. this._contract = contract;
  23. this._handle = new ContractHandle(contract);
  24. }
  25. public string Say()
  26. {
  27. return this._contract.Say();
  28. }
  29. }
  30. }

Host

  1. /*
  2. 作者:GhostBear
  3. 博客:http://blog.csdn.net/ghostbear
  4. 简介:测试MAF,这段代码是宿主程序。该程序可以针对保存在某个目录下的插件来进行选择性调用。
  5. */
  6. using System;
  7. using System.Collections.Generic;
  8. using System.Linq;
  9. using System.Text;
  10. using System.Collections;
  11. using System.Collections.ObjectModel;
  12. using System.AddIn.Hosting;
  13. using HostSideView;
  14. namespace Host
  15. {
  16. class Program
  17. {
  18. static void Main(string[] args)
  19. {
  20. string path = @"D:\学习文档\c#\c#高级编程7\MAF\MAF";
  21. string[] warnings = AddInStore.Update(path);
  22. foreach (var tmp in warnings)
  23. {
  24. Console.WriteLine(tmp);
  25. }
  26. //发现
  27. var tokens = AddInStore.FindAddIns(typeof(HostSideView.HostSideView), path);
  28. Console.WriteLine("当前共有{0}个插件可以选择。它们分别为:",tokens.Count);
  29. var index = 1;
  30. foreach (var tmp in tokens)
  31. {
  32. Console.WriteLine(string.Format("[{4}]名称:{0},描述:{1},版本:{2},发布者:{3}", tmp.Name, tmp.Description, tmp.Version, tmp.Publisher,index++));
  33. }
  34. var token = ChooseCalculator(tokens);
  35. //隔离和激活插件
  36. AddInProcess process=new AddInProcess(Platform.X64);
  37. process.Start();
  38. var addin = token.Activate<HostSideView.HostSideView>(process, AddInSecurityLevel.FullTrust);
  39. Console.WriteLine("PID:{0}",process.ProcessId);
  40. //调用插件
  41. Console.WriteLine(addin.Say());
  42. Console.ReadKey();
  43. }
  44. private static AddInToken ChooseCalculator(Collection<AddInToken> tokens)
  45. {
  46. if (tokens.Count == 0)
  47. {
  48. Console.WriteLine("No calculators are available");
  49. return null;
  50. }
  51. Console.WriteLine("Available Calculators: ");
  52. // Show the token properties for each token in the AddInToken collection
  53. // (tokens), preceded by the add-in number in [] brackets.
  54. int tokNumber = 1;
  55. foreach (AddInToken tok in tokens)
  56. {
  57. Console.WriteLine(String.Format("\t[{0}]: {1} - {2}\n\t{3}\n\t\t {4}\n\t\t {5} - {6}",
  58. tokNumber.ToString(),
  59. tok.Name,
  60. tok.AddInFullName,
  61. tok.AssemblyName,
  62. tok.Description,
  63. tok.Version,
  64. tok.Publisher));
  65. tokNumber++;
  66. }
  67. Console.WriteLine("Which calculator do you want to use?");
  68. String line = Console.ReadLine();
  69. int selection;
  70. if (Int32.TryParse(line, out selection))
  71. {
  72. if (selection <= tokens.Count)
  73. {
  74. return tokens[selection - 1];
  75. }
  76. }
  77. Console.WriteLine("Invalid selection: {0}. Please choose again.", line);
  78. return ChooseCalculator(tokens);
  79. }
  80. }
  81. }

分析

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

小结

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