2008-08-27 10 views
4

Помощь! У меня есть веб-сервис Axis, который используется приложением C#. Все работает отлично, за исключением того, что массивы длинных значений всегда встречаются как [0,0,0,0] - правильная длина, но значения не десериализуются. Я пробовал с другими примитивами (ints, double), и то же самое происходит. Что я делаю? Я не хочу менять семантику моего сервиса.Почему .NET не десериализует мой примитивный массив из веб-службы?

ответ

6

Вот что у меня получилось. Я никогда не нашел другого решения для этого, поэтому, если у вас есть что-то лучше, во что бы то ни стало, внести свой вклад.

Во-первых, длинное определение массива в WSDL: типы площадь:

<xsd:complexType name="ArrayOf_xsd_long"> 
    <xsd:complexContent mixed="false"> 
     <xsd:restriction base="soapenc:Array"> 
     <xsd:attribute wsdl:arrayType="soapenc:long[]" ref="soapenc:arrayType" /> 
     </xsd:restriction> 
    </xsd:complexContent> 
    </xsd:complexType> 

Далее мы создаем SoapExtensionAttribute, который будет выполнять исправление. Похоже, что проблема заключалась в том, что .NET не выполнял идентификатор multiref для элемента, содержащего двойное значение. Таким образом, мы обрабатываем элемент массива, надо найти значение, а затем вставить его значение в элемент:

[AttributeUsage(AttributeTargets.Method)] 
public class LongArrayHelperAttribute : SoapExtensionAttribute 
{ 
    private int priority = 0; 

    public override Type ExtensionType 
    { 
     get { return typeof (LongArrayHelper); } 
    } 

    public override int Priority 
    { 
     get { return priority; } 
     set { priority = value; } 
    } 
} 

public class LongArrayHelper : SoapExtension 
{ 
    private static ILog log = LogManager.GetLogger(typeof (LongArrayHelper)); 

    public override object GetInitializer(LogicalMethodInfo methodInfo, SoapExtensionAttribute attribute) 
    { 
     return null; 
    } 

    public override object GetInitializer(Type serviceType) 
    { 
     return null; 
    } 

    public override void Initialize(object initializer) 
    { 
    } 

    private Stream originalStream; 

    private Stream newStream; 

    public override void ProcessMessage(SoapMessage m) 
    { 
     switch (m.Stage) 
     { 
      case SoapMessageStage.AfterSerialize: 
       newStream.Position = 0; //need to reset stream 
       CopyStream(newStream, originalStream); 
       break; 

      case SoapMessageStage.BeforeDeserialize: 
       XmlWriterSettings settings = new XmlWriterSettings(); 
       settings.Indent = false; 
       settings.NewLineOnAttributes = false; 
       settings.NewLineHandling = NewLineHandling.None; 
       settings.NewLineChars = ""; 
       XmlWriter writer = XmlWriter.Create(newStream, settings); 

       XmlDocument xmlDocument = new XmlDocument(); 
       xmlDocument.Load(originalStream); 

       List<XmlElement> longArrayItems = new List<XmlElement>(); 
       Dictionary<string, XmlElement> multiRefs = new Dictionary<string, XmlElement>(); 
       FindImportantNodes(xmlDocument.DocumentElement, longArrayItems, multiRefs); 
       FixLongArrays(longArrayItems, multiRefs); 

       xmlDocument.Save(writer); 
       newStream.Position = 0; 
       break; 
     } 
    } 

    private static void FindImportantNodes(XmlElement element, List<XmlElement> longArrayItems, 
              Dictionary<string, XmlElement> multiRefs) 
    { 
     string val = element.GetAttribute("soapenc:arrayType"); 
     if (val != null && val.Contains(":long[")) 
     { 
      longArrayItems.Add(element); 
     } 
     if (element.Name == "multiRef") 
     { 
      multiRefs[element.GetAttribute("id")] = element; 
     } 
     foreach (XmlNode node in element.ChildNodes) 
     { 
      XmlElement child = node as XmlElement; 
      if (child != null) 
      { 
       FindImportantNodes(child, longArrayItems, multiRefs); 
      } 
     } 
    } 

    private static void FixLongArrays(List<XmlElement> longArrayItems, Dictionary<string, XmlElement> multiRefs) 
    { 
     foreach (XmlElement element in longArrayItems) 
     { 
      foreach (XmlNode node in element.ChildNodes) 
      { 
       XmlElement child = node as XmlElement; 
       if (child != null) 
       { 
        string href = child.GetAttribute("href"); 
        if (href == null || href.Length == 0) 
        { 
         continue; 
        } 
        if (href.StartsWith("#")) 
        { 
         href = href.Remove(0, 1); 
        } 
        XmlElement multiRef = multiRefs[href]; 
        if (multiRef == null) 
        { 
         continue; 
        } 
        child.RemoveAttribute("href"); 
        child.InnerXml = multiRef.InnerXml; 
        if (log.IsDebugEnabled) 
        { 
         log.Debug("Replaced multiRef id '" + href + "' with value: " + multiRef.InnerXml); 
        } 
       } 
      } 
     } 
    } 

    public override Stream ChainStream(Stream s) 
    { 
     originalStream = s; 
     newStream = new MemoryStream(); 
     return newStream; 
    } 

    private static void CopyStream(Stream from, Stream to) 
    { 
     TextReader reader = new StreamReader(from); 
     TextWriter writer = new StreamWriter(to); 
     writer.WriteLine(reader.ReadToEnd()); 
     writer.Flush(); 
    } 
} 

Наконец, мы помечать все методы в файле Reference.cs, что будет десериализация длинного массива с наш атрибут:

[SoapRpcMethod("", RequestNamespace="http://some.service.provider", 
     ResponseNamespace="http://some.service.provider")] 
    [return : SoapElement("getFooReturn")] 
    [LongArrayHelper] 
    public Foo getFoo() 
    { 
     object[] results = Invoke("getFoo", new object[0]); 
     return ((Foo) (results[0])); 
    } 

Это исправление давно специфична, но это, вероятно, может быть обобщена для обработки любого примитивного типа, имеющий эту проблему.

+0

Работает как шарм, только слишком плохо, нам нужно изменить сгенерированный код, чтобы добавить атрибут – Luuk 2012-06-26 14:09:26

3

Вот более или менее копия вставили версия blog post я написал на эту тему.

Аннотация: Вы можете либо изменить способ NET десериализации набора результатов (см. Решение Криса выше), либо вы можете перенастроить Axis для сериализации результатов таким образом, который совместим с реализацией .NET SOAP.

Если вы идете последний маршрут, вот как:

... сгенерированные классы выглядят и, по всей видимости функционировать нормально, но если вы будете смотреть на десериализованный массива на клиенте (.NET/WCF) вы обнаружите, что массив был десериализирован , и все значения в массиве равны 0. Вам придется вручную посмотреть ответ SOAP, возвращенный Axis, чтобы выяснить что не так; вот ответ образца (опять же, отредактированы для ясности):

<?xml version="1.0" encoding="UTF-8"?> 
<soapenv:Envelope xmlns:soapenv=http://schemas.xmlsoap.org/soap/envelope/> 
    <soapenv:Body> 
     <doSomethingResponse> 
      <doSomethingReturn> 
      <doSomethingReturn href="#id0"/> 
      <doSomethingReturn href="#id1"/> 
      <doSomethingReturn href="#id2"/> 
      <doSomethingReturn href="#id3"/> 
      <doSomethingReturn href="#id4"/> 
      </doSomethingReturn> 
     </doSomethingResponse> 
     <multiRef id="id4">5</multiRef> 
     <multiRef id="id3">4</multiRef> 
     <multiRef id="id2">3</multiRef> 
     <multiRef id="id1">2</multiRef> 
     <multiRef id="id0">1</multiRef> 
    </soapenv:Body> 
</soapenv:Envelope> 

Вы заметите, что ось не генерировать значения непосредственно в возвращаемого элемента, но вместо ссылки внешних элементов значений. Это может иметь смысл, когда есть много ссылок на относительно небольшого числа дискретных значений, но в любом случае это не правильно обрабатывается WCF BasicHttpBinding провайдера (и, по сообщениям по gSOAP и классических ссылок .NET веб, а).

Это мне потребовалось некоторое время, чтобы найти решение: редактировать серверную config.wsdd файл развертывания для Axis и найти следующий параметр:

<parameter name="sendMultiRefs" value="true"/> 

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

01 Теперь
java -cp %AXISCLASSPATH% org.apache.axis.client.AdminClient server-config.wsdl 

ответ веб-служба должна быть deserializable вашим клиентом .NET.