使用Managed Extensibility Framework方便的扩展应用程序

概述

Managed Extensibility Framework(MEF)是.NET平台下的一个扩展性管理框架,它是一系列特性的集合,包括依赖注入(DI)以及Duck Typing等。MEF为开发人员提供了一个工具,让我们可以轻松的对应用程序进行扩展并且对已有的代码产生最小的影响,开发人员在开发过程中根据功能要求定义一些扩展点,之后扩展人员就可以使用这些扩展点与应用程序交互;同时MEF让应用程序与扩展程序之间不产生直接的依赖,这样也允许在多个具有同样的扩展需求之间共享扩展程序。

本文将介绍一下Managed Extensibility Framework的一些简单使用。

简单依赖注入

大家可以去这里http://code.msdn.microsoft.com/mef下载MEF的CTP版本,在下载包里有一些简单的文档和示例。下面先来看一个简单的示例,这里输出一个字符串:

Code 1
// Author: TerryLee 2008.8.30
// URL: http://www.cnblogs.com/Terrylee
static void Main(string[] args)
{
    Console.WriteLine("This is a simple string.");
}

现在考虑到该字符串将来可能发生变化,不知道该字符串将从何处取得,也就是说这里有可能是一个变化点,也是一个扩展点,那我们现在使用MEF对其进行重新设计,我们将会把这个过程分成两个部分,一部分用于提供字符串,而另一部分则用来使用字符串,定义一个字符串提供程序,大家注意到这里为OutputTitle属性添加了一个Export特性,这标识着此处为一个输出,它使的MEF能够对其进行识别,如下代码所示:

Code 2
// Author: TerryLee 2008.8.30
// URL: http://www.cnblogs.com/Terrylee
public class WeekStringProvider
{
    [Export("Caption")]
    public String OutputTitle
    {
        get { return "星期六"; }
    }
}

这里只是定义了一个简单的属性,其实在同一个类型可以定义多个输出,Export同时指定了一个字符串的契约名称,这意味着任何匹配契约名称的程序都可以使用该扩展。除此之外,我们还可以指定一个类型来代替字符串的契约名称,后面会说到。我们再定义一个输入,即用来消费该字符串,同样是一个简单的属性,不过这次添加的是Import特性:

Code 3
// Author: TerryLee 2008.8.30
// URL: http://www.cnblogs.com/Terrylee
public class Client
{
    [Import("Caption")]
    public String OutputTitle { get; set; }
}

现在有了输出和输入,就可以在主程序中进行调用了,需要创建一个CompositionContainer容器,并添加所有的组件到该容器中,再调用它的Bind()方法,一旦调用该方法后,就可以使用所有的组件了,如下代码所示:

Code 4
// Author: TerryLee 2008.8.30
// URL: http://www.cnblogs.com/Terrylee
static void Main(string[] args)
{
    Client client = new Client();

    CompositionContainer container =
                 new CompositionContainer();

    container.AddComponent<Client>(client);
    container.AddComponent<WeekStringProvider>(new WeekStringProvider());
    container.Bind();

    Console.WriteLine(client.OutputTitle);
}

输出结果如下图所示:

TerryLee_0188

现在我们再定义另外一个扩展程序,让它返回一个日期字符串,如下代码所示:

Code 5
// Author: TerryLee 2008.8.30
// URL: http://www.cnblogs.com/Terrylee
public class DateStringProvider
{
    [Export("Caption")]
    public String OutputTitle
    {
        get { return DateTime.Now.ToLongDateString(); }
    }
}

修改一下组件注册程序,如下代码所示:

Code 6
// Author: TerryLee 2008.8.30
// URL: http://www.cnblogs.com/Terrylee
static void Main(string[] args)
{
    Client client = new Client();

    CompositionContainer container =
                 new CompositionContainer();

    container.AddComponent<Client>(client);
    container.AddComponent<DateStringProvider>(new DateStringProvider());
    container.Bind();

    Console.WriteLine(client.OutputTitle);
}

输出结果如下图所示:

TerryLee_0189 

上面的示例中我们是使用了命名契约,除此之外,还可以使用类型契约,如定义一个字符串提供者程序的接口:

Code 7
// Author: TerryLee 2008.8.30
// URL: http://www.cnblogs.com/Terrylee
public interface IStringProvider
{
    String OutputTitle { get; set; }
}

现在输出和输入对应的修改为如下代码:

Code 8
// Author: TerryLee 2008.8.30
// URL: http://www.cnblogs.com/Terrylee
public class DateStringProvider : IStringProvider 
{
    [Export(typeof(IStringProvider))]
    public String OutputTitle
    {
        get { return DateTime.Now.ToLongDateString(); }
    }
}
Code 9
// Author: TerryLee 2008.8.30
// URL: http://www.cnblogs.com/Terrylee
public class Client
{
    [Import(typeof(IStringProvider))]
    public String OutputTitle { get; set; }
}

运行后可以看到它与前面的示例效果是一样的。

Duck Typing支持

了解DI的朋友可能都有这样的疑问,其实上面的代码就是一个依赖注入,微软模式与实践团队已经开发出了Unity,为什么还需要一个MEF呢?其实MEF的定位并不是DI,在前面我已经说过,它主要是用于应用程序扩展管理,下面我们再看一个示例,它在这方面具有什么样的优势,看下面这段代码:

Code 10
// Author: TerryLee 2008.8.30
// URL: http://www.cnblogs.com/Terrylee
[ContractType("TerryLeeCalculatro")]
public interface ICalculator
{
    int Execute(int x, int y);
}

public class Client
{
    [Import(typeof(ICalculator))]
    public ICalculator Calculator { get; set; }
}

这里我们定义了一个输入,它里面具有一个ICalculator的属性,也就是说它需要的输入是一个类型是ICalculator的实例。现在我们定义输出,如下代码所示:

Code 11
// Author: TerryLee 2008.8.30
// URL: http://www.cnblogs.com/Terrylee
[Export(typeof(ICalculator))]
public class SubCalculator : ICalculator
{

    public int Execute(int x, int y)
    {
        return x - y;
    }
}

在主函数中进行调用:

Code 12
// Author: TerryLee 2008.8.30
// URL: http://www.cnblogs.com/Terrylee
static void Main(string[] args)
{
    Client client = new Client();

    CompositionContainer container =
                 new CompositionContainer();

    container.AddComponent<Client>(client);
    container.AddComponent<SubCalculator>(new SubCalculator());
    container.Bind();

    Console.WriteLine(client.Calculator.Execute(1,2));
}

输出结果如下图所示:

TerryLee_0190 

现在我们需要对该程序扩展,让其计算结果为两个数相加,如果使用DI,我们可能会想到,再编写一个支持加法计算的类,让其实现ICalculator接口,然而这里我们重新定义了一个新的接口IMyCalculator:

Code 13
// Author: TerryLee 2008.8.30
// URL: http://www.cnblogs.com/Terrylee
[ContractType("TerryLeeCalculatro")]
public interface IMyCalculator
{
    int Execute(int x, int y);
}

[Export(typeof(IMyCalculator))]
public class AddCalculator : IMyCalculator
{
    
    public int Execute(int x, int y)
    {
        return x + y;
    }
}   

这里重新定义了一个新接口IMyCalculator,我们为它设置的契约类型和前面定义的接口ICalculator一致。而AddCalculator实现这个接口,同样用Export标识它为一个输出。最后调用程序如下:

Code 14
// Author: TerryLee 2008.8.30
// URL: http://www.cnblogs.com/Terrylee
static void Main(string[] args)
{
    Client client = new Client();

    CompositionContainer container =
                 new CompositionContainer();

    container.AddComponent<Client>(client);
    container.AddComponent<AddCalculator>(new AddCalculator());
    container.Bind();

    Console.WriteLine(client.Calculator.Execute(1,2));
}

输出结果如下图所示:

TerryLee_0191 

大家可能已经意识到了,上面示例中的输入需要ICalculator类型,而我们扩展的输出却是IMyCalculator类型,它仅仅是与ICalculator标识为相同的契约类型,这种方式带来了极大的灵活性,也就是说我们在对原有应用程序进行扩展时,并不需要与原有应用程序产生任何依赖,可以独立的进行扩展。

Plug-In支持

在前面的例子中,始终有一个问题没有解决,就是当每次编写一个扩展程序后,都需要修改代码向CompositionContainer中注册组件,这样其实并没有实现真正的扩展,我们希望的扩展是Plug-In机制。在MEF对于Plug-In提供了很好的支持,它提供了DirectoryWatchingComponentCatalog类来对指定的目录进行监视,就是说我们定义好了输入之后,只要把相关的输出组件放在指定目录中,MEF会通过反射来进行自动查找,如我们定义这样的一个输入:

Code 15
// Author: TerryLee 2008.8.30
// URL: http://www.cnblogs.com/Terrylee
public class User
{
    [Import("Role")]
    public String Role { get; set; }
}

现在定义输出,我们把它放在一个单独的类库项目中:

Code 16
// Author: TerryLee 2008.8.30
// URL: http://www.cnblogs.com/Terrylee
public class DatabaseProvider
{
    [Export("Role")]
    public String AvailableRole
    {
        get { return "Developer"; }
    }
}

在主调用程序的目录下,我们创建一个Extensions的目录,然后把相关的扩展组件都放在该目录下,并在主调用程序中,为DirectoryWatchingComponentCatalog实例加入Extensions目录,这样就避免了与具体的扩展应用程序产生依赖,如下代码所示:

Code 17
// Author: TerryLee 2008.8.30
// URL: http://www.cnblogs.com/Terrylee
static void Main(string[] args)
{
    User user = new User();

    DirectoryWatchingComponentCatalog catalog = 
            new DirectoryWatchingComponentCatalog();
    catalog.AddDirectory(@"Extensions");

    CompositionContainer container = new CompositionContainer(catalog.Resolver);
    container.AddComponent<User>(user);
    container.Bind();

    Console.WriteLine(user.Role);
    Console.ReadLine();
}

运行后输出结果如下:

TerryLee_0192  

对于MEF来说,Duck Typing支持以及Plug-In支持才是它的优势所在,它不是一个简单的DI容器,而是一个真正的管理扩展框架。当然了现在MEF还处于CTP阶段,很多功能还不是很完善。在8月初,微软还特意请到了Castle之父Hammett加入该项目组,担任Program Manager,MEF的未来值得期待,更值得期待的是MEF将会为Silverlight应用程序开发一个MEF子集,让我们对于Silverlight程序也能够方便的进行扩展。

Managed Extensibility Framework的官方主页是:http://code.msdn.microsoft.com/mef

总结

本文简单介绍了Managed Extensibility Framework的一些使用,希望对大家有所帮助。

posted @ 2008-09-01 00:21  TerryLee  阅读(11755)  评论(42编辑  收藏  举报