托管可扩展性框架:第 2 部分






4.94/5 (10投票s)
管理扩展与应用程序之间的信息流。
引言
这是我的上一篇文章的续集。 第一部分 介绍了 MEF 试图解决的问题。在这一部分,我们将看到 MEF 如何解决第一部分中描述的问题场景。
示例涵盖了信息从应用程序流向扩展以及从扩展流向应用程序的场景。在 MEF 术语中,扩展是可组合部分或简称为 part。我选择交换自定义类而不是内置类型,如 string 或 int。我原本打算也涵盖事件的交换。但是,事件我将在下一部分讨论。
背景
在第一部分的末尾,我展示了一个可以作为托管扩展工作的代码。但是,那个解决方案比较底层,不够灵活。MEF 提供了可发现的目录来从各种来源(如 DirectoryCatalog 或 AssemblyCatalog)搜索 parts。然后,容器负责协调目录以匹配 export/import 对。有关更多信息,请访问 http://mef.codeplex.com/Wiki/View.aspx?title=Overview。
示例
此示例的代码包含在三个解决方案中。控制台应用程序可以消耗一个包含契约的扩展性 parts 类库,而接口类库包含实现契约的扩展性 parts 之一。您只需要 MEF 框架的一个库。目前,MEF 全部在一个名为 System.ComponentModel.Composition 的 DLL 中。您可以从 CodePlex 下载此框架。好的,让我们来实现这三个解决方案。
可以消耗可扩展部分的控制台应用程序
在这里,我们构建一个可以消耗可扩展部分的控制台应用程序。此应用程序甚至导出一个 part。
- 创建一个控制台应用程序并将其命名为“meflab1”。
- 添加对 System.ComponentModel.Composition.dll 的引用(从下载的 zip 文件的 bin 目录)。
- 将 Program.cs 中的代码替换为以下代码段
using System;
using System.IO;
using System.Reflection;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.ComponentModel.Composition.Primitives;
namespace meflab1
{
class Program
{
[Import]
public IGreetings Greetings { get; set; }
static void Main(string[] args)
{
Console.WriteLine("Enter 0 to quit, any other number to continue");
while (Console.ReadLine() != "0")
{
Program program = new Program();
program.Compose();
program.Run();
}
}
void Compose()
{
try
{
DirectoryCatalog catalog =
new DirectoryCatalog(AppDomain.CurrentDomain.BaseDirectory);
AggregateCatalog agcatalogue =
new AggregateCatalog(new ComposablePartCatalog[] {catalog,
new AssemblyCatalog(Assembly.GetExecutingAssembly())});
CompositionContainer container = new CompositionContainer(agcatalogue);
CompositionBatch batch = new CompositionBatch();
batch.AddPart(this);
container.Compose(batch);
}
catch (FileNotFoundException fnfex)
{
Console.WriteLine(fnfex.Message);
}
catch (CompositionException cex)
{
Console.WriteLine(cex.Message);
}
}
void Run()
{
if (Greetings != null)
{
Console.WriteLine(Greetings.SayHello());
}
Console.Read();
}
}
}
添加一个名为 SimpleGreeting
的类,并将 SimpleGreeting.cs 中的代码替换为以下代码段
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
namespace meflab1
{
[Export(typeof(IContextInfo))]
public class UserInfo : IContextInfo
{
public IDictionary<string,> GetContextInfo()
{
return new Dictionary<string,> { { "UserName",
Environment.UserDomainName + "\\" + Environment.UserName } };
}
}
}
保存此解决方案。
包含契约和接口的类库
- 创建一个类库并将其命名为“meflibrary”。
- 将 Class1.cs 重命名为 Contract.cs。
- 现在,将 Contract.cs 的内容替换为以下代码段
我们在这里只定义了两个接口
using System.Collections.Generic;
namespace meflab1
{
public interface IContextInfo
{
IDictionary<string,> GetContextInfo();
}
public interface IGreetings
{
string SayHello();
}
}
构建它,并将此类库的引用添加到我们在上一节中构建的应用程序。
包含实现契约的可扩展部分的类库
- 创建一个类库并将其命名为“mefpart”。
- 添加对 System.ComponentModel.Composition.dll 的引用(从下载的 zip 文件的 bin 目录)。
- 将 Class1.cs 重命名为 SimpleGreeting.cs。
- 现在,将 SimpleGreeting.cs 的内容替换为以下代码段
在这里,我们实现了一个可扩展的 part
using System.ComponentModel.Composition;
namespace meflab1
{
[Export(typeof(IGreetings))]
public class SimpleGreeting : IGreetings
{
[Import(typeof(IContextInfo))]
public IContextInfo ContextInfo { get; set; }
public string SayHello()
{
string userName;
var props = ContextInfo.GetContextInfo();
props.TryGetValue("UserName", out userName);
return "Hello " + (userName ?? "<null>") +
" from Visual Studio 2010";
}
}
}
构建它!
有了这个基础设施,我们就可以进行两个练习了。
- 在没有依赖程序集的情况下运行应用程序。
- 在启动时没有依赖程序集的情况下运行应用程序,但使其在 JIT 编译之前可用。
在没有依赖程序集的情况下运行应用程序
现在,构建第一个解决方案并尝试运行它。
- 打开命令提示符。
- 导航到第一个解决方案的输出文件夹,启动 mefpart.exe。
- 应用程序将停在“输入 0 退出,输入任何其他数字继续”。
- 输入任何非零值。
我们得到了以下错误
The composition produced a single composition error. The root cause is provided below.
Review the CompositionException.Errors property for more detailed inf
ormation.
1) No exports were found that match the constraint
'((exportDefinition.ContractName = "meflab1.IGreetings") &&
(exportDefinition.Metadata.ContainsKey("Expor
tTypeIdentity") && "meflab1.IGreetings".Equals(exportDefinition.Metadata.get_Item
("ExportTypeIdentity"))))'.
Resulting in: Cannot set import 'meflab1.Program.Greetings
(ContractName="meflab1.IGreetings")' on part 'meflab1.Program'.
Element: meflab1.Program.Greetings (ContractName="meflab1.IGreetings")
--> meflab1.Program
这是预期的,因为我们没有实现 IGreetings
接口的库。
在启动时没有依赖程序集的情况下运行应用程序,但在 JIT 编译之前使其可用
- 打开命令提示符。
- 导航到第一个解决方案的输出文件夹,启动 mefpart.exe。
- 应用程序将停在“输入 0 退出,输入任何其他数字继续”。
- 将 mefpart.dll 从 mefpart 解决方案的输出目录复制到 meflab1 的输出目录。这基本上就是命令提示符的当前目录。
- 输入任何非零值。
观察到应用程序可以无缝加载 part 并执行其中的代码。因此,第一部分中未起作用的内容现在起作用了。
我将在下一篇文章中解释事件的交换以及 MEF 加载失败的某些场景(下一节的第 2 点)以及失败原因。
关注点
- 这里使用了
AggregateCatalog
,因为应用程序导出了 'UserInfo',因此当前程序集也需要添加到目录中。 - 如果契约不是作为库构建的,并且接口是作为源定义的,则会观察到以下错误
Unhandled Exception: System.Reflection.ReflectionTypeLoadException:
Unable to load one or more of the requested types.
Retrieve the LoaderExceptions property for more information.