I am new to WCF and created a simple REST service to accept an order object (series of strings from XML file), insert that data into a database, and then return an order object that contains the results. To test the service I created a small web project and send over a stream created from an xml doc.
The problem is that even though all of the items in the xml doc get placed into the stream, the service is nullifying some of them when it receives the data. For example lineItemId will have a value but shipment status will show null. I step through the xml creation and verify that all the values are being sent. However, if I clear the datamembers and change the names around, it can work. Any help would be appreciated.
This is the interface code
[ServiceContract(Namespace="http://companyname.com/wms/")]
public interface IShipping
{
[OperationContract]
[WebInvoke(Method = "POST", UriTemplate = "/Orders/UpdateOrderStatus/", BodyStyle=WebMessageBodyStyle.Bare)]
ReturnOrder UpdateOrderStatus(Order order);
}
[DataContract(Namespace="http://companyname.com/wms/order")]
public class Order
{
[DataMember]
public string lineItemId { get; set; }
[DataMember]
public string shipmentStatus { get; set; }
[DataMember]
public string trackingNumber { get; set; }
[DataMember]
public string shipmentDate { get; set; }
[DataMember]
public string delvryMethod { get; set; }
[DataMember]
public string shipmentCarrier { get; set; }
}
[DataContract]
public class ReturnOrder
{
[DataMember(Name = "Result")]
public string Result { get; set; }
}
This is what I'm using to send over an Order object:
string lineId = txtLineItem.Text.Trim();
string status = txtDeliveryStatus.Text.Trim();
string TrackingNumber = "1x22-z4r32";
string theMethod = "Ground";
string carrier = "UPS";
string ShipmentDate = "04/27/2010";
XNamespace nsOrders = "http://tempuri.org/order";
XElement myDoc =
new XElement(nsOrders + "Order",
new XElement(nsOrders + "lineItemId", lineId),
new XElement(nsOrders + "shipmentStatus", status),
new XElement(nsOrders + "trackingNumber", TrackingNumber),
new XElement(nsOrders + "delvryMethod", theMethod),
new XElement(nsOrders + "shipmentCarrier", carrier),
new XElement(nsOrders + "shipmentDate", ShipmentDate)
);
HttpWebRequest request = (HttpWebRequest)WebRequest.Create("http://localhost:3587/Deposco.svc/wms/Orders/UpdateOrderStatus/");
request.Method = "POST";
request.ContentType = "application/xml";
try
{
request.ContentLength = myDoc.ToString().Length;
StreamWriter sw = new StreamWriter(request.GetRequestStream());
sw.Write(myDoc);
sw.Close();
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
{
StreamReader reader = new StreamReader(response.GetResponseStream());
string responseString = reader.ReadToEnd();
XDocument.Parse(responseString).Save(@"c:\DeposcoSvcWCF.xml");
}
}
catch (WebException wEx)
{
Stream errorStream = ((HttpWebResponse)wEx.Response).GetResponseStream();
string errorMsg = new StreamReader(errorStream).ReadToEnd();
}
Bindings from Web.Config
<system.serviceModel>
<services>
<service behaviorConfiguration="DesposcoService.ShippingServiceBehavior" name="DesposcoService.ShippingService">
<endpoint address="wms" binding="webHttpBinding" contract="DesposcoService.IShipping" behaviorConfiguration="REST" bindingNamespace="http://companyname.com/wms">
<identity>
<dns value="localhost"/>
</identity>
</endpoint>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="DesposcoService.ShippingServiceBehavior">
<!-- To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above before deployment -->
<serviceMetadata httpGetEnabled="true"/>
<!-- To receive exception details in faults for debugging purposes, set the value below to true. Set to false before deployment to avoid disclosing exception information -->
<serviceDebug includeExceptionDetailInFaults="true"/>
</behavior>
</serviceBehaviors>
<endpointBehaviors>
<behavior name="REST">
<webHttp/>
</behavior>
</endpointBehaviors>
</behaviors>
</system.serviceModel>
I figured this out (apparently at around the same time James did).
The issue is with the
DataContractSerializer
, and here is a test case that reproduces it:If you run this, you'll see that it comes up empty for the
Description
property.This happens because the
DataContractSerializer
expects members to be in alphabetical order. This works fine when you are using theDataContractSerializer
for both the client and service... not so great when you're manually generating XML.If you add
Order
properties to theDataMember
attributes, it works:This time it finds the
Description
and all other fields.So to resolve the issue, you can do either of the following:
Add
Order
arguments to theDataMember
attributes to match the order in which you actually plan to generate XML; orMake sure you add elements in alphabetical order (by element name) on the client side.
I'm not particularly fond of either of these workarounds. They seem hackish and easy to break. I think for POX services I'd prefer to use the
XmlSerializer
instead of theDataContractSerializer
since it's less finicky about things like that, but it doesn't quite seem to work out of the box withwebHttpBinding
. Something worth investigating when there's more time.