Today, I answered a question in the Hebrew MSDN forums about consuming WCF from a .NET 2 client, using the “Add Web Reference” option of Visual Studio.
Just in case you don’t know Hebrew, I’ll sum it up for you – when adding a web reference to a WCF service that exposes a method of the following sort:
int UseScalarTypes(int value1, int value2)
The generated method signature in the client app will look like so:
public void UseScalarTypes(
int value1, bool value1Specified,
int value2, bool value2Specified,
out int UseScalarTypesResult, out bool UseScalarTypesResultSpecified)
The question was why this happens and how this can be fixed to look like the service’s method signature.
Before we continue to see why this happens and how to fix it, the short answer is – Yes, you can make it look like the original contract by using message contracts. Continue reading to see how.
Why this happens:
In WCF, the WSDL generated for the above method looks like so:
<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>
Notice the minOccurs
? int
is a value type which means it doesn’t accept null
values, but still WCF marks it as optional. When you use the “Add Service Reference” option of WCF in Visual Studio, the generator ignores that attribute and creates the method declaration in the client side the same way it is declared in the service contract. However, if you use the old “Add Web Reference” option, the generator checks the minOccurs
, realizes that the variable is optional, and since this is a value type, it translates the optional into a set of variables: value + xxxSpecified
.
By the way, this is how the WSDL is created for the same method declaration when it is used in an ASMX-style web service:
<s:element name="UseScalarTypes">
<s:complexType>
<s:sequence>
<s:element minOccurs="1"
maxOccurs="1" name="value1" type="s:int" />
<s:element minOccurs="1"
maxOccurs="1" name="value2" type="s:int" />
</s:sequence>
</s:complexType>
</s:element>
Note 1: With ASMX web service, the minOccurs
/maxOccurs
is set properly because the “Add Web Reference” expects that, and therefore we don’t see this behavior with ASMX web service + add web reference.
Note 2: Since WCF ignores the minOccurs
, using the “Add Service Reference” to consume that ASMX web service will result in a client-side method with the same signature as declared in the service (without the xxxSpecified
).
So now that we know why this happens, let’s see how to fix it.
Step 1
Create a set of message contracts, one for the request and one for the response (if you have one).
Step 2
In the request message contract class, apply the MessageContract attribute and set the IsWrapped
parameter to false
, like so:
[MessageContract(IsWrapped = false)]
public class UseScalarTypesRequest
Setting the IsWrapped
to false
will create the XML without a wrapping element, making the properties look like they are actually method parameters.
Step 3
Add each parameter of the method to the class as a property, and apply the MessageBodyMember attribute to it. You can use this step to rename the property to use the naming convention of parameters by using the Name
parameter in the attribute. The result should look like so:
[MessageContract(IsWrapped = false)]
public class UseScalarTypesRequest
{
[MessageBodyMember(Name = "value1")]
public int Value1 { get; set; }
[MessageBodyMember(Name = "value2")]
public int Value2 { get; set; }
}
Step 4
Do the same for the response message contract, this time you just need one property for the return type of the method, for example:
[MessageContract(IsWrapped = false)]
public class UseScalarTypesResponse
{
[MessageBodyMember]
public int Result { get; set; }
}
Step 5
Change the method signature in the contract and service from using parameters to using the message contracts you’ve created, like so:
UseScalarTypesResponse UseScalarTypes(UseScalarTypesRequest parameters)
Of course, the immediate step will be to also change the implementation of your code to reflect the parameters being moved to a wrapper object - for convenience, you can leave your existing code in the service as is, change the contract, and then create the new methods which will simply call the older ones, like so:
UseScalarTypesResponse UseScalarTypes(UseScalarTypesRequest parameters)
{
UseScalarTypesResponse result = new UseScalarTypesResponse();
result.Result = UseScalarTypes(parameters.Value1, parameters.Value2);
return result;
}
That’s it, update your web reference in the client side, and watch how the method signature in the client side is without the xxxSpecified
.