Десерализовать несколько версий WSDL для одного объекта - PullRequest
2 голосов
/ 23 марта 2011

Я использую веб-сервис другой компании, у них запущено несколько версий, каждая более новая версия добавляет только новые поля / объекты, НО меняет некоторые имена элементов.

Мне бы хотелось иметь возможность использовать любую из версий с одинаковым кодом.

В частности, в одной версии метод поиска возвращает: <searchReturn><SummaryData_Version1Impl /><SummaryData_Version1Impl /></searchReturn>

и в другом варианте: <searchReturn><SummaryData_Version2Impl /><SummaryData_Version2Impl /></searchReturn>

Так что сейчас прокси, сгенерированный wsdl.exe, не может работать с обоими из-за изменения этого элемента.

  1. Лучшим решением было бы заставить другую компанию исправить свой сервис, чтобы не изменять имена элементов, но в этой ситуации это маловероятно
  2. Я думаю, что лучшим выбором для рабочего решения является отправка и получение SOAP-запроса вручную, изменение имен элементов, а затем десериализация вручную, что до сих пор казалось, что оно будет работать. - Но потребует совсем немного работы
    • Я только что подтвердил, что ручная загрузка xml (после изменения имени элемента с помощью string.Replace) десериализует любую версию службы в нужные объекты
  3. В качестве альтернативы сделайте аналогичную вещь, изменив сгенерированный прокси:
    • Если бы я мог перехватить и изменить мыльный ответ до того, как сгенерированный прокси попытается десериализовать его
    • Если бы я мог изменить атрибут XmlTypeAttribute службы во время выполнения
  4. Я также думал о наличии серии интерфейсов, поэтому у каждого класса были бы интерфейсы более старых class Data3 : IData3, IData2, IData1, которые, я думаю, позволили бы мне, по крайней мере, понижать. И поместите каждую версию в отдельное пространство имен.
  5. Существует пара приёмов утки, которые я только что немного изучил, которые могут сработать, но кажутся менее надежными.
  6. Есть ли другой способ десериализации из нескольких имен элементов?

Ответы [ 3 ]

1 голос
/ 25 марта 2011

Я получил вариант 2, который я упомянул в своем исходном вопросе, чтобы работать: (Это не полный пример, но должно быть достаточно очевидно, что вам нужно изменить, чтобы он работал в вашей ситуации, также отмечая эту вики, чтобы любой может упростить это в будущем)

Решение, описанное здесь: Создание запроса на мыло вручную, но с использованием всех сгенерированных классов wsdl.exe для десериализации и хранения данных после обработки ответа.

  • Не простое решение, но оно гораздо более мягкое, чем использование сгенерированных вызовов wsdl.exe, и любые методы, использующие сгенерированные классы, будут по-прежнему работать идеально
  • Я считаю, что он способен загружать любые элементы, общие для целевого объекта и исходного ответа, поэтому, если новые версии только добавляют поля:
    1. загрузка из более новой версии будет иметь все данные, кроме новых полей
    2. загрузка из более старой версии, новые поля будут нулевыми
  • Еще одним преимуществом является то, что вы можете загружать и XML-строку из любого места (чтение с диска, загрузка на веб-страницу) в NormalizeSummaryVersion, а остальная часть процесса будет работать точно так же, в результате чего объекты получат совместимость.

Настройка WebRequest выглядит следующим образом: (у меня был веб-сервис https с обычной аутентификацией, не удалось заставить req.Credentials работать правильно, поэтому я добавил этот заголовок вручную)

WebRequest req = WebRequest.Create(url);
req.Headers.Add("SOAPAction", soapAction);
req.ContentType = "text/xml;";
req.Method = WebRequestMethods.Http.Post;
req.Headers.Add(HttpRequestHeader.Authorization, "Basic " + basicAuthEncoded);

Затем запишите в этот поток данные xml для веб-метода: Это основной недостаток этого метода, я пока не нашел надежного способа создания мыльного конверта, для моего сервиса это не похоже заботиться о версии, указанной в xmlns:ver, поэтому я использую эту строку с SerializeObject(SearchCriteria), переданной в нее

//{0} is the soapAction
//{1} is the xml for that call

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ver="fake">
   <soapenv:Header/>
   <soapenv:Body>
      <ver:{0}>
         {1}
      </ver:{0}>
   </soapenv:Body>
</soapenv:Envelope>

Примечание: Ниже приведено мое доказательство концепции кода, я уверен, что его можно почистить и упростить приличную сумму.

С этим я могу прочитать ответ в xml от сервиса. Затем я вызываю NormalizeSummaryVersion, который переименовывает возможные различия имен узлов, также может обрабатывать любые другие узлы или данные в этом месте, если это необходимо.

public string NormalizeSummaryVersion(string xmlString)
{
    xmlString = Regex.Replace(xmlString,"SummaryData_Version2_2Impl|SummaryData_Version3_3Impl|SummaryData_Version4_4Impl",
                                "SummaryData_Version1_1Impl");

    return xmlString;
}

Так что теперь узлы имеют общее имя и формат (дополнительные или отсутствующие узлы, кажется, не имеют значения, они просто игнорируют их или устанавливают их по умолчанию с помощью этого метода десериализации)

ProcessLikeService извлекает XmlArray, который я хочу десериализовать из элементов soapenv:Envelope, и помещает его в новый XmlDocument, и я преобразую его обратно в строку.

Таким образом, после NormalizeSummaryVersion и внутри GetData() XmlDocument processedDoc будет этот xml, независимо от того, от какой версии был ответ Soap:

<?xml version="1.0" encoding="utf-16"?>
<searchReturn>
  <SummaryData_Version1_1Impl>
    <customerFirstName>first</customerFirstName>
    <customerLastName>last</customerLastName>
  </SummaryData_Version1_1Impl>
</searchReturn>

И, наконец, я могу использовать универсальный метод XmlDeserialize для получения нужных мне объектов. (Мой главный вызов для всего этого на самом деле возвращает GetData(xmlString).searchReturn, потому что

[XmlRoot("searchReturn")]
public class SearchReturn
{
    [XmlElement("SummaryData_Version1_1Impl", typeof(SummaryData))]
    public SummaryData[] searchReturn;
}

public SearchReturn GetData(string xmlString)
{
    System.Xml.XmlDocument doc = new System.Xml.XmlDocument();
    doc.LoadXml(xmlString);


    System.Xml.XmlNode DataNode = doc.SelectSingleNode("//searchReturn");

    System.Xml.XmlDocument processedDoc = new System.Xml.XmlDocument();
    processedDoc.AppendChild(processedDoc.ImportNode(DataNode, true));


    SearchReturn data = Deserialize<SearchReturn>(processedDoc);
    return data;
}

И общий метод десериализации:

public static T Deserialize<T>(XmlDocument xml)
{
    XmlSerializer s = new XmlSerializer(typeof(T));

    using (XmlReader reader = new XmlNodeReader(xml))
    {
        try
        {
            return (T)s.Deserialize(reader);
        }
        catch (Exception)
        {
            throw;
        }
    }
    throw new NotSupportedException();
}
1 голос
/ 29 марта 2011

Теперь я думаю, что лучшим вариантом является использование SoapExtension и установка атрибута SoapExtensionAttribute для его использования в любых методах, для которых необходимо изменить ответ.

  1. Изменитьсгенерируйте код и добавьте атрибут [ModifyResponseExtensionAttribute] к любым методам, которые требуют изменений, в вашем случае вам может понадобиться несколько классов SoapExtension
  2. Добавьте следующие классы в ваш проект:

    public class ModifyResponseExtension : SoapExtension
    {
        Stream inStream;
        Stream outStream;
    
        // Save the Stream representing the SOAP request or SOAP response into
        // a local memory buffer.
        public override Stream ChainStream(Stream stream)
        {
            inStream = stream;
            outStream = new MemoryStream();
            return outStream;
        }
    
        //This can get properties out of the Attribute used to enable this
        public override object GetInitializer(LogicalMethodInfo methodInfo, SoapExtensionAttribute attribute)
        {
            return null;
        }
    
        //This would have default settings when enabled by config file
        public override object GetInitializer(Type WebServiceType)
        {
            return null;
        }
    
        // Receive the object returned by GetInitializer-- set any options here
        public override void Initialize(object initializer)
        {
        }
    
        //  If the SoapMessageStage is such that the SoapRequest or
        //  SoapResponse is still in the SOAP format to be sent or received,
        //  save it out to a file.
        public override void ProcessMessage(SoapMessage message)
        {
            switch (message.Stage)
            {
                case SoapMessageStage.BeforeSerialize:
                    break;
                case SoapMessageStage.AfterSerialize:
                    //This is after the Request has been serialized, I don't need to modify this so just copy the stream as-is
                    outStream.Position = 0;
                    Copy(outStream, inStream);
                    //Not sure if this is needed (MSDN does not have it) but I like closing things
                    outStream.Close();
                    inStream.Close();
                    break;
                case SoapMessageStage.BeforeDeserialize:
                    //This is before the Response has been deserialized, modify here
                    //Could also modify based on something in the SoapMessage object if needed
                    ModifyResponseMessage();
                    break;
                case SoapMessageStage.AfterDeserialize:
                    break;
            }
        }
    
        private void ModifyResponseMessage()
        {
            TextReader reader = new StreamReader(inStream);
            TextWriter writer = new StreamWriter(outStream);
    
            //Using a StringBuilder for the replacements here
            StringBuilder sb = new StringBuilder(reader.ReadToEnd());
    
            //Modify the stream so it will deserialize with the current version (downgrading to Version1_1 here)
            sb.Replace("SummaryData_Version2_2Impl", "SummaryData_Version1_1Impl")
                .Replace("SummaryData_Version3_3Impl", "SummaryData_Version1_1Impl")
                .Replace("SummaryData_Version4_4Impl", "SummaryData_Version1_1Impl");
            //Replace the namespace
            sb.Replace("http://version2_2", "http://version1_1")
                .Replace("http://version3_3", "http://version1_1")
                .Replace("http://version4_4", "http://version1_1");
    
            //Note: Can output to a log message here if needed, with sb.ToString() to check what is different between the version responses
    
            writer.WriteLine(sb.ToString());
            writer.Flush();
    
            //Not sure if this is needed (MSDN does not have it) but I like closing things
            inStream.Close();
    
            outStream.Position = 0;
        }
    
        void Copy(Stream from, Stream to)
        {
            TextReader reader = new StreamReader(from);
            TextWriter writer = new StreamWriter(to);
            writer.WriteLine(reader.ReadToEnd());
            writer.Flush();
        }
    }
    
    // Create a SoapExtensionAttribute for the SOAP Extension that can be
    // applied to an XML Web service method.
    [AttributeUsage(AttributeTargets.Method)]
    public class ModifyResponseExtensionAttribute : SoapExtensionAttribute
    {
        private int priority;
    
        public override Type ExtensionType
        {
            get { return typeof(ModifyResponseExtension); }
        }
    
        public override int Priority
        {
            get { return priority; }
            set { priority = value; }
        }
    }
    

Таким образом, при необходимости можно изменить вручную запрос / ответ сгенерированного класса wsdl.exe.

1 голос
/ 23 марта 2011

Нет способа сделать это. Разные версии разные. Нет возможности заранее узнать, насколько они похожи.

...