使用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的一些使用,希望对大家有所帮助。

作者:TerryLee
出处:http://terrylee.cnblogs.com
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
posted @ 2008-09-01 00:21 TerryLee 阅读(6406) 评论(41)  编辑 收藏 网摘 所属分类: [01]  .NET大本营
Body:46.875,BeforeCate:15.625,0

  回复  引用  查看    
#1楼2008-09-01 00:22 | 重典      
好早,^^节日快乐。。。。
  回复  引用  查看    
#2楼[楼主]2008-09-01 00:25 | TerryLee      
@重典
呵呵,不早了^_^

  回复  引用    
#3楼2008-09-01 00:28 | xyly[未注册用户]
什么节日?
  回复  引用  查看    
#4楼2008-09-01 00:30 | 李永京      
我回来了,哎,先在这儿打住脚学习一下......
  回复  引用  查看    
#5楼[楼主]2008-09-01 00:33 | TerryLee      
@xyly
什么节日,其实我也不知道o(∩_∩)o...

  回复  引用  查看    
#6楼[楼主]2008-09-01 00:33 | TerryLee      
@李永京
欢迎回来,呵呵:)

  回复  引用  查看    
#7楼2008-09-01 00:40 | 重典      
@TerryLee
呵呵 其实我也不知道,原来想是教师节来着,后来一想,原来错了。。。不好意思,添乱了

  回复  引用  查看    
#8楼[楼主]2008-09-01 00:41 | TerryLee      
@重典
呵呵,不要紧:P

  回复  引用  查看    
#9楼2008-09-01 01:40 | 怪怪      
这些玩意共同的问题并非是“很多功能还不是很完善”,正是因为它们确实不是一个*简单*的DI容器,所以这种解决方案和倾向, 很可能从根本上来说就是错的; 如果不对各种可能的使用场景进行仔细的分析, 甚至有可能是有害的。
  回复  引用  查看    
#10楼2008-09-01 08:34 | 横刀天笑      
老大,貌似你的示例里的代码有问题,你检查下:
Code 2里面是[Export("Caption")]后来又变成了[Import("MyCaption")]这契约没遵守也能输出?
Code 11里面是 [Export(typeof(IMyCalculator))]?不对吧


觉得用这个框架就可以Mix in编程了

  回复  引用  查看    
#11楼2008-09-01 08:36 | kiler      
研究研究,Hammett参与的项目应该很不错的。
  回复  引用  查看    
#12楼2008-09-01 09:21 | Marvel's Technologies      
这么晚还没睡!向你学习了....
  回复  引用  查看    
#13楼[楼主]2008-09-01 09:51 | TerryLee      
@横刀天笑
不好意思,粘贴错误,已经更正过来了,多谢斧正:)

  回复  引用  查看    
#14楼[楼主]2008-09-01 09:54 | TerryLee      
@怪怪
呵呵,不太同意你的观点,为什么“这种解决方案和倾向, 很可能从根本上来说就是错的”?

  回复  引用  查看    
#15楼[楼主]2008-09-01 09:54 | TerryLee      
@kiler
Hammett8月份刚走马上任,呵呵:)

  回复  引用  查看    
#16楼[楼主]2008-09-01 09:55 | TerryLee      
@Marvel's Technologies
客气了,共同学习:)

  回复  引用  查看    
#17楼2008-09-01 09:57 | 小庄      
晕啊!Code 5 中还是[Import("MyCaption")]?Code 11 中[Export]后面没有了?

  回复  引用    
#18楼2008-09-01 10:01 | leleroyn[未注册用户]
感觉很好。
  回复  引用  查看    
#19楼[楼主]2008-09-01 10:04 | TerryLee      
@小庄

晕,已经修改,代码在家里机器上,晚上回家重新修改一下,见谅……

  回复  引用  查看    
#20楼[楼主]2008-09-01 10:04 | TerryLee      
@leleroyn
:)

  回复  引用  查看    
#21楼2008-09-01 12:06 | 真见      
支持支持在支持
  回复  引用  查看    
#22楼2008-09-01 13:16 | 小生      
模式之后...
不管怎樣﹐還是期待

  回复  引用    
#23楼2008-09-01 14:18 | hongyu[未注册用户]
佩服楼主的精神与毅力!!牛啊!

  回复  引用  查看    
#24楼[楼主]2008-09-01 22:06 | TerryLee      
@真见
谢谢支持:)

  回复  引用  查看    
#25楼[楼主]2008-09-01 22:06 | TerryLee      
@小生
嗯,期待MEF有更好的发展,呵呵:)

  回复  引用  查看    
#26楼[楼主]2008-09-01 22:07 | TerryLee      
@hongyu
谢谢:)

  回复  引用  查看    
#27楼2008-09-01 23:09 | 怪怪      
@TerryLee
这个问题比较复杂: 比如就是静态语言动态化的倾向和原生的动态特性之间的对比毫无优势; 比如元数据在C#这类语言中的使用方式还比较残废; 等等。

最根本的, 除了少数特别适合的任务, 它们一点不简单, 也不像宣传的那么简洁; 在直觉上就和真正正确的解决办法有冲突。 我初步的想法是, 问题可能在于使用库、模式、技巧来试图弥补表达能力上的缺陷, 就很难做的彻底。

更多的仅仅是因为.NET/Java领域能提供的解决方案因为历史原因比较有限, 暂时性的一种通用化的尝试罢了。这对于微软、SUN们来说, 主要是从补丁到彻底更新之间的尝试时期, 并希望尽量在现有基础上获得更多的东西。

当然, 路本来就是一步一步走的 :)

Update:

我发现我说话总词不达意。 来个比喻吧: Java/.NET这些的框架, 就好象C上的OO框架或者意图模拟的FP的东东。不是不能, 但在根本没法上解决好“如何组织”的问题.

这些东西的百花齐放, 也许恰恰反应了对组织工具(在这个问题上就是语言本身)的新需求。

  回复  引用  查看    
#28楼2008-09-02 15:16 | Colin Han      
节日快乐 ;-) 把昨天的日子反过来看不就是节日了。

@怪怪
我自己的一个应用程序试图做成可插拔式,最后发现出问题的确实是出在怎么组织这些对象上。
但是,我不太理解这个和语言本身有什么关系?难道其他的语言可以做的更好?给一些例子吧。

  回复  引用  查看    
#29楼2008-09-03 17:37 | jillzhang      
老李,你这都是从哪里学来的呀,这么多!
真服了你了,精力太好了

  回复  引用  查看    
#30楼[楼主]2008-09-03 21:42 | TerryLee      
@怪怪
有点难懂,呵呵,其实我觉的MEF仅仅是微软推出的一个小工具(框架)而已,在.NET领域中可能占的位置还不到1%,我们大可以把它当作一个开源项目来对待:)

  回复  引用  查看    
#31楼[楼主]2008-09-03 21:48 | TerryLee      
@Colin Han
呵呵,反过来看也行o(∩_∩)o...

  回复  引用  查看    
#32楼[楼主]2008-09-03 21:48 | TerryLee      
@jillzhang
呵呵,没事的时候就自己学习一些东西了,周末找你出去聊天:-)

  回复  引用  查看    
#33楼2008-09-08 23:52 | 秋色      
我来陪聊
  回复  引用    
#34楼2008-10-11 21:58 | 幸福[未注册用户]
左边的dotnet图做得真漂亮,佩服!
  回复  引用  查看    
#35楼2008-10-17 15:11 | Colin Han      
问一个问题,通过ImportAttribute,可以导入一个以实现某个接口的对象。但是,我怎样将所有实现了这个接口的对象全部导入到当前对象中来呢?
有没有类似下面的支持?

[Import(typeof(Interface))]
public IEnumerable<Interface> ImportObjects { get; set; }

我尝试让他将整个容器导入,但是,发现他不导入CompositionContainer,只能导入一个ICompositionService的对象,ICompositionService上没有GetBoundValues方法。:(

  回复  引用  查看    
#36楼2008-10-17 17:52 | Colin Han      
呵呵,找到解决方案了。
只需要如下:

[Import(typeof(Interface))]
public ImportInfoCollection<Interface> ImportObjects { get; set; }

  回复  引用  查看    
#37楼[楼主]2008-10-24 10:14 | TerryLee      
@Colin Han
:)

  回复  引用  查看    
#38楼2008-11-04 10:40 | ITAres      
TerryLee --国内.net同行的指路灯!!!
  回复  引用  查看    
#39楼2008-11-04 11:41 | venjiang      
good
  回复  引用  查看    
#40楼[楼主]2008-11-05 09:25 | TerryLee      
@ITAres
呵呵,太过奖了。

  回复  引用  查看    
#41楼[楼主]2008-11-05 09:25 | TerryLee      
@venjiang
:)

发表评论

昵称: [登录] [注册]

主页:

邮箱:(仅博主可见)

评论内容:

  登录  注册

[使用Ctrl+Enter键快速提交评论]

0 1280768 6g+oUSjTrWA=




相关文章:


相关搜索:
.NET Framework MEF open source [01].NET大本营

相关链接: