在开发基于Windows Communication Foundation (WCF) 的应用程序时,经常需要调用远程服务。当服务端定义了一个方法,比如:
public int UseScalarTypes(int value1, int value2)
{
// 实现细节
}
在客户端使用Visual Studio添加服务引用时,生成的方法签名可能会与服务端定义的不一致,例如:
public void UseScalarTypes(int value1, bool value1Specified, int value2, bool value2Specified, out int UseScalarTypesResult, out bool UseScalarTypesResultSpecified)
{
// 实现细节
}
这种不一致性是由WCF的WSDL生成方式导致的。在WCF中,即使参数是值类型(如int),WSDL也会将其标记为可选的。但是,通过使用消息契约,可以解决这个问题,使得客户端的方法签名与服务端保持一致。
在WCF中,WSDL的生成方式如下:
<xs:element name="UseScalarTypes">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="0" name="value1" type="xs:int" />
<xs:element minOccurs="0" name="value2" type="xs:int" />
</xs:sequence>
</xs:complexType>
</xs:element>
注意到minOccurs属性了吗?尽管int是值类型,不接受null值,WCF仍然将其标记为可选。当使用Visual Studio的“添加服务引用”功能时,生成器会忽略这个属性,按照服务契约中声明的方式创建客户端方法。但是,如果使用旧的“添加Web引用”选项,生成器会检查minOccurs属性,意识到变量是可选的,并且由于这是值类型,它将可选性转换为一组变量:value + xxxSpecified。
要修复这个问题,需要创建一组消息契约,一个用于请求,一个用于响应(如果有的话)。
首先,需要为请求和响应创建消息契约。
在请求消息契约类中,应用MessageContract属性,并将IsWrapped参数设置为false。
[MessageContract(IsWrapped = false)]
public class UseScalarTypesRequest
{
// 实现细节
}
将IsWrapped设置为false将创建没有包装元素的XML,使得属性看起来像是实际的方法参数。
将方法的每个参数添加到类中作为属性,并应用MessageBodyMember属性。
[MessageContract(IsWrapped = false)]
public class UseScalarTypesRequest
{
[MessageBodyMember(Name = "value1")]
public int Value1 { get; set; }
[MessageBodyMember(Name = "value2")]
public int Value2 { get; set; }
}
通过这一步,可以使用属性的Name参数来重命名属性,以使用参数的命名约定。
对于响应消息契约,只需要一个属性来表示方法的返回类型。
[MessageContract(IsWrapped = false)]
public class UseScalarTypesResponse
{
[MessageBodyMember]
public int Result { get; set; }
}
最后,需要在契约和服务中将方法签名从使用参数改为使用创建的消息契约。
UseScalarTypesResponse UseScalarTypes(UseScalarTypesRequest parameters)
{
UseScalarTypesResponse result = new UseScalarTypesResponse();
result.Result = UseScalarTypes(parameters.Value1, parameters.Value2);
return result;
}