扩展NerdDinner:为ASP.NET MVC添加MEF和插件

[原文发表地址]  Extending NerdDinner: Adding MEF and plugins to ASP.NET MVC

[原文发表时间] 2010-05-20 8:28 AM

原始的NerdDinner 范例程序是很简单的。事实上两个示例都很简单。或许它是新的Northwind,数据库,因为它是处理ASP.NET MVC的一个好途径。然而,对于用户们想要做的事情,NerdDinner 范例程序可能并不完美或理想化。

幸运的是,社会上有很多能人已经对NerdDinner产生过分歧,而且对其做过一些有趣的研究。这些示例通常针对一个特定的方案。所以大可不必与主干部分合并,但是尽管如此他们也是教育性的。

在更趋于社会化的方向下, Jon Galloway 和我也在NerdDinner中增添了一点东西。如今Jon的MVC Music StoreASP.NET MVC 2. 算是一更好的“入门”示例我们针对有趣的事情上发布了一系列的帖子,这些事情被网络社区上的人添加到了NerdDinner上,同样地其中的一些事情几个月前我和Jon也添加了并且发布在了Mix 上,相信再过不久我和Jon就会在CodePlex上发布一款升级版的NerdDinner v2 (尽管它在源代码标签上已经有几个星期了),并带有许多补丁程序和新的性能。我们也会添加一些一次性的范例程序并把它们发布在CodePlex上。

我曾找微软工程师Hamilton Verissimo de Oliveira 也有人叫他"Hammett"(你可能从Castle Project 和Monorailto听过这个人)谈过关于创建一个包含MEF(Managed Extensibility Framework)的范例程序,因为现在MEF 的大部分程序都已内置于.NET 4。他很乐意帮忙。所以我得以发布这篇博客,非常感谢Hammett的耐心的帮助。

NerdDinner on MEF

MEF 存在于System.ComponentModel.Composition. Hammett在他的范例程序中添加了很多有趣的东西,比如Microsoft.ComponentModel.Composition.Extensions 和 Microsoft.ComponentModel.Composition.Extensions.Web 对于一般的技术,在一些很好的扩展方法上创建命名空间,实现了IcontrollerFactory,同时还导出了HttpApplication.。

MefControllerFactory

记住,MEF使应用程序的处理变得简单。在这个范例程序中,Hammett创建了自己的MefControllerFactory,取代了默认controller factory配备的ASP.NET MVC. ASP.NET MVC能使它实现更好的转换:

    1: protected override void Application_Start()
    2: {
    3:      base.Application_Start();
    4:      ControllerBuilder.Current.SetControllerFactory(new MefControllerFactory(base.ScopeManager));
    5:      
    6:      RegisterRoutes(RouteTable.Routes);
    7:  
    8:      ViewEngines.Engines.Clear();
    9:      ViewEngines.Engines.Add(new MobileCapableWebFormViewEngine());
   10: }

值得注意的是,Hammett的controller factory含有一个ScopeManager。这是一个web应用程序,有些组件可能属于Application Scope(先创建一次,再保留),有些则可能属于Request scope(每次请求时创建一个新的程序)

关于controllers,Hammett有效地重设了ASP.NET MVC's controller factory的默认行为。但在这个过程中,他提供了很多方法,通过在MefhttpApplication中重写CreateRootCatalog使我们能够以外来的方式跳转来改变行为。它的默认实现如下:

    1: protected virtual ComposablePartCatalog CreateRootCatalog()
    2: {
    3:     return new DirectoryCatalog(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "bin"));
    4: }

可能你也知道,ASP.NET MVC查找类通过常规的方法。通过末尾的"Controller"一词查找类。也是IController implmentations. 这里是以MEF的方法来声明使用微软ComponentModel.Composition.Extensions的惯列。注意scope的使用:

    1: [assembly: Discovery(typeof(Conventions))]
    2:  
    3:  namespace NerdDinner
    4: {
    5:     public class Conventions : ConventionDiscovery
    6:     {
    7:         public Conventions()
    8:         {
    9:             Add(PartRegistration.
   10:                     Types(t => t.Name.EndsWith("Controller") && !t.IsAbstract).
   11:                     Exporting((c, t) => c.
   12:                                         Contract(typeof (IController)).
   13:                                         Metadata("Name", t.Name.Substring(0, t.Name.Length - "controller".Length)).
   14:                                         Metadata("Mode", WebScopeMode.Request))
   15:                 );
   16:          }
   17:     }
   18: }

非常好。

Controllers 和它们的需求项
每当一个更高级的程序员看NerdDinner代码,他们通常会说,他们真的不喜欢这样的:
    1: public DinnersController() : this(new DinnerRepository())
    2: {
    3: }
    4:  
    5: public DinnersController(IDinnerRepository repository)
    6: {
    7:     dinnerRepository = repository;
    8: } 
第二个构造函数运用IDinnerRepository接口,使我们能够做出不同的实现,但是默认的构造函数写着,“如果没给的情况下,这里有一个具体的实现。”这是一个大滑坡,通过添加默认实现我开始回避使用依赖注入测试控制器,同时使该控制器可测试,。但我尝试将我的controller通过直接依赖关系与DinnerRepository进行连接。有时候这也被称为(蹩脚的IoC),很多人也会说这样确实蛮蹩脚的。那只是诚实的争论罢了,但Hammett采取的立场是删除默认构造函数。
    1: public class DinnersController : Controller
    2: {
    3:      private IDinnerRepository dinnerRepository;
    4:  
    5:      public DinnersController(IDinnerRepository repository)
    6:     {
    7:         dinnerRepository = repository;
    8:     }
    9: //...
   10: }

那么DinnersController是怎样连接上IDinnerRepository的呢?并不需要担心controllers 该怎样工作,而关键是看它想不想连接。

MEF对于组件是一项有效的约会服务。 在这里,DinnerRepository表明是有空的,想要结识同样在"IDinnerRepository”的人。

    1: [Export(typeof(IDinnerRepository))]
    2: public class DinnerRepository : NerdDinner.Models.IDinnerRepository {

[Export(导出)]属性说:“我正在寻找配对,媒人,给我找个合适的吧!”每当controller向MEF发出请求时,它注意到在我们这个case中没有构造函数,然后会找到一个有效的构造函数,说道:“哇,DinnersController也在找寻配对啊,我知道你的风格。”然后它创建一个DinnerRepository,然后通知DinnersController构造函数传入。然后进行依赖注入

你会经常发现其他的组件通过[Import(导入)]属性为它们的兴趣爱好打广告。但在这个例子中是没有必要用到的,因为所有的Controllers是通过MefControllerFactory创建的。它们不需要任何属性,因为它们已经走在约会服务的大道上。

MEFified的其他服务

最近在审查MVC Music Store Ayende(和在它之前的)被搁置的代码行。这些代码行实际上是创造性地配备了ASP.NET MVC 2内存不足,但并没有写到范例程序中,(尽管他们并没有被改掉), Phil可以在我不参与的情况下表明一些具体的决定,但是参与依赖注入的人都不喜欢这样。这个一个同样有效率的谋略跟上面提到的一样,只是写起来稍微有点不同而已。

    1: public class AccountController : Controller
    2:  {
    3:     public AccountController()
    4:        : this(null, null) {
    5:    }
    6:     public AccountController(IFormsAuthentication formsAuth, IMembershipService service)
    7:    {
    8:        FormsAuth = formsAuth ?? new FormsAuthenticationService();
    9:        MembershipService = service ?? new AccountMembershipService();
   10:    }
   11:    //...
   12: }

Hammett 的implementation仅用到了MEF,所以,

    1: public AccountController(IFormsAuthentication formsAuth, IMembershipService service)
    2: {
    3:     FormsAuth = formsAuth;
    4:     MembershipService = service;
    5: }

再者,重点是能自动地找出依赖项。 [Export(typeof(IFormsAuthentication))] Export(typeof(IMembershipService))] 都分别标着“可以连接”属性状态。

总之,MEF对ASP.NET MVC应用程序来说,是一个很棒的添加功能。非常感谢Hammett的鼎力帮助