Члены данных веб-службы WCF по умолчанию имеют значение null

Я новичок в WCF и создал простую службу REST для приема объекта заказа (серии строк из файла XML), вставки этих данных в базу данных, а затем возврата объекта заказа, содержащего результаты. Чтобы протестировать сервис, я создал небольшой веб-проект и отправил поток, созданный из документа xml.

Проблема заключается в том, что, несмотря на то, что все элементы XML-документа помещаются в поток, служба аннулирует некоторые из них при получении данных. Например, lineItemId будет иметь значение, но статус отгрузки будет нулевым. Я выполняю создание xml и проверяю, что все значения отправляются. Однако, если я удалю элементы данных и изменю имена, это может сработать. Любая помощь будет оценена по достоинству.

это код интерфейса

 [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; }

}

Это то, что я использую для отправки объекта Order:

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();
    }

Привязки из 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>

person James    schedule 29.04.2010    source источник


Ответы (1)


Я понял это (очевидно, примерно в то же время, что и Джеймс).

Проблема связана с DataContractSerializer, и вот тестовый пример, который ее воспроизводит:

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; }
    }
}

Если вы запустите это, вы увидите, что оно пусто для свойства Description.

Это происходит потому, что DataContractSerializer ожидает, что элементы будут расположены в алфавитном порядке. Это прекрасно работает, когда вы используете DataContractSerializer как для клиента, так и для службы... не так хорошо, когда вы вручную генерируете XML.

Если вы добавите свойства Order к атрибутам DataMember, это сработает:

    [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; }
    }

На этот раз он находит Description и все остальные поля.

Таким образом, чтобы решить проблему, вы можете сделать одно из следующих действий:

  • Добавьте Order аргументов к DataMember атрибутам, чтобы они соответствовали порядку, в котором вы фактически планируете генерировать XML; или

  • Убедитесь, что вы добавляете элементы в алфавитном порядке (по имени элемента) на стороне клиента.

Мне не особенно нравится ни один из этих обходных путей. Они кажутся хакерскими и легко ломаются. Я думаю, что для сервисов POX я бы предпочел использовать XmlSerializer вместо DataContractSerializer, так как он менее привередлив в таких вещах, но, похоже, он не работает из коробки с webHttpBinding. Кое-что, что стоит исследовать, когда будет больше времени.

person Aaronaught    schedule 29.04.2010
comment
На самом деле у меня есть пространства имен, определенные для контракта службы и контракта данных. В них была информация о компании, поэтому я удалил их для публикации. Однако после удаления их всех я получаю неверный запрос 400 с сервера каждый раз, когда я пытаюсь отправить его. - person James; 29.04.2010
comment
@James: Можете ли вы включить эту информацию (при необходимости анонимно) в вопрос? Также опубликуйте, какие привязки/конфигурации вы используете - трудно диагностировать без полного воспроизводимого случая. - person Aaronaught; 29.04.2010
comment
Конечно, как только я выясню, куда идти, чтобы отредактировать вопрос, я добавлю его (я новичок в переполнении стека) - person James; 29.04.2010
comment
хорошо, я обновил его, но заменил название компании на название компании. Спасибо за вашу информацию до сих пор и, пожалуйста, дайте мне знать, если я могу добавить что-нибудь. - person James; 29.04.2010
comment
@James: Теперь можно воспроизвести. Я заметил одну вещь: если вы измените Order на XElement в службе, вы увидите полный XML, поэтому значения определенно встречаются. Что-то происходит с сериализацией. - person Aaronaught; 29.04.2010
comment
Спасибо, Аарон, это отличное понимание. Я тоже смог это увидеть. По крайней мере, это исключает 1 вопрос из уравнения (попадают ли туда данные). - person James; 29.04.2010
comment
Благодаря вашей информации, аарон, я решил это. WCF десериализует в алфавитном порядке. Поэтому сначала он ставил DeliverMethod и т. д. и т. д. Если xml не отправляется в том порядке, в котором он ожидает, значения равны нулю, и он принимает значения только в правильных позициях. Спасибо за помощь. - person James; 29.04.2010
comment
Вау интересный результат! Кто-нибудь поднял это как ошибку с MS? - person Rob Stevenson-Leggett; 30.04.2010
comment
@Rob: Когда вы знаете, что искать (в алфавитном порядке), это кажется хорошо известным поведением. Это должно быть по замыслу; сериализатор не собирается случайно переупорядочивать все поля в алфавитном порядке. Почему они спроектировали его именно таким, скорее загадка... - person Aaronaught; 30.04.2010
comment
Вау, это один из тех W.T.F. моменты. Алфавитный порядок? В.Т.Ф. В любом случае, это только что решило настоящую головную боль для меня. - person Jonathan Henson; 19.06.2013