ExportAttribute、ImportAttribute、CompositionContainer 和 ASP.NET MVC 3 中的 MEF






4.27/5 (7投票s)
本文演示了在 ASP.NET MVC 3 应用程序中使用托管可扩展性框架 (MEF) 的用法。
引言
本文演示了在 ASP.NET MVC 3 应用程序中使用托管可扩展性框架 (MEF) 的用法。如果您对 MEF 有一定的理解和知识,这篇文章将对您有所帮助。本文不涉及 MEF 或 ASP.NET MVC 3 系统的复杂性。
文章附带一个可下载的包,其中包含两个 Visual Studio 解决方案。ClassicMvc01 解决方案是一个简单的 ASP.NET MVC 3 应用程序,用于显示由类生成的文本。MefMvc01 解决方案是经典应用程序的重制版,采用了 MEF 构造和所需的 MVC 3 机制。
PartCreationPolicyAttribute、控制器生命周期和 ASP.NET MVC 3 中的 MEF 是本文的后续文章。
截至 2011 年 8 月,您应该考虑以下软件
- Visual Studio 2010
- Visual Studio 2010 SP1
- .NET Framework 4.0
- ASP.NET MVC 3
- C# 4.0。
本文有两个部分。第 1 部分介绍了 ExportAttribute
、ImportAttribute
和 CompositionContainer
。第 2 部分讨论了 MEF 在 ASP.NET MVC 3 系统中的用法。
第 1 部分 MEF 基础知识
微软人员将缩写MEF视为一个首字母缩略词。因此,您发音 MEF 的方式类似于单词“deaf”,并且在书写或口语中,您不会在 MEF 前面加上定冠词。
从微软的角度来看,MEF 不是一个控制反转系统。然而,MEF 提供了控制反转系统的功能。
MEF 中三个基本构造是 ExportAttribute
、ImportAttribute
和 CompositionContainer
。
您使用 ExportAttribute
来标记以下代码片段
- 类
- 字段
- 属性
- 索引器
- method
您使用 ImportAttribute
来标记以下代码片段
- 字段
- 属性
- 索引器
- 参数。
示例 1 - 用 ExportAttribute
标记的类
[ExportAttribute]
public class A
{
public void ShowMessage()
{
Console.WriteLine("this is class A");
}
}
示例 2 - 用 ImportAttribute
标记的属性
public class B
{
[ImportAttribute]
public A PropertyA { get; set; }
}
示例 3 - 用 ExportAttribute
标记的方法
public class C
{
[ExportAttribute]
public void DoSomething()
{
}
}
微软的 MEF 人员喜欢“part”这个词。例如,他们会谈论可发现的 part、组合的 part、导出的 part 等。他们从未明确定义过“part”的含义,但为了所有实际目的,您可以将part视为一个至少有一个 ExportAttribute
的类。本着这种精神,示例 2 中的 B 类不是一个 part,但示例 1 中的 A 类和示例 3 中的 C 类是 part。
另一个 MEF 构造是 CompositionContainer
类。您将用 ExportAttribute
或 ImportAttribute
标记的代码提供给 CompositionContainer
。CompositionContainer
会尝试匹配导出和导入。如果您将示例 1 中的 A 类和示例 2 中的 B 类提供给 CompositionContainer
,CompositionContainer
会将 B 类中的 PropertyA
与 A 类进行匹配。
示例 4 展示了一个完整的程序,该程序定义了用 ExportAttribute
或 ImportAttribute
适当标记的 A 和 B 类。CompositionContainer
接收 A 和 B 类的实例。CompositionContainer
组合实例,即它用导出填充导入。CompositionContainer
返回一个组合好的 part。我选择在控制台应用程序中实现示例 4。与 ASP.NET MVC 3 应用程序不同,控制台应用程序只需要一个文件。这样您就可以轻松地看到三个 MEF 构造的运行。
示例 4 - 控制台应用程序演示了 MEF 基础知识的用法
namespace MefExample4
{
using System;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
[ExportAttribute]
public class A
{
public void ShowMessage()
{
Console.WriteLine("this is class A");
}
}
[ExportAttribute]
public class B
{
[ImportAttribute]
public A PropertyA { get; set; }
}
class Program
{
static void Main(string[] args)
{
// Declare a composition container.
CompositionContainer compositionContainer = new CompositionContainer();
// Feed the container instances of A and B.
compositionContainer.ComposeParts(new A(), new B());
// Retrieve the composed part.
B b = compositionContainer.GetExportedValueOrDefault<B>();
// Use the imported construct of B.
b.PropertyA.ShowMessage();
}
}
}
ExportAttribute
、ImportAttribute
和 CompositionContainer
是 MEF 中至关重要的构造。有了这三个构造,您可以完成 70% 的工作。
第 2 部分 - ASP.NET MVC 3 中的 MEF
在讨论 MEF 在 ASP.NET MVC 3 系统中的应用之前,我邀请您看一看图 1。您是一位经验丰富的开发人员,可以猜到图 1 中应用程序的结构。
为了确保我们步调一致,我向您展示了(示例 5、6 和 7)实现图所示内容的必要代码,这些代码以经典的 ASP.NET MVC 3 方式实现。
图 1 - ASP.NET MVC 3 应用程序。红框内的文本来自一个单独的类。
文本“this message is from MessageSource”(被红框包围)来自示例 5 中的 MessageSource
类。在经典的 ASP.NET MVC 3 系统中,示例 6 中的 HomeController
创建了该类的实例,并将其发送到示例 7 中的 Index 视图。
示例 5 - 经典 ASP.NET MVC 3。MessageSource
类是 Index 视图中文本的来源
namespace ClassicMvc01
{
public class MessageSource
{
public MessageSource()
{
this.Message = "this message is from MessageSource";
}
public string Message { get; private set; }
}
}
示例 6 - 经典 ASP.NET MVC 3。HomeController
创建 MessageSource
类的实例
namespace ClassicMvc01.Controllers
{
using System.Web.Mvc;
public class HomeController : Controller
{
private MessageSource messageSource = new MessageSource();
public ActionResult Index()
{
return View(this.messageSource);
}
}
}
示例 7 - 经典 ASP.NET MVC 3。Index 视图显示来自 MessageSource
的消息
@model ClassicMvc01.MessageSource
@{ViewBag.Title = "Index";}
<h2>Home/Index</h2>
<p>@Model.Message</p>
我在可下载的包中包含了名为 ClassicMvc01
的完整 Visual Studio 解决方案。
现在我将展示如何使用 MEF 在 ASP.NET MVC 3 应用程序中实现图 1。在可下载的包中,您可以参考 MefMvc01
。
如果您想在 ASP.NET MVC 3 应用程序中使用 MEF,您应该做五件事
- 您使用
ExportAttributes
和ImportAttributes
标记 MEF 需要处理的代码片段。 - 您创建一个
CompositionContainer
实例。您用标记好的代码填充它。 - 您实现
IDependencyResolver
接口。 - 您将实现
IDependencyResolver
接口的对象提供给CompositionContainer
实例。 - 您将实现
IDependencyResolver
的对象注册到 ASP.NET MVC 3 系统。
在 Visual Studio 2010 中,我首先创建一个空的 ASP.NET MVC 3 项目。我添加对 System.ComponentModel.Composition
组件的引用。
1. 您使用 ExportAttributes 和 ImportAttributes 标记 MEF 需要处理的代码片段。
我希望 MEF 处理我的 HomeController
和 MessageSource
类,方法是创建一个 MessageSource
类的实例,并将 HomeController
的 messageSource
字段设置为该实例。
我在示例 8 中定义了 MessageSource
类。注意示例 5 中的代码和示例 8 中的代码之间的区别。在示例 8 中,
- 我有一个
using System.ComponentModel.Composition
指令,并且 - 我用
ExportAttribute
标记了MessageSource
类。
示例 8 - MessageSource
类用 ExportAttribute
标记
namespace MefMvc01
{
using System.ComponentModel.Composition;
[ExportAttribute]
public class MessageSource
{
public MessageSource()
{
this.Message = "this message is from MessageSource";
}
public string Message { get; private set; }
}
}
我在示例 9 中创建了一个最基本的 HomeController
。在 HomeController
中,您会看到
using System.ComponentModel.Composition
指令HomeController
类用ExportAttribute
标记messageSource
字段用ImportAttribute
标记
示例 9 - HomeController
类用 ExportAttribute
标记
namespace MefMvc01.Controllers
{
using System.ComponentModel.Composition;
using System.Web.Mvc;
[ExportAttribute]
public class HomeController : Controller
{
[ImportAttribute]
private MessageSource messageSource;
public ActionResult Index()
{
return View(this.messageSource);
}
}
}
在运行时,MessageSource
的实例将成为 MEF 设置给 messageSource
字段的值,即 MEF 将导出的 MessageSource
part 导入到 messageSource
字段。
最后,我在示例 10 中为 HomeController
创建了一个 Index
视图。MEF 在这里不做任何事情。代码与示例 7 中的代码几乎相同。
示例 10 - 经典 ASP.NET MVC 3 系统和带 MEF 的 ASP.NET MVC 中的 Index 视图相同
@model MefMvc01.MessageSource
@{ViewBag.Title = "Index";}
<h2>Home/Index</h2>
<p>@Model.Message</p>
如果我现在运行程序,ASP.NET MVC 3 系统将显示一个错误消息“未将对象引用设置到对象的实例”,指向 Index.cshtml 中的这行代码
<p>@Model.Message</p>
为了纠正这个问题,我必须完成第 2、3、4 和 5 项。
2. 您创建一个 CompositionContainer 实例。您用标记好的代码填充它。
在示例 11 中,我声明了一个 CompositionCotainer
并将其提供给我的 HomeController
和 MessageSource
类。我将这段代码放在 global.ascx.cs 文件的 Application_Start
方法中。
示例 11 - 声明 CompositionContainer
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
CompositionContainer compositionContainer = new CompositionContainer();
compositionContainer.ComposeParts(new HomeController(),
new MessageSource());
}
MEF 提供了几种初始化组合容器的方法。在示例 11 中,我展示了一种简单且自明的技术。
3. 您实现 IDependencyResolver 接口。
在示例 12 中,我向我的项目中添加了一个实现 IDependencyResolver
接口的 MefDependencySolver
类。IDependencyResolver
接口定义了两个我的类必须实现的方法:GetService
和 GetServices
。
示例 12 - IDependencyResolver
的实现
namespace MefMvc01
{
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.Web.Mvc;
public class MefDependencySolver : IDependencyResolver
{
public MefDependencySolver(CompositionContainer compositionContainer)
{
this.compositionContainer = compositionContainer;
}
private CompositionContainer compositionContainer;
public object GetService(Type serviceType)
{
string name = AttributedModelServices.GetContractName(serviceType);
return compositionContainer.GetExportedValueOrDefault<object>(name);
}
public IEnumerable<object> GetServices(Type serviceType)
{
return this.compositionContainer
.GetExportedValues<object>(serviceType.FullName);
}
}
}
稍后,我将展示如何将我的 MefDependencySolver
类的实例注册到 ASP.NET MVC 3 系统。当我的应用程序运行时,ASP.NET MVC 3 系统将知道我的 MefDependencySolver
,并且当用户请求页面时,ASP.NET MVC 3 系统会多次调用 GetService
方法,每次都向其中传递一个不同的“服务”。微软没有提供关于 ASP.NET MVC 3 系统传递到 GetService
方法中的“服务”的文档,但如果您跟踪应用程序的执行,您可能会观察到 ASP.NET MVC 3 系统按以下顺序传递一个或多个服务类型
IControllerFactory
IControllerActivator
HomeController
ModelMetadataProvider
IViewPageActivator
Asp_Page_Views_Home_Index_cshtml
在 GetService
方法的正文中,我要求 MEF 容器查找一个名称与服务类型名称匹配的对象。如果容器找到这样的对象,它将把它返回给 GetService
方法,然后 GetService
方法将其发送到 ASP.NET MVC 3 系统。这样,ASP.NET MVC 3 系统就知道 MEF 负责进一步处理。如果容器中没有该对象,则该方法返回 null
给 ASP.NET MVC 3 系统。因此,由 ASP.NET MVC 3 系统负责执行服务的工作。
类似地,ASP.NET MVC 3 系统会反复调用 GetServices
方法,并按以下顺序传递一个或多个服务
IFilterProvider
IModelBinderProvider
ValueProviderFactory
ModelValidatorProvider
IViewEngine
4. 您将实现 IDependencyResolver 接口的对象提供给 CompositionContainer 实例。
在示例 13 中,我将 CompositionContainer
对象传递给我的自定义 MefDependencySolver
的构造函数。我将这段代码放在 global.asax.cs 文件中。
示例 13 - 使用 CompositionContainer
初始化我的 MefDependencySolver
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
CompositionContainer compositionContainer = new CompositionContainer();
compositionContainer.ComposeParts(new HomeController(),
new MessageSource());
var mefDependencySolver = new MefDependencySolver(compositionContainer);
}
5. 您将实现 IDependecyResolver 的对象注册到 ASP.NET MVC 3 系统。
我必须通知 ASP.NET MVC 3 系统我的自定义依赖解析器。因此,在示例 14 中,我使用 MVC 3 的 DependencyResolver
对象将我的 MefDependencySolver
注册到系统中。
示例 14 - 将 MefDependencySolver
注册到 ASP.NET MVC 3 系统
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
CompositionContainer compositionContainer = new CompositionContainer();
compositionContainer.ComposeParts(new HomeController(),
new MessageSource());
var mefDependencySolver = new MefDependencySolver(compositionContainer);
DependencyResolver.SetResolver(mefDependencySolver);
}
摘要
本文展示了如何在 ASP.NET MVC 3 应用程序中使用 MEF。您只需要三个 MEF 构造就可以完成大部分工作:ExportAttribute
、ImportAttribute
和 CompositionContainer
。您必须做五件事
- 您用
ExportAttribute
或ImportAttribute
装饰 MEF 组合容器应该匹配的实体。 - 您实现
IDependencyResolver
接口。 - 您创建一个
CompositionContainer
实例并用您的实体初始化它。 - 您用组合容器初始化您定制的
IDependencyResolver
接口实现。 - 您指示 ASP.NET MVC 3 的
DependencyResolver
使用您的IDependencyResolver
接口实现。
MEF 提供了我未讨论过的其他功能。我故意将示例保持得非常简单。使用接口、抽象类、引导程序等只会使实现要求变得模糊。
在可下载的包中,您会找到两个 Visual Studio 解决方案。ClassicMvc01
应用程序易于理解。MefMvc01
应用程序的作用与 ClassicMvc01
相同,但演示了如果您想在 ASP.NET MVC 3 应用程序中使用 MEF 所需采取的关键编码措施。