65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.27/5 (7投票s)

2011年8月21日

CPOL

9分钟阅读

viewsIcon

49664

downloadIcon

769

本文演示了在 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 部分介绍了 ExportAttributeImportAttribute CompositionContainer。第 2 部分讨论了 MEF 在 ASP.NET MVC 3 系统中的用法。

第 1 部分 MEF 基础知识

微软人员将缩写MEF视为一个首字母缩略词。因此,您发音 MEF 的方式类似于单词“deaf”,并且在书写或口语中,您不会在 MEF 前面加上定冠词。

从微软的角度来看,MEF 不是一个控制反转系统。然而,MEF 提供了控制反转系统的功能。

MEF 中三个基本构造是 ExportAttributeImportAttribute 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();
    }
  }
}

ExportAttributeImportAttribute 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 应用程序。红框内的文本来自一个单独的类。

picture-1.png

文本“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,您应该做五件事

  1. 您使用 ExportAttributes ImportAttributes 标记 MEF 需要处理的代码片段。
  2. 您创建一个 CompositionContainer 实例。您用标记好的代码填充它。
  3. 您实现 IDependencyResolver 接口。
  4. 您将实现 IDependencyResolver 接口的对象提供给 CompositionContainer 实例。
  5. 您将实现 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 构造就可以完成大部分工作:ExportAttributeImportAttribute CompositionContainer。您必须做五件事

  • 您用 ExportAttribute ImportAttribute 装饰 MEF 组合容器应该匹配的实体。
  • 您实现 IDependencyResolver 接口。
  • 您创建一个 CompositionContainer 实例并用您的实体初始化它。
  • 您用组合容器初始化您定制的 IDependencyResolver 接口实现。
  • 您指示 ASP.NET MVC 3 的 DependencyResolver 使用您的 IDependencyResolver 接口实现。

MEF 提供了我未讨论过的其他功能。我故意将示例保持得非常简单。使用接口、抽象类、引导程序等只会使实现要求变得模糊。

在可下载的包中,您会找到两个 Visual Studio 解决方案。ClassicMvc01 应用程序易于理解。MefMvc01 应用程序的作用与 ClassicMvc01 相同,但演示了如果您想在 ASP.NET MVC 3 应用程序中使用 MEF 所需采取的关键编码措施。

© . All rights reserved.