Why does the XmlRoot attribute gets ignored in WCF and how to overcome this - wcf

We've observed that when we expose a WCF service which uses classes decorated with various xml serialisation attributes, despite the fact that we use the XmlSerializerFormat attribute on the interface any XmlRoot attribute on any of the operation's parameters gets completely ignored.
The namespace of the parameters is always that of the service and not what we specify.
This is causing us problems as it does not seem to be backwards compatible with ASMX and also because we're using BizTalk, and need to have tighter control over the shape of the XML's exchanged.
A few questions then -
Anybody knows what is the rationale
behind this decision?
Anybody knows
how this is happening? I was under
the impressions that WCF, with the
XmlSerializerFormat attribute, uses
the XmlSerialiser to serialise the
types, which would suggest XmlRoot
should be taken into account, how
come this is not the case? (is it
only due to the fact that, taking
the SOAP envelope into account, the
parameter is not root?)
Most
importantly - anybody knows if
there's a way to 'force the issue' -
i.e. get the parameters to be of the
namespace of our choosing?
I've seen this post, but I don't believe it is relevant to my question -
As per Wagner Silveira's request - the contracts I used to test this are -
[ServiceContract(Namespace = "http://servicecontract"),
XmlSerializerFormat(Style = OperationFormatStyle.Document)]
public interface ITestService
{
[OperationContract]
MyOtherType MyTestMethod(MyType obj);
}
// Composite class for DCS and XMLS
[Serializable, XmlType, XmlRoot(Namespace = "http://datacontract")]
public class MyType
{
[XmlAttribute]
public string StringValue { get; set; }
}
// Composite class for DCS and XMLS
[Serializable, XmlType, XmlRoot(Namespace = "http://datacontract")]
public class MyOtherType
{
[XmlAttribute]
public string OtherStringValue { get; set; }
}

I assume you're using SOAP as the message format. In this case, the object you're serializing is not the root of the XML, the soap envelope is. So it makes sense that the XmlRoot would be ignored. By default WCF will create a message contract for you and name the response and it has the namespace of the service. What you can do is create your own message contract to have full control over SOAP.
Create the following two classes:
[MessageContract]
public class MyTestMethodRequest
{
[MessageBodyMember( Namespace = "http://datacontract" )]
public MyType MyType;
}
[MessageContract]
public class MyTestMethodResponse
{
[MessageBodyMember( Namespace = "http://datacontract" )]
public MyOtherType MyOtherType;
}
Then change the signature of your service operation to the following.
[OperationContract]
public MyTestMethodResponse MyTestMethod( MyTestMethodRequest request )
{
return new MyTestMethodResponse {
MyOtherType = new MyOtherType {
OtherStringValue = "bar"
}
};
}
Now if you example the SOAP messages you should see the following:
Request
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Header>
<Action xmlns="http://schemas.microsoft.com/ws/2005/05/addressing/none"
s:mustUnderstand="1">http://servicecontract/TestService/MyTestMethod</Action>
</s:Header>
<s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<MyTestMethodRequest xmlns="http://servicecontract">
<MyType StringValue="foo" xmlns="http://datacontract" />
</MyTestMethodRequest>
</s:Body>
</s:Envelope>
Response
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Header />
<s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<MyTestMethodResponse xmlns="http://servicecontract">
<MyOtherType OtherStringValue="bar" xmlns="http://datacontract" />
</MyTestMethodResponse>
</s:Body>
</s:Envelope>

I don't know why WCF ignores XmlRoot, so I can't answer that part of your question. But I do have a couple ways to solve the problem.
start with WSDL first.
If you have a particular set of XML namespaces you would like to apply to the messages that get sent and receieved, use WSDL and XML Schema to explicitly specify them.
Then, generate the Server-side stub code, or the client-side proxy code, directly from that WSDL via the svcutil.exe tool.
use a custom ServiceHost
The other option open to you, described at this link, is to use a custom ServiceHost that overrides WCF's decision to disregard the XmlRoot or XmlType attributes on message types.
If you choose to go for the WSDL-First approach, the WSDL should look like this:
<?xml version="1.0" encoding="utf-8" ?>
<definitions
xmlns="http://schemas.xmlsoap.org/wsdl/"
targetNamespace="urn:The-Service-namespace"
xmlns:tns="urn:The-Service-namespace"
xmlns:s="http://www.w3.org/2001/XMLSchema"
xmlns:n0="urn:The-Request-namespace"
xmlns:n1="urn:The-Response-namespace"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
elementFormDefault= "unqualified"
>
<types>
<s:schema targetNamespace="urn:The-Request-namespace" >
<s:complexType name="Type1">
<s:sequence>
<s:element name="x" minOccurs="1" maxOccurs="1" type="s:string"/>
</s:sequence>
</s:complexType>
<s:element name="Type1" type="n0:Type1" />
</s:schema>
<s:schema targetNamespace="urn:The-Response-namespace" >
<s:complexType name="Type2">
<s:sequence>
<s:element name="x" minOccurs="1" maxOccurs="1" nillable="false" type="s:string"/>
<s:element name="y" minOccurs="1" maxOccurs="1" nillable="false" type="s:int"/>
<s:element name="z" minOccurs="1" maxOccurs="1" nillable="false" type="s:boolean" />
</s:sequence>
</s:complexType>
<s:element name="Type2" type="n1:Type2" />
</s:schema>
</types>
<message name="RequestMessage">
<part name="inPart1" element="n0:Type1" />
</message>
<message name="ResponseMessage">
<part name="outPart1" element="n1:Type2" />
</message>
<portType name="PortTypeName">
<operation name="Method1">
<input message="tns:RequestMessage" />
<output message="tns:ResponseMessage" />
</operation>
</portType>
<binding name="InterfaceName" type="tns:PortTypeName">
<soap:binding
transport="http://schemas.xmlsoap.org/soap/http"
style="rpc" />
<operation name="Method1">
<soap:operation soapAction="" style="document" />
<input> <soap:body use="literal" /> </input>
<output> <soap:body use="literal" /> </output>
</operation>
</binding>
</definitions>
This WSDL is very simple - it defines a single operation, with a single request message and a single response message.
Notice there are three xml namespaces:
urn:The-Service-namespace
used for the element that wraps the request and response - the first element inside the <SOAP:body>
urn:The-Request-namespace
used for the element wrapped inside that request wrapper, which gets deserialized into an instance of Type1.
urn:The-Response-namespace
used for the element wrapped inside that response wrapper, which gets deserialized into an instance of Type2.
If your web services interface is more complicated, has more operations and consequently more request and response message types, you can add more namespaces, if you like, for all those additional types.

Related

HTTPClient.PostAsync<T> with XmlMediaTypeFormatter serialization attributes being ignored

I'm working with an existing xsd which looks something like this (shortened for brevity):
<?xml version="1.0" encoding="utf-8"?>
<xs:schema targetNamespace="http://www.mycompany.com/Widgets"
xmlns="http://www.mycompany.com/Widgets"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
elementFormDefault="qualified">
<xs:element name="Widget" type="WidgetDefinition" />
<xs:complexType name="WidgetDefinition">
<xs:sequence>
<xs:element name="name" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:schema>
When you run this through xsd.exe, you get a class definition like:
[System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "2.0.50727.3038")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(Namespace="http://www.mycompany.com/Widgets")]
[System.Xml.Serialization.XmlRootAttribute("Widget", Namespace="http://www.mycompany.com/Widgets", IsNullable=false)]
public partial class WidgetDefinition {
private string nameField;
public string Name {
get {
return this.nameField;
}
set {
this.nameField = value;
}
}
}
Fast forward ... I'm using HTTPClient to POST to a REST service. The code here is pretty straightforward.
var widget = new WidgetDefinition();
// do something here to hydrate widget
var httpClient = new HttpClient();
return httpClient.PostAsync<WidgetDefinition>(
uri, terminatedCall, new XmlMediaTypeFormatter());
On the receiving end, I want to take the request payload and convert it back to a WidgetDefinition object. If you examine the request content using:
request.Content.ReadAsStringAsync().Result
The xml looks like:
<WidgetDefinition xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns=\"http://schemas.datacontract.org/2004/07/\">
...
Based on the XmlTypeAttribute and XmlRootAttribute attributes on the generated WidgetDefinition class, I expect this to look like:
<Widget xmlns:i=\"http://www.mycompany.com/Widgets\">
...
It appears that the XmlTypeAttribute and XmlRootAttribute attributes are ignored when the serialization is happening on the sending side.
Any clue what could be causing this?
EDIT: If I serialize this manually using XmlSerializer, it obeys the serialization attributes on the WidgetDefinition class. I think my issue has to do with the formatter being passed to the PostAsync call.
From this xmlns=\"http://schemas.datacontract.org/2004/07/\" it appears that your REST service is configured to use DataContractSerializer (the default in a WCF service) and not XmlSerializer.
You can configure your service to use XmlSerializer instead which should at least get you further along:
http://msdn.microsoft.com/en-us/library/ms733901.aspx

Problems with WCF deserialising SOAP array containing polymorphic objects

I have a problem with a WCF web services that is deserialising an array of polymorphic objects. The server side is not WCF, but I created the stubs from WSDL. The important parts in WSDL are
<complexType name="NodeList">
<sequence>
<element name="data" type="cmtypes:Node" minOccurs="0" maxOccurs="unbounded" nillable="true"/>
</sequence>
</complexType>
and
<complexType name="Device">
<sequence>
<element name="uniqueKey" type="xsd:unsignedLong" minOccurs="1" maxOccurs="1"/>
<element name="revision" type="xsd:string" minOccurs="1" maxOccurs="1"/>
[...}
</sequence>
</complexType>
<complexType name="Node">
<complexContent>
<extension base="cmtypes:Device">
<sequence>
<element name="cmdaemonUrl" type="xsd:string" minOccurs="1" maxOccurs="1"/>
<element name="networks" type="cmtypes:NetworkInterfaceList" minOccurs="0" maxOccurs="1" nillable="true"/>
[...]
</sequence>
</extension>
</complexContent>
</complexType>
<complexType name="MasterNode">
<complexContent>
<extension base="cmtypes:Node">
<sequence>
</sequence>
</extension>
</complexContent>
</complexType>
VS created the following type for the list:
[System.Xml.Serialization.SoapIncludeAttribute(typeof(MasterNode))]
[System.Xml.Serialization.SoapIncludeAttribute(typeof(SlaveNode))]
[System.Xml.Serialization.SoapIncludeAttribute(typeof(VirtualSMPNode))]
[System.Xml.Serialization.SoapIncludeAttribute(typeof(VirtualNode))]
[System.Xml.Serialization.SoapIncludeAttribute(typeof(PhysicalNode))]
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.233")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.SoapTypeAttribute(Namespace="http://www.brightcomputing.com/cmtypes.xsd")]
public partial class Node : Device {
private string cmdaemonUrlField;
[...]
}
However, if I receive a message containing a master node like
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope" xmlns:SOAP-ENC="http://www.w3.org/2003/05/soap-encoding" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:cmtypes="http://www.brightcomputing.com/cmtypes.xsd" xmlns:cmdevice="http://www.brightcomputing.com/cmdevice.wsdl">
<s:Header xmlns:s="http://www.w3.org/2003/05/soap-envelope"></s:Header>
<SOAP-ENV:Body>
<cmdevice:getNodesResponse SOAP-ENV:encodingStyle="http://www.w3.org/2003/05/soap-encoding">
<nodes xsi:type="cmtypes:NodeList" xmlns="">
<data xsi:type="cmtypes:MasterNode">
<uniqueKey xsi:type="xsd:unsignedLong">38654705666</uniqueKey>
<revision xsi:type="xsd:string"></revision>
<modified xsi:type="xsd:boolean">false</modified>
<toBeRemoved xsi:type="xsd:boolean">false</toBeRemoved>
[...]
the whole stuff explodes with a System.ServiceModel.CommunicationException saying "Error in deserializing body of reply message for operation 'getNodes'." and "Object cannot be stored in an array of this type." in the inner exception. Why? What can I do to fix this (the stub generation)?
I have absolutely no clue due to very limited WCF/SOAP knowledge, so any help is appreciated.
Best regards,
Christoph
Edit: It might be relevant that the response actually contains "data" elements of different, polymorphic types, i.e. cmtypes:MasterNode and cmtypes:PhysicalNode.
Edit: Could it be that the Stub and the Message do not go together? As I understand, the stub expects a data property, which is an array:
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.233")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.SoapTypeAttribute(Namespace="http://www.brightcomputing.com/cmtypes.xsd")]
public partial class NodeList : object, System.ComponentModel.INotifyPropertyChanged {
private Node[] dataField;
/// <remarks/>
[System.Xml.Serialization.SoapElementAttribute(IsNullable=true)]
public Node[] data {
get {
return this.dataField;
}
set {
this.dataField = value;
this.RaisePropertyChanged("data");
}
}
public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName) {
System.ComponentModel.PropertyChangedEventHandler propertyChanged = this.PropertyChanged;
if ((propertyChanged != null)) {
propertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
}
}
}
However, in my answer I get a NodeList directly containing a sequence of data elements, which are actually nodes or derived from nodes:
<cmdevice:getNodesResponse SOAP-ENV:encodingStyle="http://www.w3.org/2003/05/soap-encoding">
<nodes xsi:type="cmtypes:NodeList" xmlns="">
<data xsi:type="cmtypes:MasterNode">
<uniqueKey xsi:type="xsd:unsignedLong">38654705666</uniqueKey>
[...]
</data>
<data xsi:type="cmtypes:PhysicalNode">
<uniqueKey xsi:type="xsd:unsignedLong">38654705669</uniqueKey>
[...]
</data>
[...]
Do you think I can fix that somehow by injecting a behavior in WCF? My big problem is that I have absolutely no possibility of chaning the server side...
Troubleshooting this is going to be hard. One thing I would try is using XmlSerializer to see if it can deserialize then start reducing the message until you get the offending type.

WCF: Element has already been exported error

I've acquired more information since the original post and have been able to recreate the problem in a very simple File > New Project > WCF Service Application solution. As a result I'm heavily editing the original content of this post to get rid of some of the superfluous information and simplify the examples:
We have a message contract defined as follows.
[MessageContract( WrapperName = "SingleTypeResponse", WrapperNamespace = "urn:WcfService1" )]
public class SingleTypeResponse<T>
{
[MessageBodyMember( Name = "ReturnValue" )]
public T ReturnValue { get; set; }
}
The service interface has the following:
[OperationContract]
SingleTypeResponse<string> GetStringData();
[OperationContract]
SingleTypeResponse<int> GetIntData();
When I run the project and navigate to the .svc file I get the following:
An exception was thrown in a call to a WSDL export extension: System.ServiceModel.Description.DataContractSerializerOperationBehavior
contract: http://tempuri.org/:IService1 ----> System.InvalidOperationException: The WcfService1.IService1.GetIntData operation references a message element [urn:WcfService1:SingleTypeResponse] that has already been exported from the WcfService1.IService1.GetStringData operation. You can change the name of one of the operations by changing the method name or using the Name property of OperationContractAttribute. Alternatively, you can control the element name in greater detail using the MessageContract programming model.
If I comment out GetIntData on the interface and test the service using the WCF Test Client, with the WrapperName property set, I get the following response XML:
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Header />
<s:Body>
<SingleTypeResponse xmlns="urn:WcfService1">
<ReturnValue xmlns="http://tempuri.org/">foo</ReturnValue>
</SingleTypeResponse>
</s:Body>
</s:Envelope>
I'm guessing that element is the source of the problem, it sees two versions of the same element, one with a string and one with an int.
I then uncommented the GetIntData and removed the WrapperName property from the MessageContract:
[MessageContract( WrapperNamespace = "urn:WcfService1" )]
public class SingleTypeResponse<T>
{
[MessageBodyMember( Name = "ReturnValue" )]
public T ReturnValue { get; set; }
}
I get the same error message with the exception that the message element it's complaining about is the ReturnValue property of the contract rather than the message contract name.
Once again commenting out GetIntData and testing with WCF Test Client I get:
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Header />
<s:Body>
<SingleTypeResponseOf_String xmlns="urn:WcfService1">
<ReturnValue xmlns="http://tempuri.org/">foo</ReturnValue>
</SingleTypeResponseOf_String>
</s:Body>
</s:Envelope>
So I'm able to get it to create a unique name for the wrapper, but the fact that SingleTypeResponseOf_String and SingleTypeResponseOf_Int32 both have a ReturnValue property continues to cause it to blow its brains out.
I've never thought about it before but it makes sense that generic types need to be defined as concrete types for a WSDL. Coming from that perspective, it appears that yes - you are defining two 'types' of SingleTypeResponse in your service. WSDL does not allow two element definitions of the same name & namespace to coexist (for obvious reasons - they need to be uniquely identifiable).
There are a couple of potential conflicts here. I think you've identified the first one - an element named SingleTypeResponse within the urn:WcfService1 namespace. When you turn off the naming of the message contract (allowing it to be named by the serializer) you see the below:
<!-- Setting wrapper name & wrapper namespace -->
<wsdl:message name="SingleTypeResponseOf_String">
<wsdl:part name="parameters" element="q1:SingleTypeResponse"
xmlns:q1="urn:WcfService1" />
</wsdl:message>
<!-- Setting wrapper namespace only -->
<wsdl:message name="SingleTypeResponseOf_String">
<wsdl:part name="parameters" element="q1:SingleTypeResponseOf_String"
xmlns:q1="urn:WcfService1" />
</wsdl:message>
This should avoid the first conflict, because running your two operations means that you'll be able to have two types of element (SingleTypeResponseOf_String and SingleTypeResponseOf_Int).
I believe your second conflict comes from the MessageBodyMember attribute. Because both the operations define messages in the same namespace, and both return types contain an element ReturnValue, you will get a conflict where the urn:ReturnValue element is defined twice, once as an int and once as a string.
To demonstrate see the following, with the GetIntData operation comment out, see the ReturnValue element defined:
<!-- from the XSD http://localhost/Service1.svc?xsd=xsd0 -->
<xs:element name="SingleTypeResponseOf_String">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="0" ref="q1:ReturnValue"
xmlns:q1="http://tempuri.org/" />
</xs:sequence>
</xs:complexType>
</xs:element>
<!-- from the XSD http://localhost/Service1.svc?xsd=xsd2 -->
<xs:schema elementFormDefault="qualified"
targetNamespace="http://tempuri.org/"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:tns="http://tempuri.org/">
<xs:element name="ReturnValue"
nillable="true"
type="xs:string" />
</xs:schema>
How can you allow the MessageContract to rename the ReturnValue property? I don't think you can with DataContractSerializer.
What's the solution? Well, I don't think there is a solution using [MessageBodyMember]. If you use [DataMember] it will work fine. You mentioned before that you're using net.tcp, so I assume your .NET to .NET. Do you need to exercise that level of control of the SOAP envelopes?
Normally I use DataContract as much as possible, and only venture into MessageContract when necessary - interfacing with older SOAP style platforms.

Why is my WCF Data Services method not appearing in the OData collections list?

When I view the root of my WCF Data Services service (http://localhost/MyService.svc/) in a browser I see this:
<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<service xml:base="http://localhost/MyService.svc/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:app="http://www.w3.org/2007/app" xmlns="http://www.w3.org/2007/app">
<workspace>
<atom:title>Default</atom:title>
</workspace>
</service>
I would expect to see a list of collections.
When I go to the $metadata URL I see this:
<?xml version="1.0" encoding="iso-8859-1" standalone="yes"?>
<edmx:Edmx Version="1.0" xmlns:edmx="http://schemas.microsoft.com/ado/2007/06/edmx">
<edmx:DataServices xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" m:DataServiceVersion="1.0">
<Schema Namespace="MyApp" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns="http://schemas.microsoft.com/ado/2007/05/edm">
<ComplexType Name="Package">
<Property Name="Id" Type="Edm.String" Nullable="true" />
</ComplexType>
</Schema>
<Schema Namespace="MyApp" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns="http://schemas.microsoft.com/ado/2007/05/edm">
<EntityContainer Name="PackageService" m:IsDefaultEntityContainer="true">
<FunctionImport Name="GetQueryablePackages" ReturnType="Collection(MyApp.Package)" m:HttpMethod="GET" />
</EntityContainer>
</Schema>
</edmx:DataServices>
</edmx:Edmx>
Why might my GetQueryablePackages collection not be appearing?
I'm using these access settings:
config.SetEntitySetAccessRule("*", EntitySetRights.AllRead);
config.SetServiceOperationAccessRule("*", ServiceOperationRights.All);
Service operations (the function import in the EDM) is not exposed in the service document. Only entity sets are exposed there.
If you want your data to be exposed in the service document make an entity set out of it. Depending on the provider model this differs. Typically it means exposing a property of type IQueryable on your context class. Note that T has to be an entity type (must have a key).
Can you share the context definition where you have defined the IQueryable <> properties. There are 2 things that come to my mind: First the properties must be of type IQueryable<> or some type that derives from it. Second, the element type refered by the IQueryable<> must be an entity type i.e. they must have key properties declared in them.
Hope this helps.
Thanks
Pratik
Or you can create an extension method like this:
public static class TestEntitiesExtensions
{
public static IEnumerable<Package> GetQueryablePackages(this TestEntities context)
{
var uri = new Uri(context.BaseUri, "GetQueryablePackages");
return context.Execute<Package>(uri);
}
}

Effect of Soap message in Adding a new member in Data Contract

Adding new members in the Data contract are harmless for all versions of clients. Its a known fact.
I wanted to test this.
I created a Book Data Contract. Its first version had the following members.
public class Book
{
[DataMember(IsRequired = false)]
public long BookID;
[DataMember(IsRequired = true)]
public string Title;
[DataMember(IsRequired = false)]
public string Author;
}
The client was created for this version of Contract.
Then I added another member "Edition" to the Book and the new version of the Book is
public class Book
{
[DataMember(IsRequired = false)]
public long BookID;
[DataMember(IsRequired = true)]
public string Title;
[DataMember(IsRequired = false)]
public string Author;
[DataMember(IsRequired = false)]
public string Edition;
}
The newer version of the contract works flawlessly with the older vesion of client.
I wanted to Physically see the on wire soap message. I have enabled the client and server side message logging.
I understand that new member "Edition" would be taken as Nil and would be while serializing.
But on the client side message, I saw something strange, the following.
The soap envelop looks like this
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Header>
<Action s:mustUnderstand="1" xmlns="http://schemas.microsoft.com/ws/2005/05/addressing/none">http://www.8fingergenie.com/BookShop/CheckAvailability</Action>
<ActivityId CorrelationId="631f540a-915b-4203-8fd8-e251da2fab85" xmlns="http://schemas.microsoft.com/2004/09/ServiceModel/Diagnostics">68a15797-124d-47f7-a85a-cf5c661e8011</ActivityId>
</s:Header>
<s:Body>
<CheckAvailability xmlns="http://www.8fingergenie.com/">
<book xmlns:d4p1="http://8fingergenie.com" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<d4p1:Author>Spensor Johnson</d4p1:Author>
<d4p1:BookID>1</d4p1:BookID>
<d4p1:Edition i:nil="true"></d4p1:Edition>
<d4p1:Title>Who Moved My Cheese</d4p1:Title>
</book>
</CheckAvailability>
</s:Body>
My question is how did the client with older version of Data contract know about edition while making communication? or am I missing something here.
Following is the structure of the client side Book's schema.
<xs:schema xmlns:tns="http://8fingergenie.com" elementFormDefault="qualified" targetNamespace="http://8fingergenie.com" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:complexType name="Book">
<xs:sequence>
<xs:element minOccurs="0" name="Author" nillable="true" type="xs:string" />
<xs:element minOccurs="0" name="BookID" type="xs:long" />
<xs:element name="Title" nillable="true" type="xs:string" />
</xs:sequence>
</xs:complexType>
<xs:element name="Book" nillable="true" type="tns:Book" />
It is not harmless for all clients. You could write a client that would fail if any members were added, deleted, or changed.
The fact that a client could be deliberately written to fail in that situation implies that it's possible to write a client which would inadvertently fail in the same situation.
On Further research I have found the following.
I was using Service reference in the client. The service reference by default creates the Data contract as type derived from IExtensibleDataObject. I didn't notice this would be the default.
So since the type is derived from IExtensibleDataObject, the new members from the other end is wraped inside the ExtensionDataObject. This is how the client was aware of the datacontract's new member.
Thank you for everyone who tried to help me.