探索 Windows Communication Foundation - 第二部分






4.69/5 (25投票s)
在本系列的第二篇文章《探索 Windows Communication Foundation》中,我们将探讨服务的自定义以及与故障和错误的处理。
引言
在本系列的第二篇文章《探索 Windows Communication Foundation》中,我们将探讨服务的自定义以及与故障和错误的处理。具体来说,我们关注以下方面:
- 在 IIS/ASP.NET 运行时中托管服务
- 自定义服务契约的详细信息,例如契约名称、所属的命名空间 URI 等
- 自定义操作契约的详细信息,例如契约名称、其参数、顺序等
在 IIS/ASP.NET 中托管 WCF 服务
要在 IIS/ASP.NET 中托管服务,我们需要执行以下操作:
- 如果尚未安装 IIS,请安装 IIS。
- 如果尚未注册 ASP.NET 2.0,请将其与 IIS 注册。
- 将 WCF 与 IIS 和 ASP.NET 2.0 注册。
如果您按此顺序安装了 IIS 和 .NET Framework 2.0 可再发行组件包,则无需注册 ASP.NET 运行时。您可以检查 ASP.NET 运行时是否已注册到某个网站,方法是检查该网站的属性,如下图所示:


如果 ASP.NET 运行时尚未与 IIS 注册,请在命令行中执行以下命令进行注册:
%windir%\Microsoft.Net\Framework\v2.0.50727\aspnet_regiis.exe -i
要将 WCF 与 IIS 和 ASP.NET 2.0 注册,请在命令行中执行以下命令:
ServiceModelReg.exe -i -x
应用程序 ServiceModelReg.exe
位于 "%windir%\Microsoft.Net\Framework\v3.0\Windows Communication Foundation"
文件夹中。-i
选项用于将 WCF 与 IIS 和 ASP.Net 运行时注册,-x
选项用于执行自定义操作脚本。注册的两个重要作用是:将 .svc
扩展名注册到 IIS,使其由 ASP.Net 2.0 运行时处理;以及将 .svc
扩展名注册到 ASP.Net 运行时,使其由其 IHttpHandler
处理。
下一步是创建一个 ASP.Net 网站。我们称之为 ShoppingCart
。在创建的网站中,添加一个新的 WCF 服务并命名为 ShoppingCartService.svc
。

如果您找不到添加新 WCF 服务的模板,请不要灰心。您可以简单地添加一个新的文本文件并将其命名为 ShoppingCartService.svc
。
如果 ShoppingCartService.svc
文件尚未打开,请打开它。将其修改为包含以下内容:
<%@ ServiceHost Debug="true" Service="ShoppingCartLibrary.ShoppingCartImpl" %>
在您的项目内创建 Bin
文件夹。添加我们在上一篇文章中创建的 ShoppingCartLibrary
程序集。如果您没有,可以从上一篇文章获取。对于所有使用 Visual Studio 的人来说,您可能希望删除自动为代码隐藏文件创建的 ShoppingCartService
类,该文件位于 App_Code
文件夹中。我们不需要它。
修改配置文件 web.config
以包含以下内容:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="HttpGetBehaviour">
<serviceMetadata httpGetEnabled="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service name="ShoppingCartLibrary.ShoppingCartImpl"
behaviorConfiguration="HttpGetBehaviour">
<endpoint binding="mexHttpBinding"
contract="ShoppingCartLibrary.IShoppingCartService"
address=""/>
</service>
</services>
</system.serviceModel>
</configuration>
太棒了!服务已部署。浏览到 http:///ShoppingCart/ShoppingCartService.svc
。如果您看到如下图所示的输出,则表示脚本映射未正确注册。

在这种情况下(或未编译的输出),请在命令行中执行以下命令:
ServiceModelReg.exe -s:W3SVC/1/ROOT/ShoppingCart
在浏览器中刷新页面。现在,您应该能够看到如下所示的输出:

更新客户端
下一步是更新客户端(代理类和配置)以连接到我们的新服务。
啊!我们不需要重新生成所有内容。打开客户端应用程序配置文件,并将端点地址(在 configuration/system.serviceModel/client/endpoint
部分,属性 address
)修改为 http:///ShoppingCart/ShoppingCartService.svc
。
客户端应给出与我们在上一篇文章中创建的自托管服务工作时获得的相同结果。
服务自定义
现在让我们看看将 WCF 契约名称与 .Net 名称(数据类型和/或参数)分离的重要方面。
通常,我们不希望依赖于我们在 .Net 中选择的字段/参数/数据类型名称以及我们向世界公开的名称。这不仅可能使应用程序面临风险,而且可能是强制性的。例如,如果我们希望有一个名为 in
或 for
的参数,我们就无法拥有它,因为这些是 C# 中的关键字(严格来说,我们可以在 C# 中拥有它,但 C++.Net 或 VB.Net 呢?)。因此,我们希望消除这种相互依赖性。
服务契约名称
我们首先关注的是分离服务名称、它所属的命名空间 URI 以及接口名称。
ServiceContractAttribute
有两个属性 - Name
和 Namespace
,可用于控制这一点。这两个属性的默认值是接口名称和 http://www.tempuri.org
。让我们将它们更改为 Shopping-Cart-Service
和 http://www.edujinionline.com/ns/wcf/tutorial/scart
。
将接口 IShoppingCartService
更新为如下所示(粗体代码是所做的更改):
using System;
using System.ServiceModel;
namespace ShoppingCartLibrary
{
[ServiceContract(Name="Shopping-Cart-Service",
Namespace="http://www.edujinionline.com/ns/wcf/tutorial/scart")]
public interface IShoppingCartService
{
[OperationContract]
bool CheckUserExists(string username);
[OperationContract]
DateTime? GetLastTransactionTime(string username);
}
}
启动我们在上一篇文章中创建的托管应用程序。浏览到 http://:8080/ShoppingCartService?wsdl
。您会注意到生成的 WSDL 明显较短,并引用了另一个 WSDL。浏览到 http://:8080/ShoppingCartService?wsdl=wsdl0
。您会注意到类型、消息和操作(在 portTypes 部分)的所有定义现在都属于我们指定的新命名空间 URI。
还请注意,端口类型的名称现在是 Shopping-Cart-Service
而不是 IShoppingCart
。请注意,portType 部分定义了服务契约(操作及相关消息)。绑定也现在来自 Shopping-Cart-Service
类型。
但是,如果我们想更改 WSDL 中服务部分的服务名称及其命名空间 URI,我们需要更改服务行为而不是契约详细信息。它们的默认值是实现类的名称和 http://tempuri.org。让我们将它们分别更新为 Shopping-Cart-Impl
和 http://www.edujinionline.com/ns/wcf/tutorial/scart/impl
。
我们将使用 System.ServiceModel.ServiceBehaviorAttribute
来指定服务的名称和命名空间 URI。
将 ShoppingCartImpl
类更新为如下所示(粗体代码是所做的更改):
using System;
using System.ServiceModel;
namespace ShoppingCartLibrary
{
[ServiceBehavior(Name="Shopping-Cart-Impl",
Namespace="http://www.edujinionline.com/ns/wcf/tutorials/scart/impl")]
public class ShoppingCartImpl : IShoppingCartService
{
//Other code not shown
}
}
操作名称
同样,要更改操作的值,可以使用 System.ServiceModel.OperationContractAttribute
的 Name
属性。让我们将操作的名称从它们的默认名称更新为 UserExists<code> 和 <code>LastTransactionTime
。将接口 IShoppingCartService
更新为如下所示:
using System;
using System.ServiceModel;
namespace ShoppingCartLibrary
{
[ServiceContract(Name="Shopping-Cart-Service",
Namespace="http://www.edujinionline.com/ns/wcf/tutorial/scart")]
public interface IShoppingCartService
{
[OperationContract(Name="UserExists")]
bool CheckUserExists(string username);
[OperationContract(Name="LastTransactionTime")]
DateTime? GetLastTransactionTime(string username);
}
}
输入和输出消息的操作 URI
启动托管应用程序并浏览到 http://:8080/ShoppingCartService?wsdl=wsdl0
。查看更新的操作名称。但是,输入和输出消息的 Action
看起来不太好。让我们将其更新为 http://www.myurl.com/scart/actions/UserExists
和 http://www.
myurl.com/scart
/actions/LastTransactionTime
(输入),以及 http://www.
myurl.com/scart
/actions/UserExistsOut
和 http://www.
myurl.com/scart
/actions/LastTransactionTimeOut
(输出)。
Action
和 ReplyAction
属性可用于自定义输入和输出操作的操作 URI。再次,将接口更改为如下所示(粗体代码是所做的更改):
using System;
using System.ServiceModel;
namespace ShoppingCartLibrary
{
[ServiceContract(Name="Shopping-Cart-Service",
Namespace="http://www.myurl.com/scart")]
public interface IShoppingCartService
{
[OperationContract(Name="UserExists",
Action="http://www.myurl.com/scart/actions/UserExists",
ReplyAction="http://www.myurl.com/scart/actions/UserExistsOut")]
bool CheckUserExists(string username);
[OperationContract(Name="LastTransactionTime",
Action="http://www.myurl.com/scart/actions/LastTranactionTime",
ReplyAction="www.myurl.com/scart/actions/LastTranactionTimeOut")]
DateTime? GetLastTransactionTime(string username);
}
}
同样,可以通过适当地设置 System.ServiceModel.OperationContractAttribute
的 Order
属性来定义操作在 WSDL 中出现的顺序。默认情况下,WCF 引擎会按照其从 IL(使用反射)检索的顺序显示操作。
参数名称
假设我们的购物车在与外界的接口方面使用电子邮件地址。在内部,我们希望将其称为用户名,就像我们一直以来那样。
System.ServiceModel.MessageParameterAttribute
可用于自定义输入参数以及响应中输出参数的名称。将代码更新为如下所示(粗体代码显示了所做的更改):
using System;
using System.ServiceModel;
namespace ShoppingCartLibrary
{
[ServiceContract(Name="Shopping-Cart-Service",
Namespace="http://www.myurl.com/scart")]
public interface IShoppingCartService
{
[OperationContract(Name="UserExists",
Action="http://www.myurl.com/scart/actions/UserExists",
ReplyAction="http://www.myurl.com/scart/actions/UserExistsOut")]
[return: MessageParameter(Name="ExistsInfo")]
bool CheckUserExists(
[MessageParameter(Name="email")] string username);
[OperationContract(Name="LastTransactionTime",
Action="http://www.myurl.com/scart/actions/LastTranactionTime",
ReplyAction="http://www.myurl.com/scart/actions/LastTranactionTimeOut")]
[return: MessageParameter(Name="TimeInfo")]
DateTime? GetLastTransactionTime(
[MessageParameter(Name="email")] string username);
}
}
有了所有这些更改,我们需要重新生成代理客户端(使用 svcutil
)。您会注意到这次生成的默认类文件是 Shopping-Cart-Service.cs
,这恰好是我们的服务名称!
现在,在继续任何事务之前,让我们启用跟踪以查看正在传输的消息。将主机应用程序的配置文件更新为包含以下条目:
<configuration>
<system.diagnostics>
<sources>
<source name="System.ServiceModel.MessageLogging"
switchValue="Verbose">
<listeners>
<add name="xml"
type="System.Diagnostics.XmlWriterTraceListener"
initializeData="..\..\..\wcf_scart.svclog" />
</listeners>
</source>
</sources>
<trace autoflush="true" />
</system.diagnostics>
<system.serviceModel>
<diagnostics>
<messageLogging logEntireMessage="true" maxMessagesToLog="300"
logMessagesAtServiceLevel="false"
logMalformedMessages="true"
logMessagesAtTransportLevel="true" />
</diagnostics>
< ... >
</system.ServiceModel>
</configuration>
将客户端应用程序更新为如下所示(粗体代码是所做的更新):
using System;
namespace ShoppingCartConsumer
{
public class MainClass
{
public static void Main(string[] args)
{
ShoppingCartServiceClient client = new ShoppingCartServiceClient();
string[] users = new string[] { "gvaish", "edujini", "other" };
foreach(string user in users)
{
Console.WriteLine("User: {0}", user);
Console.WriteLine("\tExists: {0}", client.UserExists(user));
DateTime? time = client.LastTransactionTime(user);
if(time.HasValue)
{
Console.WriteLine("\tLast Transaction: {0}", time.Value);
} else
{
Console.WriteLine("\tNever transacted");
}
}
Console.WriteLine();
}
}
}
请注意,代理客户端中不再存在 CheckUserExists
和/或 GetLastTransactionTime
方法。
启动 Service Trace Viewer
,即在安装 Windows SDK for Windows Vista and .Net Framework 3.0 Runtime Components
的 bin
文件夹中找到的应用程序 SvcTraceViewer.exe
。打开生成的 wcf_scart.svclog
文件。注意发送的请求和收到的响应中的重要更改。


摘要
我们已经了解了如何在 IIS/ASP.Net 中托管服务。我们还了解了如何分离 .Net 参数和操作参数名称。暂时就到这里。时间不早了,我将在下一篇文章中讨论 WCF 中的故障和错误处理。
联系方式
- 您可以直接在下方发表评论,我将回复。
- 您可以直接联系我:gaurav[dot]vaish[at]gmail。