WCF web service Data Members defaulting to null

2019-04-17 04:16发布

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>

1条回答
看我几分像从前
2楼-- · 2019-04-17 05:10

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:

class Program
{
    static void Main(string[] args)
    {
        XNamespace ns = "http://tempuri.org/";
        XElement element =
            new XElement(ns + "MyRequest",
                new XElement(ns + "ID", 5),
                new XElement(ns + "Name", "Test"),
                new XElement(ns + "Description", "This is a test"));

        DataContractSerializer serializer = new
            DataContractSerializer(typeof(MyRequest));
        using (XmlReader reader = element.CreateReader())
        {
            MyRequest request = (MyRequest)serializer.ReadObject(reader);
            Console.WriteLine("ID: {0}, Name: {1}, Description: {2}",
                request.ID, request.Name, request.Description);
        }
        Console.ReadLine();
    }

    [DataContract(Name = "MyRequest", Namespace = "http://tempuri.org/")]
    public class MyRequest
    {
        [DataMember]
        public int ID { get; set; }

        [DataMember]
        public string Name { get; set; }

        [DataMember]
        public string Description { get; set; }
    }
}

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 the DataContractSerializer for both the client and service... not so great when you're manually generating XML.

If you add Order properties to the DataMember attributes, it works:

    [DataContract(Name = "MyRequest", Namespace = "http://tempuri.org/")]
    public class MyRequest
    {
        [DataMember(Order = 0)]
        public int ID { get; set; }

        [DataMember(Order = 1)]
        public string Name { get; set; }

        [DataMember(Order = 2)]
        public string Description { get; set; }
    }

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 the DataMember attributes to match the order in which you actually plan to generate XML; or

  • Make 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 the DataContractSerializer since it's less finicky about things like that, but it doesn't quite seem to work out of the box with webHttpBinding. Something worth investigating when there's more time.

查看更多
登录 后发表回答