Visual Studio 2008中WCF的新增功能

互联网 | 编辑: 江海明 2008-08-22 11:52:00转载

Visual Studio 2008和基础Microsoft .NET Framework 3.5提供了新工具,并支持Windows Communication Foundation(WCF)。它们并未改变WCF 1.0(与.NET Framework 3.0一起发布)的基本功能,而是对其进行了扩展和完善。

Visual Studio 2008可自动执行WCF手动任务,包括更新代理引用和消除重复任务(例如创建简单托管项目)。Visual Studio还能解决跨目标和数据协定类型共享等一些困难问题。在本专栏中,我将逐步介绍其新功能及优势,并对其任何缺陷和解决方法做出解释。尽管我将在此使用C#项目设置,但除非我另行说明,否则所有设置同样适用于Visual Basic。

.NET Framework跨目标

Visual Studio的早期版本通常针对随其一起提供的 .NET Framework 版本。例如,Visual Studio 2005 只能生成针对 .NET Framework 2.0 的程序集,而此惯例并不能反映大多数开发人员所面临的真实情况。通常,开发人员需要在维护针对早期版本的 .NET 而编写的旧版应用程序的同时,还要在其新版应用程序中使用新版 Visual Studio。

此外,此惯例还意味着开发人员在维护为早期版本的 .NET Framework 而编写的应用程序时,无法从效率增强功能(例如 Visual Studio 2005 中引入的代码重构支持)中获益。

问题在于 .NET Framework 各版本不支持跨目标功能。您要么必须安装多个版本的 Visual Studio,要么使用独立的测试和部署内部版本进行补偿。Visual Studio 2008尝试通过为多个版本的.NET Framework提供充分支持(虽然不完美)来解决这一问题。由于实际上.NET Framework 3.0和.NET Framework 3.5使用的CLR版本与.NET Framework 2.0使用的相同,唯一的区别在于新引用的程序集,因此Visual Studio仍可针对相同运行库,而为.NET Framework版本2.0、3.0和3.5(其中.NET Framework版本号对应的是发行版本,而不是运行库版本,运行库版本仍为CLR 2.0)提供跨目标功能。

在Visual Studio 2008中,“Properties”(项目)的“Application”(应用程序)窗格包含一个称为“Target Framework”(目标框架)的新组合框,通过该组合框您就可以针对.NET Framework版本2.0、3.0和3.5(参见图1)。

498)this.style.width=498;">
图1:Visual Studio 2008中的目标框架属性

Target Framework值仅在开发时有效,而在运行时无效(您的程序集仍指向.NET 2.0 CLR)。您选择的值表示您构建程序集时可针对的.NET Framework的最早版本。新项目默认被配置为针对.NET Framework 3.5。如果添加引用,则会变得有些复杂;如果在引用较高版本程序集时,降低Target Framework版本,Visual Studio 2008将提示您引用出错和内部版本失败。Visual Studio 2008不允许您向其所需Framework版本高于现有项目的.NET Framework程序集添加引用。如果您向版本更高的同一解决方案中的其他项目添加引用,Visual Studio 2008将警告您可能会发生冲突。如果通过浏览程序集来添加引用,Visual Studio 2008将不会干预您进行操作。

在语言和跨目标方面,请注意,您可以在C#中(而不是Visual Basic中),通过限制编译器版本来限制使用.NET Framework 2.0或3.0项目中诸如匿名类型和扩展方法等功能。您可以转至“Build”(内部版本)窗格,然后单击“Advanced”(高级)按钮,并选择“ISO-2(C# 2.0)”作为语言版本(而不是尚未标准化的默认版本)来执行此操作。

在Visual Studio 2008中打开Visual Studio 2005 WCF项目时,升级过程中框架版本保持为 2.0。实际上这可以正常运行(请记住,基础运行库版本并未改变),但我建议根据需要手动将其设置为版本 3.0 或 3.5。

使用新项目模板时,Target Framework 版本是最重要的因素。WCF 工作流和整合项目必须针对 .NET Framework 3.5 进行构建;Service Library 项目要求针对 .NET Framework 3.0 或 3.5。“Add Service Reference”(添加服务引用)功能仅当为项目选择 Framework 版本 3.0 或 3.5 时可用,本专栏稍后将介绍此功能。

WCF 提供的主机

Visual Studio 2008 附带了一款名为 WcfSvcHost.exe 的现成的通用服务主机。它位于 C:Program FilesMicrosoft Visual Studio 9.0Common7IDE 中。为了方便使用,我建议将该位置添加到系统的 Path 变量中。WcfSvcHost 是一种简单的命令行实用工具,可接受两个参数:一个是指向包含一个或多个服务类的 .NET 程序集的文件路径,另一个是指向托管 .config 文件的文件路径。例如:

WcfSvcHost.exe /service:MyService.dll  /config:App.config

指定的服务程序集可以是类库程序集 (DLL),也可以是应用程序程序集 (EXE)。WcfSvcHost 将启动一个新进程,该进程将自动托管指定 .config 文件的服务部分中列出的所有服务类。请注意,这些服务类及其服务约定和数据协定不必是公共类型,可以是内部类型。此外,自动托管的服务不需要提供任何元数据,但是它们可以发布元数据(如果选择发布)。

WcfSvcHost 是一种以桌面任务栏图标驻留的 Windows 窗体应用程序。若要关闭主机,只需从任务栏图标上下文菜单中选择“Exit”(退出)即可。使用这种方式终止托管不太妥当,因为 WcfSvcHost 将中止当前正在进行的所有调用,客户端很可能会收到异常。如果单击 WcfSvcHost 任务栏图标,将出现一个对话框,其中列出托管的所有服务(参见图 2)。

498)this.style.width=498;">
图2:WcfSvcHost 服务列表

该对话框还显示服务的状态及其元数据地址,您可以将这些信息复制到剪贴板,稍后向服务添加引用时可能会用得到。关闭 WcfSvcHost UI 只是将其折叠回任务栏。

WcfSvcHost 的目的就是在开发过程中不再需要使用独立托管程序集来配合服务库。诸如托管项目等的开发工作是一种重复任务,这些主机通常包含大量相同的代码行,当有多个服务库时,这些重复代码容易导致解决方案变得臃肿。为了便于开发和测试,可以将 WcfSvcHost 直接集成到您的 Visual Studio 2008 服务库项目中。在项目属性的“Debug”(调试)窗格中,将 WcfSvcHost.exe 指定为要启动的外部程序,然后将您的类库名及其 .config 文件(自动生成并自动复制到 bin 文件夹)指定为参数。

完成后,当您运行类库(完成上述操作后才能执行运行)时,它将借助该进程附带的调试器由 WcfSvcHost 自动托管。停止调试时,Visual Studio 2008 将以不妥当的方式中止托管。

您甚至可以在 .NET Framework 3.0 应用程序中与 Visual Studio 2005 项目一起使用 WcfSvcHost,由于 WcfSvcHost 只需要 .NET Framework 3.0,因此只需从安装了 Visual Studio 2008 的计算机上复制 WcfSvcHost 即可。为了使用更加方便,建议您将 WcfSvcHost 添加到 .NET Framework 3.0 计算机上的全局程序集缓存 (GAC) 中。

WcfSvcHost 的最后一个功能是能够自动启动客户端应用程序,甚至能够为客户端提供特定于该应用程序的可选参数:

WcfSvcHost.exe /service:MyService.dll  /config:App.config                 
/client:MyClient.exe    /clientArgs:123,ABC

此功能对于在自动测试、甚至是简单部署的情况下启动主机和客户端非常有用。

WcfSvcHost 的最大缺陷是只适用于简单情况,例如在打开主机实例之前无需以编程方式访问主机实例的情况,或在其打开之后以编程方式访问其事件模型的情况。与使用 IIS 或 Windows Activation Service (WAS) 进行托管不同,WcfSvcHost 没有对等的服务主机工厂支持。因此,不具备动态添加基址、配置终结点、中止调用以及在主机级别配置自定义行为等功能。通过我对 WCF 的使用体验发现,除了最简单的情况之外,所有其他情况中最终都需要以编程方式访问主机实例,因此我并不将 WcfSvcHost 视为一种成熟、高效的主机,而我自己执行 WAS 或进行专用自托管。

WCF 提供的测试客户端

除服务主机外,Visual Studio 2008 还随附了一个用于进行基本测试的简单通用的测试客户端,您可以使用它调用大多数服务上的操作。正常安装后,测试客户端 WcfTestClient.exe 位于 WcfTestClient.exe, is found after normal installation at C:Program FilesMicrosoft Visual Studio 9.0Common7IDE 下。您必须为 WcfTestClient 提供一个命令行参数,其中包含要测试的服务的元数据地址:

WcfTestClient.exe http://localhost:9000/

您可以指定任何元数据地址,例如 HTTP-GET 或者 HTTP、TCP 或 IPC(命名管道)上的元数据终结点。也可以指定多个元数据地址:

WcfTestClient.exe http://localhost:8000/ net.tcp://localhost:9000/MEX

WcfTestClient 是一个 Windows Forms 3.5 应用程序(参见图 3)。在此图中,左侧的树控件包含测试的服务及其终结点。您可以详细查看某个终结点的约定并选择某项操作。特定于该调用的信息将显示在右侧窗格的选项卡中。图 4 所示为一个简单约定及其实现的示例。

498)this.style.width=498;">
图3:使用WcfTestClient

您可以在方法选项卡的“Request”(请求)部分中输入作为操作参数的一个整数和一个字符串,如图 3 所示。单击“Invoke”(调用)按钮时,它将向服务调度调用,并在“Response”(响应)中显示返回值或输出参数。如果是单向操作,WcfTestClient 将在消息框中通知您已成功调度此操作。如果出现异常,WcfTestClient 将在消息框中显示异常信息,并让您发出其他调用。

Figure 4:示例服务

[ServiceContract]
interface IMyContract
{
[OperationContract]
string MyMethod(int someNumber,string someText);
}
class MyService : IMyContract
{
public string MyMethod(int someNumber,string someText)
{
return "Hello";
}
}

WcfTestClient 不使用测试服务来维护传输层会话(或任何其他会话)。所有调用均在新代理实例上进行。此外,所有调用均异步进行,这样 UI 能随时进行响应。但是,尽管是异步调用,WcfTestClient 只允许一次调度一个调用。

WcfTestClient 从代理文件(包括 .config 文件)以静默方式创建程序集,然后从临时位置加载该程序集。如果单击树中的“Config File”(配置文件)项,您可以获取该 .config 文件(添加服务引用时生成的同一 .config 文件),并可以将其显示其选项卡中。

与早期的 Visual Studio ASMX Web 服务测试页不同,通过 WcfTestClient 您可以借助枚举、类或结构(每个类或结构都是其他类或结构的组成部分)等复合参数、甚至是参数集合和参数数组来调用操作。只需展开“Request”(请求)部分中的项,从下拉列表中设置其值(例如枚举值),并进行调用即可。如果操作接受集合或数组,您还需要设置其长度。例如,图 5 显示了以下操作的结果请求和响应:

498)this.style.width=498;">
图5:指定数组长度和值

[OperationContract]
bool ProcessNumbers(int[] numbers]

类似地,“Response”(响应)窗格将包含所有返回的复合值或输出参数。这样我们发现了 WcfTestClient 的一个缺陷:为了指定要测试的不同服务,您必须先关闭,并在更改命令行参数后重新启动 WcfTestClient。如果能同时在 GUI 中提供服务地址,则可能会很好地缓解这个问题。

您可以直接将 WcfTestClient 集成到您的 Visual Studio 2008 解决方案中。首先,将类库项目添加到解决方案,并删除所有引用、文件夹和源文件(因为您不需要这些项)。然后,将 WcfTestClient.exe 设置为外部启动程序,并提供一个或多个测试服务的一个或多个元数据地址,例如 IIS 或 WAS 托管项目的 .svc 地址,或者就此而言,解决方案内部或外部主机项目的任何其他元数据地址。

请注意,如果计算机上只安装了 .NET Framework 3.0,则您不能在其上使用 WcfTestClient,因为 WcfTestClient 需要使用内部 .NET Framework 3.5 树网格控件(用于表示复合参数的控件)。

当然,您可以在一个步骤中结合 WcfTestClient 和 WcfSvcHost,这样即可自动托管服务库中的服务并对其进行测试:

WcfSvcHost.exe /service:MyService.dll    /config:App.config
/client:WcfTestClient.exe
/clientArgs:http://localhost:9000/

但是,可以使用 WcfSvcHost 指定元数据参数。默认情况下,WcfSvcHost 将向指定客户端应用程序传送它在服务 .config 文件中找到的元数据地址。仅当服务未提供自己的元数据或您希望测试客户端使用其他地址时,才应显式指定元数据地址。此外,如果服务 .config 文件包含给定服务的多个元数据终结点,则这些终结点将按以下优先顺序提供:HTTP、TCP、IPC 和 HTTP-GET。您可以在 Visual Studio 2008 中合并这些步骤以进行无缝托管和测试体验。将 WcfSvcHost.exe 指定为启动程序,并将 .config 文件和 WcfTestClient.exe 指定为客户端。

WCF 服务库

作为 Target Framework 的一项功能,Visual Studio 2008 提供了多个新的 WCF 项目模板。通过“New Project”(新建项目)对话框中的组合框,您可以指定 Target Framework 的版本(2.0、3.0 或 3.5),如图 6 所示。

498)this.style.width=498;">
图6:WCF 项目模板

如果选择 Framework 2.0,则没有任何新模板可用。Framework 3.0 中提供了一个称为 WCF 服务库的新项目模板。此项目类型只是 WcfSvcHost 和 WcfTestClient 的预构建用法,与我前面提到的技术(将两者合二为一)非常类似。请注意,使用 WCF 服务库模板,就不需要将 WcfSvcHost.exe 指定为启动程序或 .config 文件,因为项目文件包含适用于 WCF 服务库的新 ProjectTypeGuids 元素。

但是该模板有一个负面影响:停止调试程序不会终止测试客户端;时间久了,您的桌面会由于孤立客户端而变得杂乱。要解决此问题,只需还原到前面清晰介绍的手动步骤。

WCF 服务库还提供了一个用于服务约定、服务约定的实施及匹配的 .config 文件的简单模板。

利用聚合服务库,您可以通过 WCF 终结点实现 RSS 源,并且可以从返回源的简单服务约定、服务约定的实施及匹配的 .config 文件着手操作。您可以托管和显示自己的源,与托管和显示任何其他服务一样。聚合终结点使用新的 WebHttpBinding 绑定。此新绑定旨在用于接收 Web 请求,而不能用于常规服务调用。

使用“顺序工作流服务库”模板,您可以将终结点的约定操作作为工作流活动来实现,或者将工作流显示为服务。项目将包含一个用于实现简单约定和匹配的 .config 文件的顺序活动。尽管客户端也将与类似传统终结点的终结点进行交互,但其实现完全是工作流驱动的。

“状态机工作流服务库”模板使用状态机而不是顺序工作流来实施其操作(触发状态转换)。工作流项目模板使用 WcfSvcHost 和 WcfTestClient,如同普通 WCF 服务库那样。工作流模板还使用新的上下文绑定对工作流实例 ID 的传递进行管理,从而支持持续工作流。我将在以后的专栏中详细介绍这些绑定。

添加服务引用

Visual Studio 2005 extensions for .NET Framework 3.0 提供了一项用于向 WCF 服务添加引用的基本功能,而不具有 SvcUtil 的许多高级功能。Visual Studio 2008 具有一个新的服务引用对话框,如图 7 所示。

498)this.style.width=498;">
图7:“添加服务引用”对话框

您可以在任何项目中打开此新对话框,方法是:右键单击解决方案资源管理器中项目内的任意位置,然后从上下文菜单中选择“Add Service Reference”(添加服务引用)。请注意,必须将项目配置为针对 .NET Framework 3.0(及更高版本)才能启用此选项。

在“Add Service Reference”(添加服务引用)对话框中,首先需要指定服务元数据地址(不是服务 URL,如对话框中所述),然后单击“Go”(执行)查看可用服务终结点(不是服务,如标记所示)。必须指定命名空间(如 MyService)才能包含生成的代理,然后单击“OK”(确定)生成代理并更新 .config 文件。请注意,在多数情况下,Visual Studio 2008 不是足够的智能来推断最清楚的绑定值,因此,它将通过声明绑定的所有默认值来删除 .config 文件。Visual Studio 的未来版本中将会解决此问题。如果您关注对 .config 文件的维护,请先打开 .config 文件,再添加引用,然后执行一次撤消 (Ctrl+Z) 操作,最后手动将 .config 文件条目添加到客户端部分中。

只要 WCF 服务位于网站项目或某个新 WCF 服务库中,您就可以使用“Discover”(搜索)按钮在自己的解决方案中查找这些服务。如果位于网站项目中,Visual Studio 2008 将检索 IIS 中的元数据或启动基于文件系统的 ASP.NET 开发服务器。如果位于 WCF 服务库中,则 WCF 将自动启动其主机 (WcfSvcHost) 以便获取元数据。

可以通过“Advanced”(高级)按钮打开设置对话框,从而调整代理生成,如同使用 SvcUtil(参见图 8)一样。通过使用更直观的选项,您可以配置生成的代理和约定(公共或内部)的可见性;可以为数据类型生成适用于高级互操作方案的消息约定,在此方案中必须遵从现有消息格式(通常为自定义),并可以单击“Add Web Reference”(添加 Web 引用)按钮将引用转换为旧 ASMX Web 服务引用。

498)this.style.width=498;">
图8:服务引用高级选项

“Generate asynchronous operations”(生成异步操作)复选框可以为导入的约定中的每个操作添加一对匹配的 Begin<operation> 和 End<operation> 元素,这两个元素允许客户端在工作线程中异步发出调用,并且通过提供完成回调方法或阻止完成来允许客户端稍后与操作完成进行同步。例如,假设约定的定义如下:

[ServiceContract]
interface ICalculator
{
[OperationContract]
int Add(int number1,int number2);
}

则导入的约定将如图 9 所示。

Figure 9:导入的异步约定

[ServiceContract]
interface ICalculator
{
[OperationContract]
int Add(int number1,int number2);

[OperationContract(AsyncPattern = true)]
IAsyncResult BeginAdd(int number1,int number2,
AsyncCallback callback,object asyncState);
int EndAdd(IAsyncResult result);

//Rest of the methods
}


异步调用的匹配客户端代码将类似于如下所示:

CalculatorClient proxy = new CalculatorClient();
int sum;
AsyncCallback completion = (result)=>
{
sum = proxy.EndAdd(result);
Debug.Assert(sum == 5);
proxy.Close();
};
proxy.BeginAdd(2,3,completion,null);

虽然您可以按原样使用这些方法,但是提供给 Begin<operation> 的完成回调是从线程池中的线程调用的。如果使用回调访问某些与特定线程相关联的资源,则这样做会出现严重问题。Windows 窗体(或 WPF)应用程序即为典型示例,此应用程序异步调度较长的服务调用(避免阻止 UI),然后希望使用调用结果来更新 UI。不允许使用原始 Begin<operation> 进行更新,因为只允许 UI 线程更新 UI。为了更好地处理此情况,已用受保护的 InvokeAsync 方法扩展了 ClientBase<T> 基类,该方法可以获取客户端的同步上下文并将其用于调用完成回调,如图 10 所示。

Figure 10:ClientBase 中的异步回调管理

public abstract class ClientBase<T> : ...
{
protected delegate IAsyncResult BeginOperationDelegate(
object[] inValues,AsyncCallback asyncCallback,object state);

protected delegate object[] EndOperationDelegate(IAsyncResult result);

//Picks up sync context used for completion callback
protected void InvokeAsync(BeginOperationDelegate beginOpDelegate,
object[] inValues,
EndOperationDelegate endOpDelegate,
SendOrPostCallback opCompletedCallback,
object userState)
{}
//More members
}

ClientBase<T> 还提供了一个事件参数帮助器类和两个用于启动和结束异步调用的专用委托。生成的代理类由 ClientBase<T> 派生而来,此代理类将使用这些基本功能。代理将具有一个 <operation>Completed 公共事件(该事件使用特定于异步方法结果的强类型事件参数类)和两个 <operation>Async 方法(用于异步调度调用):

partial class AddCompletedEventArgs : AsyncCompletedEventArgs
{
public int Result
{get;}
}

class CalculatorClient : ClientBase<ICalculator>,ICalculator
{
public event EventHandler<AddCompletedEventArgs> AddCompleted;

public void AddAsync(int number1,int number2,object userState);
public void AddAsync(int number1,int number2);

//Rest of the proxy
}

客户端还可以为 <operation>Completed 事件订阅事件处理程序,以便在完成时调用此事件处理程序。使用 <operation>Async 与 Begin<operation> 的主要差别在于:<operation>Async 方法获取客户端的同步上下文,并在该同步上下文中激发 <operation>Completed 事件,如图 11 所示。

Figure 11:针对用户界面友好的异步调用的调用

partial class CalculatorForm : Form
{
CalculatorClient m_Proxy;

public MyClient()
{
InitializeComponent();

m_Proxy = new CalculatorClient();
m_Proxy.AddCompleted += OnAddCompleted;
}
void CallAsync(object sender,EventArgs args)
{
m_Proxy.AddAsync(2,3); //Sync context picked up here
}
//Called on the UI thread
void OnAddCompleted(object sender,AddCompletedEventArgs args)
{    
Text = "Sum = " + args.Result;
}
}

通过“Collection”(集合)类型组合框,您可以指定如何向客户端显示在服务元数据中找到的某些类型的集合和数组。例如,如果服务操作返回 IEnumerable<T>、IList<T> 或 ICollection<T> 集合中的某一个,则默认情况下代理会将其显示为数组。例如,以下服务端操作:

[OperationContract]
IEnumerable<int> GetNumbers();  

将在代理上表示为:

[OperationContract]
int[] GetNumbers();  

但是,您可以请求 Visual Studio 2008 使用其他集合(例如用于进行数据绑定的 BindingList、List<T>、Collection 和 LinkedList<T> 等)。如果可以进行转换,代理将使用请求的集合类型而不是数组,如下所示:

[OperationContract]
List<int> GetNumbers();  

字典也具有类似功能。通常情况下,如果服务操作返回可序列化的字典,如下所示:

[Serializable]
class MyDictionary<K,T> : IDictionary<K,T>
{...}

[OperationContract]
MyDictionary<int,string> GetDictionary();  


代理类随后会将该字典表示为 Dictionary<T,K>,这是以下 Dictionary 集合类型组合框的默认值:

[OperationContract]
Dictionary<int,string> GetDictionary();  

但是,您可以请求其他字典类型(如 SortedDictionary<T,K>、HashTable 或 ListDictionary),代理将改为使用这些字典(如果可能):

[OperationContract]
SortedDictionary<int,string> GetDictionary();

综上所述,新服务引用最重要的功能就是能够在程序集之间共享数据协定类型。使用 Visual Studio 2005,如果客户端将服务引用添加到两个支持相同数据协定的独立服务中,那么客户端将获得两个类型完全相同(表示相同数据协定)的不同服务引用。使用 Visual Studio 2008,默认情况下,如果客户端引用的任何程序集所具有的数据协定类型与引用服务的元数据中显示的数据协定类型相匹配,则 Visual Studio 2008 将不会再次导入该类型。有必要再次强调一下,现有数据协定引用必须位于另一个引用程序集中,而不是位于客户端项目本身中。Visual Studio 的未来版本中可能会解决此限制。目前,解决方法和最佳实践都很明显:将所有共享数据协定都构建到指定的类库中,并让所有客户端都引用该程序集。

通过服务引用的高级设置对话框,您可以配置数据协定共享。默认情况下“Reuse types in the referenced assemblies”(重用引用程序集中的类型)复选框处于选中状态,但您可以关闭此功能。不管复选框的名称是什么,此复选框都将只能共享数据协定而不能共享服务约定。使用复选框下面的单选按钮(参见图 8),还可以指示 Visual Studio 2008 在所有引用的程序集中重用数据协定,或者通过选中列表中的特定程序集来限制共享这些程序集。

添加引用后,项目将具有一个新文件夹 Service References,该文件夹中显示了每个引用的服务的服务引用项(参见图 12)。

498)this.style.width=498;">
图12:服务引用文件夹

随时可以右键单击引用并选择“Update Service Reference”(更新服务引用)来重新生成代理并更新客户端的 .config 文件。由于服务引用项中还包含一个记录了所使用的原始元数据地址的文件,因此可以执行此操作。

还可以选择“Configure Service Reference”(配置服务引用)打开一个对话框,该对话框与添加引用时使用的高级设置对话框类似。通过配置服务引用对话框,您可以更改服务元数据地址以及其他高级代理设置。

相关阅读

每日精选

点击查看更多

首页 手机 数码相机 笔记本 游戏 DIY硬件 硬件外设 办公中心 数字家电 平板电脑