WCF – interchangeable data-contract types
In a WSDL based environment, unlike a CLR-world, we pass around the ‘state’ of an object and not the reference of an object. Well firstly, what does ‘state’ mean and does this also mean that we can send a struct where a class is expected (or vice-versa) as long as their ‘state’ is one and the same? Let’s see.
So I have an operation contract defined as below:
1: [ServiceContract]
2: public interface ILearnWcfServiceExtend : ILearnWcfService
3: {
4: [OperationContract]
5: Employee SaveEmployee(Employee employee);
6: }
7:
8: [ServiceBehavior]
9: public class LearnWcfService : ILearnWcfServiceExtend
10: {
11: public Employee SaveEmployee(Employee employee)
12: {
13: employee.EmployeeId = 123;
14: return employee;
15: }
16: }
Quite simplistic operation there (which translates to ‘absolutely no business value’). Now, the data contract Employee mentioned above is a struct.
1: public struct Employee
2: {
3: public int EmployeeId { get; set; }
4:
5: public string FName { get; set; }
6: }
After compilation and consumption of this service, my proxy (in the Reference.cs file) looks like below (I’ve ignored the rest of the details just to avoid unwanted confusion):
1: public partial struct Employee : System.Runtime.Serialization.IExtensibleDataObject, System.ComponentModel.INotifyPropertyChanged
I call the service with the code below:
1: private static void CallWcfService()
2: {
3: Employee employee = new Employee { FName = "A" };
4: Console.WriteLine("IsValueType: {0}", employee.GetType().IsValueType);
5: Console.WriteLine("IsClass: {0}", employee.GetType().IsClass);
6: Console.WriteLine("Before calling the service: {0} - {1}", employee.EmployeeId, employee.FName);
7: employee = LearnWcfServiceClient.SaveEmployee(employee);
8: Console.WriteLine("Return from the service: {0} - {1}", employee.EmployeeId, employee.FName);
9: }
The output is:
I now change my Employee type from a struct to a class in the proxy class and run the application:
1: public partial class Employee : System.Runtime.Serialization.IExtensibleDataObject, System.ComponentModel.INotifyPropertyChanged {
The output this time is:
The state of an object implies towards its composition, the properties and the values of these properties and not based on whether it is a reference type (class) or a value type (struct). And as shown above, we’re actually passing an object by its state and not by reference.
Continuing on the same topic of ‘type-interchangeability’, WCF treats two data contracts as equivalent if they have the same ‘wire-representation’. We can do so using the DataContract and DataMember attributes’ Name property.
1: [DataContract]
2: public struct Person
3: {
4: [DataMember]
5: public int Id { get; set; }
6:
7: [DataMember]
8: public string FirstName { get; set; }
9: }
10:
11: [DataContract(Name="Person")]
12: public class Employee
13: {
14: [DataMember(Name = "Id")]
15: public int EmployeeId { get; set; }
16:
17: [DataMember(Name="FirstName")]
18: public string FName { get; set; }
19: }
I’ve created two data contracts with the exact same wire-representation. Just remember that the names and the types of data members need to match to be considered equivalent. The question then arises as to what gets generated in the proxy class.
Despite us declaring two data contracts (Person and Employee), only one gets emitted – Person. This is because we’re saying that the Employee type has the same wire-representation as the Person type. Also that the signature of the SaveEmployee operation gets changed on the proxy side:
1: [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")]
2: [System.ServiceModel.ServiceContractAttribute(ConfigurationName="ServiceProxy.ILearnWcfServiceExtend")]
3: public interface ILearnWcfServiceExtend
4: {
5: [System.ServiceModel.OperationContractAttribute(Action="http://tempuri.org/ILearnWcfServiceExtend/SaveEmployee", ReplyAction="http://tempuri.org/ILearnWcfServiceExtend/SaveEmployeeResponse")]
6: ClientApplication.ServiceProxy.Person SaveEmployee(ClientApplication.ServiceProxy.Person employee);
7: }
But, on the service side, the SaveEmployee still accepts and returns an Employee data contract.
1: [ServiceBehavior]
2: public class LearnWcfService : ILearnWcfServiceExtend
3: {
4: public Employee SaveEmployee(Employee employee)
5: {
6: employee.EmployeeId = 123;
7: return employee;
8: }
9: }
Despite all these changes, our output remains the same as the last one:
This is type-interchangeability at work!
Here’s one more thing to ponder about. Our Person type is a struct and Employee type is a class. Then how is it that the Person type got emitted as a ‘class’ in the proxy? It’s worth mentioning that WSDL describes a type called Employee and does not say whether it is a class or a struct (see the SOAP message below):
1: <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
2: xmlns:tem="http://tempuri.org/"
3: xmlns:ser="http://schemas.datacontract.org/2004/07/ServiceApplication">
4: <soapenv:Header/>
5: <soapenv:Body>
6: <tem:SaveEmployee>
7: <!--Optional:-->
8: <tem:employee>
9: <!--Optional:-->
10: <ser:EmployeeId>?</ser:EmployeeId>
11: <!--Optional:-->
12: <ser:FName>?</ser:FName>
13: </tem:employee>
14: </tem:SaveEmployee>
15: </soapenv:Body>
16: </soapenv:Envelope>
There are some differences between how ‘Add Service Reference’ and the svcutil.exe generate the proxy class, but turns out both do some kind of reflection and determine the type of the data contract and emit the code accordingly. So since the Employee type is a class, the proxy ‘Person’ type gets generated as a class. In fact, reflecting on svcutil.exe application, you’ll see that there are a couple of places wherein a flag actually determines a type as a class or a struct. One example is in the ExportISerializableDataContract method in the System.Runtime.Serialization.CodeExporter class. Seems like these flags have a say in deciding whether the type gets emitted as a struct or a class.
This behavior is different if you use the WSDL tool though. WSDL tool does not do any kind of reflection of the data contract / serialized type, it emits the type as a class by default.
You can check this using the two command lines below:
Note to self: Remember ‘state’ and type-interchangeability when traversing through the WSDL planet!