Joycode@Ab110.com

Visual Studio 扩展性

[原文地址]:Visual Studio Extensibility

[原文发表时间]:Thursday, September 25, 2008 9:03 PM
我对我们的合作伙伴策略非常有信心。也就是说,我们的商业模式确保我们可以创造一个强有力的开发工具平台以及一系列的工具。我非常期待和我们的合作伙伴一起,在我们的平台上开发出更多的工具产品。这样,结合我们和我们的合作伙伴所提供的产品,我们可以有一个更为宽泛的工具集合来提供给我们的开发者客户。
 
上周,我们召开了一年一度的开发者大会。这些开发者都是基于我们的工具平台来开发他们的Visual Studio扩展工具的。对我们的产品开发部门来说,跟他们展开一个如何集成我们现有产品的对话,以及分享我们今后的计划来帮助我们的合作伙伴跟我们共同前进都是一个很好的机会。这是第一次我们把这个大会开放给所有的Visual Studio的扩展性开发者,使得广大的开发者社区有机会学习怎么来扩展我们的现有技术。
 
大会非常成功。一共有325名与会者,代表了125家公司,其中包括2个还在高中年龄的年轻的开发者!我们有超过20个关于VS2008扩展性的讲座,包括5个由社区开发者做的讲座。
 
一些摘自大会参加者的反馈非常生动地反映了他们对此次会议的感受:“在这一周,我接受了许多东西,以至于我需要租用一架货机来装所有收到的信息,主意,学习材料和联系信息回比利时。”“这个会议能覆盖这么大的信息量,我印象深刻。”以及“没有比VSIP Summit更加深刻地给出了我们的合作伙伴是多么重要的这个信号。”我非常高兴地从这些合作伙伴这里听到他们说从我们的产品部门得到了很多。我们知道我们的成功依赖于我们的合作伙伴和广大开发者的成功。
 
一些讲座是以Screencast的形式录制下来,下周开始可以在VSX Dev Center上下载。
 
Namaste!
Team Dev = Team Dev + Team Data

[原文地址]Team Dev = Team Dev + Team Data

[原文发表时间]Thursday, October 02, 2008 11:17 AM

我们发现,开发人员的工作范围正在变得越来越宽泛。一个经常听到的反馈是,在创建完一系列数据库表格后,还要撰写一堆前端代码才能访问这些数据。

针对这种情况,我们发布了一款新的集成化的Visual Studio Team System 2008 Development Edition。它把Team Development EditionTeam Database Edition的功能合二为一。这样一来,开发人员就可以同时获得用于应用程序开发的核心工具以及针对数据库开发的必要工具。这为你和你的开发团队提供了一个容易使用的一站式的开发环境。

Visual Studio Professional Edition的客户只要再购买一个附带MSDN订阅的Visual Studio Team System 2008 Development Edition许可证或者一个附带MSDN订阅的Visual Studio Team System Database Edition许可证,就可以上述集成版本的所有功能。一旦按要求购买后,你可以去MSDN订阅下载网站下载相关安装文件(请向你所在组织的MSDN管理员获取访问权限)。如果你购买的是Team System Development Edition (Team Dev + MSDN),那么Visual Studio Team System Database Edition会出现在你的下载列表里。你可以通过这里访问MSDN订阅网站

欲了解详情,请点击这里

Namaste!

Type 'System.Web.UI.WebControls.Parameter' does not have a public property named 'DbType'

In Visual C# 2005 SP1, I added an object data source to a web page that uses my business class as the select method. The method has one parameter of type Guid. The data source wizard generates code like this

<asp:Parameter DbType="Guid" Name="rowId" />

Although the web server has .Net 2.0 SP1 installed (I checked the registry), it still throws an error

Type 'System.Web.UI.WebControls.Parameter' does not have a public property named 'DbType'

The walk around is easy:

<asp:Parameter Type="Object" Name="rowId" />

使用托管扩展性框架(MEF)创建可扩展应用的简介

【原文地址】Simple Introduction to Extensible Applications with the Managed Extensions Framework

【原文发表日期】29 September 08 08:21

 

近期我的团队一直致力于托管扩展性框架(Managed Extensions Framework)(MEF)的工作……现在我获得了一个机会向各位解释它的概念,而且我认为我已经发现一种新方法来讲解MEF,以使各位能更容易地理解接受它。因为我想花一点点时间来浏览一个非常简单MEF示例,以这种方式为大家介绍一般的扩展应用的能力,以及MEF独特的功能。

顺便说一句,你可以下载当前CTP版本的MEF最终可运行的示例

 

背景

 

让我们从最简单的例子开始:Hello World!

   1: using System;
   2:  
   3: class Program
   4: {
   5:     public void Run()
   6:     {
   7:         Console.WriteLine("Hello World!");
   8:         Console.ReadKey();
   9:     }
  10:     static void Main(string[] args)
  11:     {
  12:         Program p = new Program();
  13:         p.Run();
  14:     }
  15: }

现在,你也许不想永远打出相同的字符串,那么让我们稍稍地重构一下,将字符串抽取出来……

   1: public string Message { get; set; }
   2:  
   3: public void Run()
   4: {
   5:     Console.WriteLine(Message);
   6:     Console.ReadKey();
   7: }

看起来不错,现在我们要添加message……好吧,实际的文本内容是一项独立的关注点,它应该来自另外的类。比如:

   1: public class SimpleHello 
   2: {
   3:     public string Message
   4:     {
   5:         get
   6:         {
   7:             return "hello world!!";
   8:         }
   9:     }
  10: }

现在我们只需简单地把它们拼装起来:

   1: public void Run()
   2: {
   3:     SimpleHello hello = new SimpleHello();
   4:     Message = hello.Message;
   5:  
   6:     Console.WriteLine(Message);
   7:     Console.ReadKey();
   8: }

这已经能够运行了,但第3和第4行看上去有点怪怪的……我们带来了紧耦合的问题……我们真正想做的是将第3和第4行外部化,在不改变程序逻辑的其它部分的情况下控制它们。

 

进入MEF

添加对System.ComponentModel.Composition.dll程序集的引用,它可在MEF压缩包的bin目录下找到。

添加

   1: using System.ComponentModel.Composition;

现在,在Program类里,我们需要导入Message的值 -- 就是说,我们要指定程序外部的某段代码必须提供message。随后我们就要移除紧耦合。注意第4和第5行,我们指定了要导入Message的值。这里我将展示一下如何根据类型(字符串)引入……基础的类型,例如字符串可能会显得太通用了,因此可以考虑使用一个具名的import,例如[Import(“Message”)]。

   1: class Program
   2: {
   3:  
   4:     [Import]
   5:     public string Message { get; set; }
   6:  
   7:     public void Run()
   8:     {
   9:        // SimpleHello hello = new SimpleHello();
  10:         //Message = hello.Message;
  11:  
  12:         Console.WriteLine(Message);
  13:         Console.ReadKey();
  14:     }

现在我们需要在SimpleHello类中导出Message这个属性,它通知系统它能够满足需求。注意第3和第4行添加了一个Export属性(attribute)……同样地,它根据类型(在此为字符串)进行导出。同上,你也许会在实际运用中添加一个显式的名称[Export(“Message”)]。

   1: public class SimpleHello 
   2: {
   3:     [Export]
   4:     public string Message
   5:     {
   6:         get
   7:         {
   8:             return "hello world!!";
   9:         }
  10:     }
  11: }

现在我们需要告诉MEF为我们拼装这些内容。

   1: public void Run()
   2: {
   3:     //SimpleHello hello = new SimpleHello();
   4:     //Message = hello.Message;
   5:     var catalog = new AttributedAssemblyPartCatalog(Assembly.GetExecutingAssembly());
   6:     var container = new CompositionContainer(catalog.CreateResolver());
   7:     container.AddPart(this);
   8:     container.Compose();
   9:  
  10:  
  11:     Console.WriteLine(Message);
  12:     Console.ReadKey();
  13: }

在第5行,我们创建了一个catalog——它告诉MEF在何处寻找导入与导出。在此,我们指定了当前运行的程序集。共有成千上万种不同的catalog,我们稍后再来看看,当然你也可以自行创建catalog。

在第6行,我们创建了一个Composition container——这就是实际上将所有不同的部分拼装起来的地方。

在第7行,我们将当前Program类的实例作为依赖项加入到container中。

在第8行,我们调用了Compose,这也就是Program类的Message属性得以赋值之处。

注意,这个例子中拼装是通过类型匹配实现的(字符串到字符串)……显然这不总是正确的方法,我们稍后再看看其它的拼装方法。

运行它,你就会看到期待中的输出“hello world!”。

现在我们再添加一个message,以添加一点乐趣……

   1: public class MoreMessages
   2: {
   3:     [Export]
   4:     public string FunMessage
   5:     {
   6:         get
   7:         {
   8:             return "This is getting fun!";
   9:         }
  10:     }
  11: }

现在运行……程序崩溃了!为什么?好吧,让我们看一看异常:

System.ComponentModel.Composition.CompositionException  Error : Multiple exports were found that match the constraint '(composableItem.ContractName = \"System.String\")'. The import for this contract requires a single export only."

从错误中看起来,似乎是我们为Import提供了太多的选择……MEF不知道该选择哪个了。当然你可以以编程的方式解决它,你也可以移除其中一个message的export……不过更有趣的是,实际上你可以告诉MEF你能够处理零个或多个结果。如下所示改变Program的Message属性。

   1: [Import]
   2: public IEnumerable<string> Messages { get; set; }

注意到我们将返回类型更改为一个字符串集合,而不是仅仅单个字符串。

现在稍稍地改变调用的代码,我们获得了:

   1: class Program
   2: {
   3:  
   4:     [Import]
   5:     public IEnumerable<string> Messages { get; set; }
   6:  
   7:     public void Run()
   8:     {
   9:         //SimpleHello hello = new SimpleHello();
  10:         //Message = hello.Message;
  11:         var catalog = new AttributedAssemblyPartCatalog(Assembly.GetExecutingAssembly());
  12:         var container = new CompositionContainer(catalog.CreateResolver());
  13:         container.AddPart(this);
  14:         container.Compose();
  15:  
  16:         foreach (var s in Messages)
  17:         {
  18:             Console.WriteLine(s);
  19:         }
  20:  
  21:     
  22:         Console.ReadKey();
  23:     }

Console output 1

哇噢——我们获得了两个message!太酷了!

MEF更多的价值

OK,我想我们都同意,当我们做的工作影响到了同一个程序集的时候,这种方式实际上给我们的代码增加了一点复杂度。当你各自的部门工作于不同的组件时,MEF才最管用。根据定义,这些组件通常在没有交叉引用的独立程序集内。为展示MEF是怎样支持这一点的,让我们添加一个新的Class Library项目到解决方案中,命名为ExternalMessages,并添加对System.ComponentModel.Composition.dll程序集的引用。

添加如下的类。

   1: using System;
   2: using System.ComponentModel.Composition;
   3:  
   4: public class Class1
   5: {
   6:     [Export]
   7:     public string Message
   8:     {
   9:         get
  10:         {
  11:             return "I am starting to get it...";
  12:         }
  13:     }
  14: }

现在我们需要将这个类拼装入catalog……注意第6行,我们将catalog改为在某个目录中寻找所需各部分。

   1: public void Run()
   2:  {
   3:      //SimpleHello hello = new SimpleHello();
   4:      //Message = hello.Message;
   5:      var catalog = new DirectoryPartCatalog(@"..\..\..\ExternalMessages\bin\Debug");
   6:          // new AttributedAssemblyPartCatalog(Assembly.GetExecutingAssembly());
   7:      var container = new CompositionContainer(catalog.CreateResolver());
   8:      container.AddPart(this);
   9:      container.Compose();
  10:  
  11:      foreach (var s in Messages)
  12:      {
  13:          Console.WriteLine(s);
  14:      }
  15:  
  16:  
  17:      Console.ReadKey();
  18:  }

注意:DirectoryPartCatalog同样支持相对路径,它会在当前的AppDomain.CurrentDomain.BaseDirectory下的路径进行查找,比如说:

new DirectoryPartCatalog(@”.\extensions\”);

运行它,我们就获得了新的message!

酷,但我们也失去了原来的message,而我希望也能得到它们……好吧,幸运的是,我们还有一个聚合部件目录类(aggregate part catalog)能够从多个源获取所需部分。

   1: public void Run()
   2:  {
   3:      //SimpleHello hello = new SimpleHello();
   4:      //Message = hello.Message;
   5:      var catalog = new AggregatingComposablePartCatalog();
   6:         catalog.Catalogs.Add (new DirectoryPartCatalog(@"..\..\..\ExternalMessages\bin\Debug"));
   7:         catalog.Catalogs.Add (new AttributedAssemblyPartCatalog(Assembly.GetExecutingAssembly()));
   8:      var container = new CompositionContainer(catalog.CreateResolver());
   9:      container.AddPart(this);
  10:      container.Compose();

太酷了,现在我们获得了所有的message!

Console output 2 

最后,提一下这一点……我创建了一些导出message的不同的程序集……所要做的仅是将catalog指向它们并运行。

Multiple assemblies

   1: public void Run()
   2: {
   3:     //SimpleHello hello = new SimpleHello();
   4:     //Message = hello.Message;
   5:     var catalog = new AggregatingComposablePartCatalog();
   6:        catalog.Catalogs.Add (new DirectoryPartCatalog(@"..\..\..\ExternalMessages\bin\Debug"));
   7:        catalog.Catalogs.Add(new DirectoryPartCatalog(@"..\..\..\ExtraMessages"));
   8:        catalog.Catalogs.Add (new AttributedAssemblyPartCatalog(Assembly.GetExecutingAssembly()));
   9:     var container = new CompositionContainer(catalog.CreateResolver());
  10:     container.AddPart(this);
  11:     container.Compose();
  12:  

看我在第7行把它们加入的路径……现在只需把程序集拷贝到这个目录下,它们就可以在程序中使用了!注意即使我不断地添加更多的扩展,我也不需要改变任何核心程序的逻辑。

Console output 3

将MEF带入下一层次

 

以上我仅展示了最简单的场景……让我们尝试一些更强大的。如果你仍然吹毛求疵地想在主程序中找到紧耦合的部分,那么Console.WriteLine()将显露出来……如果你想写到日志中呢?如果你想调用一个web service或者是输出到HTML或WPF呢?对Console的紧耦合使这一点不容易做到。我们将如何使用关注分离(separation of concerns)原则与MEF来消除这种紧耦合呢?

首先,我们需要定义一个接口,描述输出字符串的契约。为确保正确的依赖项管理,让我们创建一个新的Library项目并命名为SharedLibrary,添加这个接口,并让其它所有的项目都添加对这个项目的引用。

   1: namespace SharedLibrary
   2: {
   3:     public interface IOutputString
   4:     {
   5:         void OutputStringToConsole(string value);
   6:     }
   7: }

现在回到主程序,我们将能够提取出Console.WriteLine()……

   1: class Program
   2: {
   3:     [Import]
   4:     public IEnumerable<string> Messages { get; set; }
   5:  
   6:     [Import]
   7:     public IOutputString Out { get; set; }
   8:  
   9:     public void Run()
  10:     {
  11:         //SimpleHello hello = new SimpleHello();
  12:         //Message = hello.Message;
  13:         var catalog = new AggregatingComposablePartCatalog();
  14:            catalog.Catalogs.Add (new DirectoryPartCatalog(@"..\..\..\ExternalMessages\bin\Debug"));
  15:            catalog.Catalogs.Add(new DirectoryPartCatalog(@"..\..\..\ExtraMessages"));
  16:            catalog.Catalogs.Add (new AttributedAssemblyPartCatalog(Assembly.GetExecutingAssembly()));
  17:         var container = new CompositionContainer(catalog.CreateResolver());
  18:         container.AddPart(this);
  19:         container.Compose();
  20:  
  21:         foreach (var s in Messages)
  22:         {
  23:             Out.OutputString(s);
  24:         }
  25:  
  26:     
  27:         Console.ReadKey();
  28:     }

在第6、7行中我们定义了Out,并在第23行中将Console.WriteLine()改为Out.OutputString()。

现在在ExternalMessages项目中添加以下类

   1: [Export(typeof(IOutputString))]
   2: public class Class1 : IOutputString
   3: {
   4:     public void OutputString(string value)
   5:     {
   6:         Console.WriteLine("Output=" + value);
   7:     }
   8:  
   9:     

注意我们在这里显式地声明了导出类型为刚才定义的共享接口。现在当程序运行时,我们获得了:

Console output 4 

为了更有趣,让我们添加另一个更有创意的IOutputString的实现。

   1: [Export(typeof(IOutputString))]
   2: public class ReverseOutputter : IOutputString
   3: {
   4:  
   5:     public void OutputString(string value)
   6:     {
   7:         foreach (var s in value.Split().Reverse())
   8:         {
   9:             Console.ForegroundColor = (ConsoleColor)(s.Length % 10);
  10:             Console.Write(s + " ");
  11:         }
  12:         Console.WriteLine();
  13:     }
  14: }

现在就运行的话会给我们一个错误,因为我们告诉了MEF我们只需要一个IOutputString……如果我们改变一下代码以获取多个的话就更有趣了!将第7行改为接受一个IOutputString的集合,而在第19行指定对所有输出设备进行循环。

   1: class Program
   2: {
   3:     [Import]
   4:     public IEnumerable<string> Messages { get; set; }
   5:  
   6:     [Import]
   7:     public IEnumerable<IOutputString> OutputSet { get; set; }
   8:  
   9:     public void Run()
  10:     {
  11:         var catalog = new AggregatingComposablePartCatalog();
  12:            catalog.Catalogs.Add (new DirectoryPartCatalog(@"..\..\..\ExternalMessages\bin\Debug"));
  13:            catalog.Catalogs.Add(new DirectoryPartCatalog(@"..\..\..\ExtraMessages"));
  14:            catalog.Catalogs.Add (new AttributedAssemblyPartCatalog(Assembly.GetExecutingAssembly()));
  15:         var container = new CompositionContainer(catalog.CreateResolver());
  16:         container.AddPart(this);
  17:         container.Compose();
  18:  
  19:         foreach (var Out in OutputSet)
  20:         {
  21:             foreach (var s in Messages)
  22:             {
  23:                 Out.OutputString(s);
  24:             }
  25:         }
  26: