理解 WCF 中的契约






4.86/5 (30投票s)
本文解释了 WCF 支持的契约类型
- 下载源类库 - 3.3 KB
- 下载源客户端应用程序 - 5.3 KB
- 下载源宿主应用程序 - 2.8 KB
- 下载可执行宿主应用程序 - 5.9 KB
- 下载可执行客户端应用程序 - 5.6 KB
- 下载源 Web 客户端 - 139.2 KB
介绍
WCF 中的合同提供互操作性,这是客户端进行通信所必需的。在本文中,我将解释合同的类型以及如何在实际应用中使用这些合同。在本文中,我尝试通过一个简单的例子来解释所有这些内容。在这个例子中,我以一个真实世界的长方体为例,它有 3 个属性(长、宽、高),我们的服务计算长方体的体积和总表面积。通过消息合同和数据合同两种方式进行演示。
背景
正是这些合同,客户端和服务才就通信时使用的操作类型和结构达成一致。这是客户端和服务之间的一项正式协议,用于定义描述服务功能、独立于平台的标准方式。WCF 定义了四种类型的合同:
- 服务合同
- 数据合同
- 消息合同
- 错误合同
服务合同
服务合同**描述了服务终结点上可用的操作**(方法),并向外部公开。服务合同描述了服务公开的、客户端可调用的操作(函数),除此之外,它还描述了:
- 操作、接口和服务方法与独立于平台的描述的映射关系
- 服务可以与其他方进行的消息交换模式,可能是一次性/请求-回复/双工。
要创建服务合同,您可以定义一个具有相关方法的接口,这些方法代表了一组服务操作,然后使用**ServiceContract**特性修饰该接口/类,以表明它是一个服务合同。接口中应包含在服务合同中的方法应使用**OperationContract**特性进行修饰。
[ServiceContract]
interface ICuboidService
{
[OperationContract]
CuboidDetail CalculateDetails2(CuboidInfo cInfo);
}
数据合同
一句话来说,数据合同**描述要交换的数据**。它是服务和客户端之间的正式协议,**包含有关他们将要交换的数据的信息**。需要在此指出的重要一点是,双方不必共享相同的数据类型即可通信,它们只需要共享相同的数据合同。数据合同定义了哪些参数和返回类型将被序列化/反序列化(**二进制<==>XML**)以便在一方与另一方之间传输。数据合同可以通过注释类、枚举甚至结构来定义,但不能注释接口。
要定义数据合同,只需使用**[DataContract]**特性修饰一个类或枚举,如下所示。
[DataContract]
public class CuboidInfo
{
}
消息合同
WCF 使用 SOAP 消息进行通信。大多数时候,开发人员更专注于开发**数据合同**、序列化数据等。有时开发人员还需要控制 SOAP 消息格式。在这种情况下,WCF 提供了消息合同来根据需要自定义消息。
**消息合同**用于控制消息正文的结构和序列化过程。它也用于在 SOAP 标头中发送/访问信息。默认情况下,WCF 会负责创建 SOAP 消息。
根据服务**数据合同**和**操作合同**。

因此,当您需要完全控制 SOAP 消息的结构以及序列化过程时,特别是当您的服务需要与不同类型的客户端互操作,或者服务需要为消息和消息部分提供额外的安全层时,消息就是一个数据包,WCF 使用这个数据包在源到目的地传输信息。这个消息包含一个**信封**、一个**标头**和一个**正文**。在处理消息合同时,有一些规则需要遵循。
- 当使用消息合同类型作为参数时,**操作中只能使用一个参数**。
- 服务操作**应返回 MessageContract 类型**,**或者**不应返回任何值。
- 操作将**仅接受和返回消息合同类型**。不允许使用其他数据类型。
数据合同与消息合同之间的选择。
**90%** 的时间,**数据合同就足够了**。只有当您需要非常紧密、非常具体地控制 SOAP 消息的布局时,才需要消息合同。**大多数时候,您不需要**。
消息合同允许您明确说明哪些元素(标量类型或复合类型,如**数据合同**)将包含在 SOAP 标头中,哪些将包含在 SOAP 正文中。
如果您有一个通信伙伴,您已经与其就非常具体的格式达成一致,并且您必须调整 SOAP 消息以精确匹配该给定布局,那么您可能需要它。这几乎是您**需要**并且**应该使用消息合同**的**唯一有效场景**。
然而,有时对 SOAP 消息结构的完全控制与对其内容的控制同样重要。当**互操作性很重要,或者需要特别控制消息或消息部分的安全性问题**时,尤其如此。在这些情况下,您可以创建一个*消息合同*,使您能够将参数或返回值类型用于*直接序列化为所需 SOAP 消息的类型*。
所以,长话短说:**始终**使用**数据合同**,几乎从不使用消息合同(**除非您确实需要**)。错误合同
错误合同**为服务中发生的错误向客户端提供了文档化的视图**。这有助于我们轻松识别发生了什么错误以及在哪里发生的。默认情况下,当我们在服务中抛出任何异常时,它不会到达客户端。客户端对服务器端发生的事情了解得越少,交互就越解耦。这种现象(不允许错误的实际原因到达客户端)被称为**错误掩蔽**。默认情况下,服务端的所有异常都会作为**FaultException**到达客户端,通过使所有服务异常都无法区分,WCF 将客户端与服务解耦。
虽然默认的**错误掩蔽策略**是**WCF 的最佳实践**,但有时**客户端需要以预先规定的有意义的方式响应这些异常**。WCF 提供了通过**SOAP Fault**合同处理错误消息并将其传达给客户端的选项。**SOAP** 错误基于行业标准,独立于任何特定于技术的异常,它是将特定于技术的异常映射到一些中性错误信息的一种方式。因此,当服务遇到意外错误时,服务可以抛出 **FaultException<T>** 类的实例,而不是抛出原始的 CLR 异常(特定于技术)。
使用代码
本文已分为 4 个模块:
- WCF 服务类库 (ContractLib.dll):实际的服务逻辑,它定义并实现服务,并公开一些函数供外部使用。
- 用于托管 WCF 服务类库的控制台宿主应用程序 (ContractLibHost.exe):托管 WCF 类库。
- 控制台客户端应用程序 (ContractClient.exe):将使用此服务的客户端应用程序。
- Web 客户端 (ContractWebClient):将使用此服务的网站。
第一个模块:WCF 服务类库 (ContractLib.dll)
要创建此项目,您可以简单地选择“**类库**”项目,从项目向导选项中选择。让我们称它为“ContractLib
”,它实现了实际的业务逻辑。项目已包含一个文件 Class1.cs,在编写任何服务库代码之前,让我们进行一些清理工作。
一点清理
- 从项目中删除文件 Class1.cs。
- 向项目中添加一个名为
ICuboidService
的新**接口**,将添加一个新文件 ICuboidService.cs 到项目中。 - 向项目中添加一个名为
CuboidService
的新**类**,它将实现ICuboidService
接口,将添加一个新文件CuboidService.
cs 到项目中。
定义服务接口 - 数据合同(s)
CuboidFaulltException 的定义
[DataContract]
public class CuboidFaultException
{
private string _reason;
private string _source;
private string _detail;
private string _helpLink;
[DataMember]
public string Reason
{
get { return _reason; }
set { _reason = value; }
}
[DataMember]
public string Source
{
get { return _source; }
set { _source = value; }
}
[DataMember]
public string Detail
{
get { return _detail; }
set { _detail = value; }
}
[DataMember]
public string HelpLink
{
get { return _helpLink; }
set { _helpLink = value; }
}
}
解释
这个**数据合同**定义了当服务抛出**CuboidFaultException**时要交换的数据。由于服务也实现了错误合同,因此这些信息将从服务发送到客户端。我定义了 4 个字段,用于将错误信息发送到客户端,您可以根据需要定义更多字段,以将其他上下文信息发送到客户端。原因 (Reason):异常的实际原因,为什么服务抛出了**CuboidFaultException**。
来源 (Source):抛出异常的来源信息,在此字段中,您可以发送函数名称、异常发生的行号,或您认为适合作为错误的来源的任何信息。
错误字段。
详细信息 (Detail):您可以在此处打包有关错误实际详细信息的信息。
帮助链接 (HelpLink):有关此类型错误的文档的网页链接,任何您认为有帮助的东西。
CuboidDimension 的定义
[DataContract]
public class CuboidDimension
{
[DataMember] public int Length;
[DataMember] public int Width;
[DataMember] public int Height;
}
解释
这个数据合同简单地定义了一个长方体的 3 个维度,即长、宽和高。
CuboidInfo 的定义
[DataContract]
public class CuboidInfo
{
[DataMember] public int ID;
[DataMember] public CuboidDimension Dimension = new CuboidDimension();
}
解释
这个数据合同定义了一个类,它包含两个字段:第一个字段是长方体的**ID**,第二个字段是前面定义的**CuboidDimension**类的对象,它包含长方体的尺寸(长、宽、高)。
CuboidDetail 的定义
[DataContract]
public class CuboidDetail
{
[DataMember] public int ID;
[DataMember] public double SurfaceArea;
[DataMember] public double Volume;
}
解释
这个数据合同简单地定义了一个类,它包含 3 个字段:第一个字段是长方体的**ID**,第二个字段是一个 double,它将保存长方体的**表面积**值,第三个字段是一个 double,它将包含长方体的**体积**。
定义服务接口 - 消息合同(s)
CuboidInfoRequest 的定义
[MessageContract]
public class CuboidInfoRequest
{
private int m_nID ;
private CuboidDimension m_Dims = new CuboidDimension();
[MessageHeader]
public int ID
{
get { return m_nID; }
set { m_nID = value; }
}
[MessageBodyMember]
public CuboidDimension Dimension
{
get { return m_Dims; }
set { m_Dims = value; }
}
}
解释
这个消息合同简单地定义了一个类,它包含两个字段:第一个字段是长方体的 ID,已被定义为消息标头。消息正文中包含一个成员 **CuboidDimension**,它是长方体的详细信息(尺寸)。定义服务接口 - 消息合同(s)
CuboidInfoResponse 的定义
[MessageContract]
public class CuboidDetailResponse
{
private int m_nID;
private double m_dblArea;
private double m_dblVolume;
[MessageHeader]
public int ID
{
get { return m_nID; }
set { m_nID = value; }
}
[MessageBodyMember]
public double SurfaceArea
{
get { return m_dblArea ; }
set { m_dblArea = value ; }
}
[MessageBodyMember]
public double Volume
{
get { return m_dblVolume; }
set { m_dblVolume = value; }
}
}
解释
这个消息合同定义了一个类,它包含 3 个字段:长方体的**ID**已被定义为**消息标头**。**消息正文**包含两个成员,即长方体的**体积**和**表面积**。
定义服务接口 – 服务合同、操作合同、错误合同
定义服务合同 ICuboidService
[ServiceContract]
interface ICuboidService
{
[OperationContract]
[FaultContract(typeof(CuboidFaultException))]
CuboidDetailResponse CalculateDetails1(CuboidInfoRequest cInfo);
[OperationContract]
[FaultContract(typeof(CuboidFaultException))]
CuboidDetail CalculateDetails2(CuboidInfo cInfo);
[OperationContract]
[FaultContract(typeof(CuboidFaultException))]
CuboidDetail CalculateDetails3(int nID, CuboidDimension cInfo);
}
解释
服务定义了 3 个方法作为服务的公开操作。
第一个方法 **CalculateDetails1** 接受 **CuboidInfoRequest** 消息合同类型作为输入参数,并返回 **CuboidDetailResponse** 消息合同类型。
遵守规则。
- 当使用消息合同类型作为参数时,**操作中只能使用一个参数**。
- 服务操作**应返回 MessageContract 类型**,**或者**不应返回任何值。
- 操作将**仅接受和返回消息合同类型**。不允许使用其他数据类型。
实现了一个**错误合同**,它在一切正常时抛出 **CuboidFaultException**。
第二个方法 **CalculateDetails2** 接受 **CuboidInfo** 数据合同类型作为输入参数,并返回 **CuboidDetail** 数据合同类型。尽管方法只接受一个参数。
实现了一个**错误合同**,它在一切正常时抛出 **CuboidFaultException**。与消息合同不同,使用数据合同不强制执行任何规则。
第三个方法 **CalculateDetails3** 接受 2 个参数,第一个参数是一个 int,第二个参数是 **CuboidDimension** 数据合同类型作为输入参数,并返回 **CuboidDetail** 数据合同类型。实现了一个**错误合同**,它在一切正常时抛出 **CuboidFaultException**。使用数据合同不强制执行任何规则,与消息合同不同。
使用数据合同不强制执行任何规则,与消息合同不同。
实现服务接口
实现方法 CalculateDetails1
public CuboidDetailResponse CalculateDetails1(CuboidInfoRequest cInfo)
{
if ((cInfo.Dimension.ID <=0) || (cInfo.Dimension.Length <= 0) || (cInfo.Dimension.Width <= 0) || (cInfo.Dimension.Height <= 0))
{
CuboidFaultException faultEx = new CuboidFaultException ();
faultEx.Reason = "Any dimension of Cuboid (length/width/height) can not be zero or negative value";
faultEx.Source = "CalculateDetails1()";
faultEx.HelpLink = "http://praveenkatiyar.wordpress.com";
StringBuilder sbDetail = new StringBuilder ("");
if (cInfo.Dimension.ID <= 0) sbDetail.Append ( "[ID is <= 0] ");
if (cInfo.Dimension.Length <= 0) sbDetail.Append ( "[Length is <= 0] ");
if (cInfo.Dimension.Width <= 0) sbDetail.Append ( "[Width is <= 0] ");
if (cInfo.Dimension.Height <= 0) sbDetail.Append ( "[Height is <= 0]") ;
faultEx.Detail = sbDetail.ToString();
throw new FaultException<CuboidFaultException>(faultEx);
}
CuboidDetailResponse dInfo = new CuboidDetailResponse();
dInfo.ID = cInfo.ID ;
dInfo.Volume = (cInfo.Dimension.Length * cInfo.Dimension.Width * cInfo.Dimension.Height);
dInfo.SurfaceArea = 2 * ( (cInfo.Dimension.Length * cInfo.Dimension.Width) +
(cInfo.Dimension.Width * cInfo.Dimension.Height) +
(cInfo.Dimension.Length * cInfo.Dimension.Height ));
return dInfo;
}
解释
操作接受一个 MessageContrat(**CuboidInfoRequest**)类型参数。操作检查长方体的 ID 和属性(长、宽、高)是否不为零或负数。如果是,则抛出一个**CuboidFaultException**类型的**FaultException**,并为**CuboidFaultException**的字段赋值。如果一切正常,则计算所需值,创建一个新的**MessageContract**对象(**CuboidDetailResponse**)并为其字段赋值并返回该对象。
实现方法 CalculateDetails2
public CuboidDetail CalculateDetails2(CuboidInfo cInfo)
{
if ((cInfo.Dimension.ID <=0) || (cInfo.Dimension.Length <= 0) || (cInfo.Dimension.Width <= 0) || (cInfo.Dimension.Height <= 0))
{
CuboidFaultException faultEx = new CuboidFaultException ();
faultEx.Reason = "Any dimension of Cuboid (length/width/height) can not be zero or negative value";
faultEx.Source = "CalculateDetails2()";
faultEx.HelpLink = "http://praveenkatiyar.wordpress.com";
StringBuilder sbDetail = new StringBuilder ("");
if (cInfo.Dimension.ID <= 0) sbDetail.Append ( "[ID is <= 0] ");
if (cInfo.Dimension.Length <= 0) sbDetail.Append ( "[Length is <= 0] ");
if (cInfo.Dimension.Width <= 0) sbDetail.Append ( "[Width is <= 0] ");
if (cInfo.Dimension.Height <= 0) sbDetail.Append ( "[Height is <= 0]") ;
faultEx.Detail = sbDetail.ToString();
throw new FaultException<CuboidFaultException>(faultEx);
}
CuboidDetail dInfo = new CuboidDetail();
dInfo.ID = cInfo.ID;
dInfo.Volume = (cInfo.Dimension.Length * cInfo.Dimension.Width * cInfo.Dimension.Height);
dInfo.SurfaceArea = 2 * ((cInfo.Dimension.Length * cInfo.Dimension.Width) +
(cInfo.Dimension.Width * cInfo.Dimension.Height) +
(cInfo.Dimension.Length * cInfo.Dimension.Height));
return dInfo;
}
解释
操作接受一个 DataContrat(**CuboidInfo**)类型参数。方法检查长方体的 ID 和属性(长、宽、高)是否不为零或负数。如果是,则简单地创建并抛出一个**CuboidFaultException**类型的**FaultException**,并为**CuboidFaultException**的字段赋值。如果一切正常,则计算所需值,创建一个新的**DataContract**对象(**CuboidDetail**)并为其字段赋值并返回该对象。
实现方法 CalculateDetails3
public CuboidDetail CalculateDetails3(int nID, CuboidDimension cDims)
{
if ((nID <=0) || (cDims.Length <= 0) || (cDims.Width <= 0) || (cDims.Height <= 0))
{
CuboidFaultException faultEx = new CuboidFaultException();
faultEx.Reason = "Any dimension of Cuboid (length/width/height) can not be zero or negative value";
faultEx.Source = "CalculateDetails3()";
faultEx.HelpLink = "http://praveenkatiyar.wordpress.com";
StringBuilder sbDetail = new StringBuilder("");
if (nID <= 0) sbDetail.Append ( "[ID is <= 0] ");
if (cDims.Length <= 0) sbDetail.Append("[Length is <= 0] ");
if (cDims.Width <= 0) sbDetail.Append("[Width is <= 0] ");
if (cDims.Height <= 0) sbDetail.Append("[Height is <= 0]");
faultEx.Detail = sbDetail.ToString();
throw new FaultException<CuboidFaultException>(faultEx);
}
CuboidDetail dInfo = new CuboidDetail();
dInfo.ID = nID;
dInfo.Volume = (cDims.Length * cDims.Width * cDims.Height);
dInfo.SurfaceArea = 2 * ((cDims.Length * cDims.Width) + (cDims.Width * cDims.Height) + (cDims.Length * cDims.Height));
return dInfo;
}
解释
操作接受两个参数:
- 第一个参数是长方体的(整数)ID。
- 第二个参数是 DataContract **CuboidDimension** 的对象,这表明在使用 DataContract 时,可以传递多个参数。
操作检查长方体的 ID 和属性(长、宽、高)是否不为零或负数。如果是,则简单地创建并抛出一个**CuboidFaultException**类型的**FaultException**,并为**CuboidFaultException**的字段赋值。如果一切正常,则计算所需值,创建一个新的**DataContract**对象(**CuboidDetail**)并为其字段赋值并返回该对象。
**构建项目**(类库),**第一模块完成。 **
第二模块:宿主应用程序 (ContractLibHost.exe)
我在本项目中使用了自宿主。要创建此项目,您可以简单地选择“**控制台应用程序**”项目,从项目向导选项中选择。让我们称它为“ContractLibHost
”,此应用程序将自宿主服务。在编写任何宿主应用程序代码之前,让我们进行一些清理工作。
添加应用程序配置
- 向项目中添加一个**应用程序配置文件 (App.config)**。
定义配置
在编写任何宿主代码之前,让我们先定义配置。
假设
宿主应用程序将为服务公开以下终结点:
- CuboidService 将在**9011 端口**上公开**HTTP 终结点**。
- 对应的**mex** 终结点** (IMetadatExchange)** 用于 HTTP 终结点。
为**CuboidService**定义配置
定义终结点
<service name="ContractLib.CuboidService" behaviorConfiguration="CuboidServiceBehavior">
<host>
<baseAddresses>
<add baseAddress="https://:9011/CuboidService"/>
</baseAddresses>
</host>
<endpoint address="https://:9011/CuboidService" binding="wsHttpBinding" contract="ContractLib.ICuboidService"/>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
</service>
解释
服务定义了一个具有**wsHttpBinding**的**终结点**,在 9011 端口上,另一个终结点定义为**mex**,用于发布元数据,**IMetadataExchange**是**mex**终结点的标准合同。
定义行为
<behaviors>
<serviceBehaviors>
<!– CalcService Behavior –>
<behavior name="CuboidServiceBehavior">
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="true "/>
</behavior>
</serviceBehaviors>
</behaviors>
解释
服务行为定义了两个属性:
<serviceMetadata httpGetEnabled="true" />
获取或设置一个值,该值指示是否通过 HTTP/GET 请求发布服务元数据,true 表示是,元数据可用于通过 HTTP/GET 请求检索。
<serviceDebug includeExceptionDetailInFaults="true "/>
将 IncludeExceptionDetailsInFaults
设置为 true
**以使客户端能够获取有关内部服务方法异常的信息;仅建议将其作为临时调试服务应用程序的方式。在生产服务器上,此属性**必须设置为 false**。
托管服务
由于已定义了服务配置,在编写任何代码之前,
- 将 **System.ServiceModel** 的引用添加到项目中。
try
{
ServiceHost host = new ServiceHost(typeof(ContractLib.CuboidService));
host.Open();
Console.WriteLine("Service is Hosted as https://:9011/CuboidService");
Console.WriteLine("\nPress key to stop the service.");
Console.ReadKey();
host.Close();
}
catch (Exception eX)
{
Console.WriteLine("There was en error while Hosting Service [" + eX.Message + "]");
Console.WriteLine("\nPress key to close.");
Console.ReadKey();
}
解释
ServiceHost 类提供服务的宿主。
构建并执行宿主。
以管理员模式打开命令提示符(**以管理员身份运行**)并执行宿主。输出如下。
创建客户端 (ContractClient.exe)
创建基本项目
使用基于控制台的应用程序。命名为 ContractClient。
生成代理
当宿主应用程序(ContractLibHost.exe) 正在运行时,打开另一个命令提示符,
- 切换到创建客户端项目的文件夹,假设(E:\ContractClient)
- 发出以下命令来生成服务代理。
SvcUtil https://:9011/CuboidService /out:CuboidServiceProxy.cs /config:App.config <return>
此命令将生成两个文件。
- CuboidServiceProxy.cs
- App.Config
将这两个文件添加到客户端项目中。
在编写任何代码之前,让我们检查 app.config 文件。
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.serviceModel>
<bindings>
. . . .
</bindings>
<client>
. . . .
</client>
</system.serviceModel>
</configuration>
绑定部分包含标准绑定和自定义绑定的集合。每个条目都是一个绑定元素,可以通过其唯一名称来标识。
<bindings>
<wsHttpBinding>
<binding name="WSHttpBinding_ICuboidService" />
</wsHttpBinding>
</bindings>
客户端部分包含客户端用于连接到服务的终结点列表。
<client>
<endpoint address="https://:9011/CuboidService" binding="wsHttpBinding" bindingConfiguration="WSHttpBinding_ICuboidService" contract="CuboidServiceReference.ICuboidService" name="WSHttpBinding_ICuboidService">
<identity>
<userPrincipalName value="PRAVEEN-WIN7\BriskTech" />
</identity>
</endpoint>
</client>
注意:
<userPrincipalName value="PRAVEEN-WIN7\BriskTech" />
在您的机器上会有所不同。
使用代理调用服务
声明一些临时变量来读取输入。
int nID = 0;
int nLength = 0;
int nWidth = 0;
int nHeight = 0;
读取长方体的 ID、长度、宽度和高度。
Console.WriteLine();
Console.Write("Enter ID : ");
string strID = Console.ReadLine();
bool bID = int.TryParse(strID, out nID);
Console.WriteLine();
Console.Write("Enter Length : ");
string strLength = Console.ReadLine();
bool bLength = int.TryParse(strLength, out nLength);
Console.WriteLine();
Console.Write("Enter Width : ");
string strWidth = Console.ReadLine();
bool bWidth = int.TryParse(strWidth, out nWidth);
Console.WriteLine();
Console.Write("Enter Height : ");
string strHeight = Console.ReadLine();
bool bHeight = int.TryParse(strHeight, out nHeight);
确保所有输入均有效,否则退出。
bool bValid = ((bID) && (bWidth) && (bLength) && (bHeight));
if (bValid == false )
{
if (bID == false) Console.WriteLine("ID should be a positive numeric value");
if (bLength == false) Console.WriteLine("Length should be a positive numeric value");
if (bWidth == false) Console.WriteLine("Width should be a positive numeric value");
if (bHeight == false) Console.WriteLine("Height should be a positive numeric value");
return ;
}
创建服务代理对象,如果失败则退出。
CuboidServiceClient objClient = null ;
try
{
objClient = new CuboidServiceClient ();
}
catch (Exception eX)
{
objClient = null;
Console.WriteLine("\nFailed while creating Service proxy [" + eX.Message + "]" );
return;
}
调用方法 1,使用消息合同
将所有内容包含在 try 块中。
try {
创建对象来保存请求参数,**CuboidInfoRequest** 定义为消息合同。
CuboidInfoRequest msgRequest = new CuboidInfoRequest();
msgRequest. Dimension = new CuboidDimension ();
将输入值分配给请求对象。msgRequest.ID = nID;
msgRequest.Dimension.Length = nLength;
msgRequest.Dimension.Width = nWidth;
msgRequest.Dimension.Height = nHeight;
显示打包在消息请求中的输入值。Console.WriteLine("\nCalling Method : CalculateDetails1");
Console.WriteLine("Message Request");
Console.WriteLine("Cuboid ID ............. : {0:D2}", msgRequest.ID);
Console.WriteLine("Cuboid Length ......... : {0:F2}", msgRequest.Dimension.Length);
Console.WriteLine("Cuboid Width .......... : {0:F2}", msgRequest.Dimension.Width);
Console.WriteLine("Cuboid Height ......... : {0:F2}", msgRequest.Dimension.Height);
创建一个对象来保存消息响应。**CuboidDetailResponse** 已定义为**消息合同**。CuboidDetailResponse msgResponse = new CuboidDetailResponse (); msgResponse.SurfaceArea = objClient.CalculateDetails1(ref msgRequest.ID, msgRequest.Dimension, out msgResponse.Volume);显示第一个服务方法的输出。
Console.WriteLine("\nMessage Response"); Console.WriteLine("Total Surface Area .. : {0:F2}", msgResponse.SurfaceArea); Console.WriteLine("Total Volume ........ : {0:F2}", msgResponse.Volume);
如果服务端出现问题,或者您将 0 或 -1 作为任何维度传递,则显示服务发送的信息,请记住服务已实现**错误合同**。
catch (FaultException<CuboidFaultException> eX) { Console.WriteLine("\nThere was an error while fetching details . . ."); Console.WriteLine("Reason .............. : " + eX.Detail.Reason); Console.WriteLine("Source .............. : " + eX.Detail.Source); Console.WriteLine("Details ............. : " + eX.Detail.Detail); Console.WriteLine("Help Link ........... : " + eX.Detail.HelpLink); }
调用方法 2,使用数据合同
将所有内容包含在 try 块中。
try {
创建对象来保存输入值,**CuboidInfo** 定义为数据合同。
CuboidInfo dataRequest = new CuboidInfo();
dataRequest.Dimension = new CuboidDimension ();
将输入值分配给输入数据对象。dataRequest.ID = nID;
dataRequest.Dimension.Length = nLength;
dataRequest.Dimension.Width = nWidth;
dataRequest.Dimension.Height = nHeight;
显示打包在数据合同对象中的输入值。Console.WriteLine("\nCalling Method : CalculateDetails2");
Console.WriteLine("Input Data ");
Console.WriteLine("Cuboid ID ............. : {0:D2}", dataRequest.ID);
Console.WriteLine("Cuboid Length ......... : {0:F2}", dataRequest.Dimension.Length);
Console.WriteLine("Cuboid Width .......... : {0:F2}", dataRequest.Dimension.Width);
Console.WriteLine("Cuboid Height ......... : {0:F2}", dataRequest.Dimension.Height);
创建一个对象来保存方法 **CalculateDetails2** 返回的数据合同对象,**CuboidDetail** 已定义为**数据合同**。CuboidDetail dataResponse = new CuboidDetail(); dataResponse = objClient.CalculateDetails2(dataRequest);
显示第二个服务方法的输出。
Console.WriteLine("\nData Returned"); Console.WriteLine("Total Surface Area .. : {0:F2}", dataResponse.SurfaceArea); Console.WriteLine("Total Volume ........ : {0:F2}", dataResponse.Volume);
如果服务端出现问题,或者您将 0 或 -1 作为任何维度传递,则显示服务发送的错误信息,请记住服务已实现错误合同。
catch (FaultException<CuboidFaultException> eX) { Console.WriteLine("\nThere was an error while fetching details . . ."); Console.WriteLine("Reason .............. : " + eX.Detail.Reason); Console.WriteLine("Source .............. : " + eX.Detail.Source); Console.WriteLine("Details ............. : " + eX.Detail.Detail); Console.WriteLine("Help Link ........... : " + eX.Detail.HelpLink); }
调用方法 3,使用数据合同
将所有内容包含在 try 块中。
try {
创建对象来保存输入值,**CuboidInfo** 定义为数据合同。
CuboidDimension dimsCubiod = new CuboidDimension();
将输入值分配给输入数据对象。dimsCubiod.Length = nLength;
dimsCubiod.Width = nWidth;
dimsCubiod.Height = nHeight;
显示打包在数据合同对象中的输入值。Console.WriteLine("\nCalling Method : CalculateDetails3");
Console.WriteLine("Input Data ");
Console.WriteLine("Cuboid ID ............. : {0:D2}", nID);
Console.WriteLine("Cuboid Length ......... : {0:F2}", dimsCubiod.Length);
Console.WriteLine("Cuboid Width .......... : {0:F2}", dimsCubiod.Width);
Console.WriteLine("Cuboid Height ......... : {0:F2}", dimsCubiod.Height);
创建一个对象来保存方法 **CalculateDetails3** 返回的数据合同对象,**CuboidDetail** 已定义为**数据合同**。CuboidDetail dataResponse = new CuboidDetail(); dataResponse = objClient.CalculateDetails3(nID, dimsCubiod);
显示第二个服务方法的输出。
Console.WriteLine("\nData Returned"); Console.WriteLine("Total Surface Area .. : {0:F2}", dataResponse.SurfaceArea); Console.WriteLine("Total Volume ........ : {0:F2}", dataResponse.Volume);
如果服务端出现问题,或者您将 0 或负值作为任何维度传递,则显示服务发送的错误信息,请记住服务已实现错误合同。
catch (FaultException<CuboidFaultException> eX) { Console.WriteLine("\nThere was an error while fetching details . . ."); Console.WriteLine("Reason .............. : " + eX.Detail.Reason); Console.WriteLine("Source .............. : " + eX.Detail.Source); Console.WriteLine("Details ............. : " + eX.Detail.Detail); Console.WriteLine("Help Link ........... : " + eX.Detail.HelpLink); }
构建并执行客户端,不同输入的输出如下。
输出
当您提供所有有效输入时的输出。
其中一个输入无效时的输出。
输入两个无效。

创建 Web 客户端
创建基本项目
创建一个网站。
生成代理
当宿主应用程序运行时,右键单击客户端应用程序项目,右键单击网站。
添加服务引用
在地址栏中键入**CuboidService**的**mex**终结点地址,如下所示。
设计 Web 客户端的 UI
向页面添加一些控件,如下所示。
控件列表及其要修改的属性如下。
ID | 类型 | 文本 |
lblID | 标签 | ID |
txtID | 文本框 | |
lblLength | Label | 长度 |
txtLength | 文本框 | |
lblWidth | Label | 宽度 |
txtWidth | 文本框 | |
lblHeight | Label | 高度 |
txtHeight | 文本框 | |
btnCalc1 | Button | CalculateDetails1 |
btnCalc2 | Button | CalculateDetails2 |
btnCalc3 | Button | CalculateDetails3 |
lblMethod | Label | |
lblArea | Label | |
lblVolume | Label | |
lblStatus | Label |
编写 Page_Load 事件
添加完所有控件后,让我们编写 Web 页面的 Page_Load 处理程序。当您想初始化控件并赋予一些初始值时,它很有用。双击页面,IDE 将为 Page_Load 事件添加一个处理程序并打开它,代码如下。
protected void Page_Load(object sender, EventArgs e)
{
if (!(Page.IsPostBack))
{
txtID.Text = "1";
txtLength.Text = "10";
txtWidth.Text = "20";
txtHeight.Text = "30";
Session["ID"] = "1";
Session["Length"] = "10";
Session["Width"] = "20";
Session["Height"] = "30";
}
}
解释
在 Page_Load 时,如果页面是第一次加载(不是因为回发),我们用一些初始值初始化控件,并将相同的值存储到会话中,以便在回发期间可以从会话中存储的值初始化控件。
编写辅助函数以简化生活
在我们编写按钮单击事件的代码之前,让我们编写 2 个辅助函数。**第一个函数**是 **ParseAndValidate** 函数,如下所示。
protected bool ParseAndValidateInputs(ref int nID, ref int nLength, ref int nWidth, ref int nHeight)
{
bool bID = int.TryParse(txtID.Text, out nID);
bool bLength = int.TryParse(txtLength.Text, out nLength);
bool bWidth = int.TryParse(txtWidth.Text, out nWidth);
bool bHeight = int.TryParse(txtHeight.Text, out nHeight);
bool bValid = ((bID) && (bWidth) && (bLength) && (bHeight));
if (bValid == false)
{
StringBuilder sbTmp = new StringBuilder();
sbTmp.Append("<Font Color=#ff0000>");
if (bID) sbTmp.Append("<p>ID should be a positive numeric value");
if (bLength) sbTmp.Append("<p>Length should be a positive numeric value");
if (bWidth) sbTmp.Append("<p>Width should be a positive numeric value");
if (bHeight) sbTmp.Append("<p>Height should be a positive numeric value");
sbTmp.Append("</Font>");
lblStatus.Text = sbTmp.ToString();
return false ;
}
return true ;
}
解释
验证页面控件中输入的值,使用标准的库函数 **TryParse** 从字符串解析数值。值存储在通过函数传递的 ref 变量中。如果输入不是数字,函数返回 false,并将原因显示在分配给状态的标签(**lblStatus**)上。
**第二个函数**创建服务代理对象,并返回新创建的对象,如下所示。
protected CuboidServiceClient GetServiceClientObject()
{
CuboidServiceClient objClient = null;
try
{
objClient = new CuboidServiceClient();
}
catch (Exception eX)
{
objClient = null;
lblStatus.Text = "<Font color=#ff0000>Failed while creating Service proxy ..... </font>";
}
return objClient ;
}
解释
简单地创建一个服务代理对象,并返回新创建的对象。如果创建失败,则将错误原因显示在分配给状态显示的标签(**lblStatus**)上,并**返回 null**。
调用方法 CalculateDetails1
当 Button btnCalc1 被单击时,将调用此方法。这是其处理程序。在此事件中,我们只需解析通过 Web 页面控件输入的输入值,然后调用方法。以下是事件处理程序的分解。
protected void btnCalc1_Click(object sender, EventArgs e)
{
声明变量来保存不同的值,并解析页面控件中的输入(ID、长度、宽度和高度)。如果无效(输入了非数字值,允许输入 0 和 -1),则返回。
int nID=0, nLength=0, nWidth=0, nHeight=0;
if (ParseAndValidateInputs ( ref nID, ref nLength, ref nWidth, ref nHeight ) == false) return;
您获得了允许的值,因此将其存储到会话中,这在回发时很有用,因为您可以在回发期间用会话中存储的值初始化控件。
Session["ID"] = nID.ToString () ;
Session["Length"] = nLength .ToString ();
Session["Width"] = nWidth .ToString ();
Session["Height"] = nHeight.ToString () ;
创建服务代理对象,如果对象创建失败,则返回;
CuboidServiceClient objClient = GetServiceClientObject(); if (objClient == null) return;
一旦收集了输入,并且代理对象已准备好,就可以调用服务了。将所有内容包含在 try 块中,用于错误捕获,以及捕获服务抛出的**FaultException**。
// Call Method 1 try {
创建 MessageRequest 对象,并将输入值分配给请求。
cuboidInfoRequest msgRequest = new CuboidInfoRequest (); msgRequest.Dimension = new CuboidDimension(); msgRequest.ID = nID ; msgRequest.Dimension.Length = nLength ; msgRequest.Dimension.Width = nWidth ; msgRequest.Dimension.Height = nHeight ;
为 Message Response 对象分配空间。调用服务函数并将响应保存。
CuboidDetailResponse msgResponse = new CuboidDetailResponse (); msgResponse.SurfaceArea = objClient.CalculateDetails1 ( ref msgRequest.ID, msgRequest.Dimension, out msgResponse.Volume );
将响应输出显示到指定的控件。
lblMethod.Text = "<Font color=#0000ff>Method Called ..... : CalculateDetails1 </font>"; lblStatus.Text = "<font Color=#008000>Status : Success </font>"; lblArea.Text = "<Font color=#000080>Total Surface Area .. : " + msgResponse.SurfaceArea.ToString ("F2") + "</font>" ; lblVolume.Text = "<Font color=#000080>Total Volume .. : " + msgResponse.Volume.ToString ("F2") + "</font>" ;
关闭 try 块。
}
查看是否有任何错误(**CuboidFaultException**)来自服务。
catch (FaultException<CuboidFaultException> eX) { lblMethod.Text = "<Font color=#ff0000>Method Called ..... : CalculateDetails1 </font>"; StringBuilder sbErr = new StringBuilder ("<font Color=#ff0000>Status : Error "); sbErr .Append ( "<br>Reason : " ); sbErr.Append ( eX.Detail.Reason ); sbErr .Append ( "<br>Source : " ); sbErr.Append ( eX.Detail.Source ); sbErr .Append ( "<br>Details : " ); sbErr.Append ( eX.Detail.Detail ); sbErr .Append ( "<br>HelpLink : " ); sbErr.Append ( eX.Detail.HelpLink ); sbErr .Append ( "</Font>"); lblStatus.Text = sbErr.ToString(); ; lblArea.Text = "" ; lblVolume.Text = ""; }
解释
如果您收到了**CuboidFaultException**,它包含了客户端可能感兴趣的所有信息。这里您只需**在指定的标签上显示该信息**,该标签用于**状态**。
调用方法 CalculateDetails2
当 button **btnCalc2** 被单击时,将调用此方法。这是其处理程序。在此事件中,我们只需解析通过 Web 页面控件输入的输入值,然后调用方法。以下是事件处理程序的分解。
protected void btnCalc2_Click(object sender, EventArgs e)
{
声明变量来保存不同的值,并解析页面控件中的输入(ID、长度、宽度和高度)。如果无效(输入了非数字值),则返回,允许输入 0 和 -1。int nID=0, nLength=0, nWidth=0, nHeight=0;
if (ParseAndValidateInputs ( ref nID, ref nLength, ref nWidth, ref nHeight ) == false) return;
您获得了允许的值,因此将其存储到会话中,这在回发时很有用,因为您可以在回发时用会话中存储的值初始化控件。Session["ID"] = nID.ToString () ;
Session["Length"] = nLength .ToString ();
Session["Width"] = nWidth .ToString ();
Session["Height"] = nHeight.ToString () ;
创建服务代理对象,如果对象创建失败,则返回;CuboidServiceClient objClient = GetServiceClientObject();
if (objClient == null) return;
一旦收集了输入,并且代理对象已准备好,就可以调用服务了。将所有内容包含在 try 块中,用于错误捕获,以及捕获服务抛出的**FaultException**。try
{
将所有内容包含在 try 块中。
CuboidInfo dataRequest = new CuboidInfo();
dataRequest.Dimension = new CuboidDimension();
dataRequest.ID = nID;
dataRequest.Dimension.Length = nLength;
dataRequest.Dimension.Width = nWidth;
dataRequest.Dimension.Height = nHeight;
收集返回的对象。 CuboidDetail dataResponse = new CuboidDetail();
dataResponse = objClient.CalculateDetails2(dataRequest);
在指定的标签上显示返回对象中的值。 lblMethod.Text = "<Font color=#0000ff>Method Called ..... : CalculateDetails2 </font>"
lblArea.Text = "<Font color=#000080>Total Surface Area .. : " + dataResponse.SurfaceArea.ToString("F2") + "</font>";
lblVolume.Text = "<Font color=#000080>Total Volume .. : " + dataResponse.Volume.ToString("F2") + "</font>";
lblStatus.Text = "<font Color=#008000>Status : Success </font>";
}
关闭 try 块,并检查服务是否抛出了**CuboidFaultException**。如果是,则只需在指定用于状态显示的标签上显示 **CuboidFaultException** 的详细信息,并清除用于显示输出值(表面积、体积)的其他标签。
catch (FaultException< CuboidFaultException> eX)
{
lblMethod.Text = "<Font color=#ff0000>Method Called ..... : CalculateDetails2 </font>";
StringBuilder sbErr = new StringBuilder("<font Color=#ff0000>Status : Error ");
sbErr.Append("<br>Reason : "); sbErr.Append(eX.Detail.Reason);
sbErr.Append("<br>Source : "); sbErr.Append(eX.Detail.Source);
sbErr.Append("<br>Details : "); sbErr.Append(eX.Detail.Detail);
sbErr.Append("<br>HelpLink : "); sbErr.Append(eX.Detail.HelpLink);
sbErr.Append("</Font>");
lblArea.Text = "";
lblVolume.Text = "";
lblStatus.Text = sbErr.ToString();
}
在指定的标签上显示 CuboidFaultException 的详细信息,并清除用于显示输出值(表面积、体积)的标签。
调用方法 CalculateDetails3
当 button **btnCalc3** 被单击时,将调用此方法。这是其处理程序。在此事件中,我们只需解析通过 Web 页面控件输入的输入值,然后调用方法。以下是事件处理程序的分解。protected void btnCalc3_Click(object sender, EventArgs e)
{
声明变量来保存不同的值,并解析页面控件中的输入(ID、长度、宽度和高度)。如果无效(输入了非数字值),则返回,允许输入 0 和 -1)。int nID=0, nLength=0, nWidth=0, nHeight=0;
if (ParseAndValidateInputs ( ref nID, ref nLength, ref nWidth, ref nHeight ) == false) return;
您获得了允许的值,因此将其存储到会话中,这在回发时很有用,因为您可以在回发时用会话中存储的值初始化控件。Session["ID"] = nID.ToString () ;
Session["Length"] = nLength .ToString ();
Session["Width"] = nWidth .ToString ();
Session["Height"] = nHeight.ToString () ;
创建服务代理对象,如果对象创建失败,则返回;CuboidServiceClient objClient = GetServiceClientObject();
if (objClient == null) return;
一旦收集了输入,并且代理对象已准备好,就可以调用服务了。将所有内容包含在 try 块中,用于错误捕获,以及捕获服务抛出的**FaultException**。
try
{
创建 CuboidDimension 对象,将输入值分配给该对象,以作为输入参数发送。
CuboidDimension dimData = new CuboidDimension();
dimData.Length = nLength;
dimData.Width = nWidth;
dimData.Height = nHeight;
创建对象以从服务返回的对象中收集值。
CuboidDetail dataResponse = new CuboidDetail();
dataResponse = objClient.CalculateDetails3(nID, dimData);
将输出显示在指定的标签上。
lblMethod.Text = "<Font color=#0000ff>Method Called ..... : CalculateDetails3 </font>";
lblArea.Text = "<Font color=#000080>Total Surface Area .. : " + dataResponse.SurfaceArea.ToString("F2") + "</font>";
lblVolume.Text = "<Font color=#000080>Total Volume .. : " + dataResponse.Volume.ToString("F2") + "</font>";
lblStatus.Text = "<font Color=#008000>Status : Success </font>";
关闭 try 块。
}
在发生服务端错误的情况下处理 CuboidFaultException,并将 CuboidFaultExcepti 的字段值以红色显示在指定为状态的标签上。
catch (FaultException<CuboidFaultException> eX)
{
lblMethod.Text = "<Font color=#ff0000>Method Called ..... : CalculateDetails3 </font>";
StringBuilder sbErr = new StringBuilder("<font Color=#ff0000>Status : Error ");
sbErr.Append("<br>Reason : ");
sbErr.Append(eX.Detail.Reason);
sbErr.Append("<br>Source : ");
sbErr.Append(eX.Detail.Source);
sbErr.Append("<br>Details : ");
sbErr.Append(eX.Detail.Detail);
sbErr.Append("<br>HelpLink : ");
sbErr.Append(eX.Detail.HelpLink);
sbErr.Append("</Font>");
lblArea.Text = "";
lblVolume.Text = "";
lblStatus.Text = sbErr.ToString();
}
输出
以下是 CuboidService 的基于 Web 的客户端的输出:
当所有输入均有效,并且调用了 CalculateDetails1。
当所有输入均有效,并且调用了 CalculateDetails2。
当所有输入均有效,并且调用了 CalculateDetails3。
当一个或多个输入无效,并且调用了 CalculateDetails1。
当所有输入均有效,并且调用了 CalculateDetails2。
当所有输入均有效,并且调用了 CalculateDetails3。
关注点
在本文中,我以一种非常简单的方式描述了所有三种合同类型,我还解释了每种合同类型的适用性。什么时候应该使用它们,我希望您会像我一样享受阅读这篇文章。
非常感谢您的阅读。
历史
初始版本。