Клиент SOAP не обрабатывает объекты XML должным образом; обнаружение Ошибка в XML-документе

Некоторые потребители нашей веб-службы WCF сталкиваются с исключением при попытке проанализировать наши ответы:

System.InvalidOperationException: There is an error in XML document (5, -349).
   at System.Xml.Serialization.XmlSerializer.Deserialize(XmlReader xmlReader, String encodingStyle, XmlDeserializationEvents events)
   at System.Xml.Serialization.XmlSerializer.Deserialize(XmlReader xmlReader, String encodingStyle)
   at System.Web.Services.Protocols.SoapHttpClientProtocol.ReadResponse(SoapClientMessage message, WebResponse response, Stream responseStream, Boolean asyncCall)
   at System.Web.Services.Protocols.SoapHttpClientProtocol.Invoke(String methodName, Object[] parameters)
   at [Consumer's Code]

Внутреннее исключение выглядит так:

'', hexadecimal value 0x0B, is an invalid character. Line 5, position -349.

   at System.Xml.XmlTextReaderImpl.Throw(Exception e)
   at System.Xml.XmlTextReaderImpl.Throw(String res, String[] args)
   at System.Xml.XmlTextReaderImpl.ThrowInvalidChar(Int32 pos, Char invChar)
   at System.Xml.XmlTextReaderImpl.ParseNumericCharRefInline(Int32 startPos, Boolean expand, BufferBuilder internalSubsetBuilder, Int32& charCount, EntityType& entityType)
   at System.Xml.XmlTextReaderImpl.ParseCharRefInline(Int32 startPos, Int32& charCount, EntityType& entityType)
   at System.Xml.XmlTextReaderImpl.ParseText(Int32& startPos, Int32& endPos, Int32& outOrChars)
   at System.Xml.XmlTextReaderImpl.ParseText()
   at System.Xml.XmlTextReaderImpl.ParseElementContent()
   at System.Xml.XmlTextReaderImpl.Read()
   at System.Xml.XmlTextReader.Read()
   at System.Xml.XmlReader.ReadElementString()
   at Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationReader1.Read43_TextWidgetConfig(Boolean isNullable, Boolean checkType)
   at Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationReader1.Read45_TextWidgetInfo(Boolean isNullable, Boolean checkType)
   at Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationReader1.Read49_WidgetInfo(Boolean isNullable, Boolean checkType)
   at Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationReader1.Read50_InstantPageData(Boolean isNullable, Boolean checkType)
   at Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationReader1.Read128_GetInstantPageDataResponse()
   at Microsoft.Xml.Serialization.GeneratedAssembly.ArrayOfObjectSerializer141.Deserialize(XmlSerializationReader reader)
   at System.Xml.Serialization.XmlSerializer.Deserialize(XmlReader xmlReader, String encodingStyle, XmlDeserializationEvents events)

Возвращаемые данные клиента каким-то образом содержали символы вертикальной табуляции. Глядя на наш XML, мы могли видеть, что эти символы правильно отображаются как  сущности. Выполнив быстрый поиск в Google, мы обнаружили ошибку с XmlSerializer, когда он не может обрабатывать определенные объекты, что необходимо исправить, изменив параметр в автоматически сгенерированных средствах чтения XML-прокси.

Потребитель признает, что ему необходимо исправить код на стороне клиента, но он не может быстро решить эту проблему с помощью патча. Они хотели бы, чтобы мы применили патч в нашем собственном коде, чтобы отфильтровать эти запрещенные символы.

  1. Задокументирован ли где-нибудь список проблемных символов для XmlSerializer?
  2. Есть ли у нас чистый способ изменить нашу службу WCF, чтобы мы могли автоматически удалять символы, не прибегая к замене строк во всех наших веб-методах?

Обновление:

Я нашел ответ на №1. Согласно спецификации XML, разрешены только определенные коды символов:

Char :: = # x9 | #xA | #xD | [# x20- # xD7FF] | [# xE000- # xFFFD] | [# x10000- # x10FFFF]

Итак, похоже, что DataContractSerializer на нашем сервере - это то, что здесь ошибочно. Сейчас я изучаю, как настроить этот сериализатор.

Обновление 2:

Похоже, проблема DataContractSerializer известна и вошли в Microsoft Connect.


person Jacob    schedule 16.02.2011    source источник
comment
Итак, ваш клиент предоставляет вам искаженный XML и ожидает, что вы примените хакерство, чтобы решить его проблему? Это просто даст им повод НЕ исправлять свой конец.   -  person drudge    schedule 17.02.2011
comment
Нет, мы обслуживаем несколько искаженный XML (с сущностями, выходящими за пределы разрешенного диапазона символов), созданный DataContractSerializer, потребителю нашей веб-службы. Даже WebService Studio подавляется выводом.   -  person Jacob    schedule 17.02.2011


Ответы (1)


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

Чтобы обойти эту проблему, я создал новый атрибут поведения операции, чтобы изменить сериализатор на настраиваемый сериализатор, который будет вырезать символы, которые будут отображаться как недопустимые объекты XML:

public class StripInvalidXmlCharactersBehaviorAttribute 
    : Attribute, IOperationBehavior
{
    public void AddBindingParameters(
        OperationDescription operationDescription, 
        BindingParameterCollection bindingParameters)
    {
    }

    public void ApplyClientBehavior(
        OperationDescription operationDescription, 
        ClientOperation clientOperation)
    {
        IOperationBehavior behavior =
            new StripInvalidXmlCharactersBehavior(operationDescription);
        behavior.ApplyClientBehavior(operationDescription, clientOperation);
    }

    public void ApplyDispatchBehavior(
        OperationDescription operationDescription, 
        DispatchOperation dispatchOperation)
    {
        IOperationBehavior behavior =
            new StripInvalidXmlCharactersBehavior(operationDescription);
        behavior.ApplyDispatchBehavior(
            operationDescription, dispatchOperation);
    }

    public void Validate(OperationDescription operationDescription)
    {
    }
}

Само поведение выглядит так:

internal class StripInvalidXmlCharactersBehavior 
    : DataContractSerializerOperationBehavior
{
    public StripInvalidXmlCharactersBehavior(OperationDescription opDesc)
        : base(opDesc)
    {
    }

    public override XmlObjectSerializer CreateSerializer(
        Type type, string name, string ns, IList<Type> knownTypes)
    {
        return new InvalidXmlStrippingSerializer(type, name, ns, knownTypes);
    }

    public override XmlObjectSerializer CreateSerializer(
        Type type, XmlDictionaryString name, XmlDictionaryString ns, 
        IList<Type> knownTypes)
    {
        return new InvalidXmlStrippingSerializer(type, name, ns, knownTypes);
    }
}

А это сериализатор:

internal class InvalidXmlStrippingSerializer : XmlObjectSerializer
{
    private DataContractSerializer _innerSerializer;

    public InvalidXmlStrippingSerializer(
        Type type, string name, string ns, IList<Type> knownTypes)
    {
        _innerSerializer = 
            new DataContractSerializer(type, name, ns, knownTypes);
    }

    public InvalidXmlStrippingSerializer(
        Type type, XmlDictionaryString name, XmlDictionaryString ns, 
        IList<Type> knownTypes)
    {
        _innerSerializer =
            new DataContractSerializer(type, name, ns, knownTypes);
    }

    public override bool IsStartObject(XmlDictionaryReader reader)
    {
        return _innerSerializer.IsStartObject(reader);
    }

    public override object ReadObject(
        XmlDictionaryReader reader, bool verifyObjectName)
    {
        return _innerSerializer.ReadObject(reader, verifyObjectName);
    }

    public override void WriteEndObject(XmlDictionaryWriter writer)
    {
        _innerSerializer.WriteEndObject(writer);
    }

    public override void WriteObjectContent(
        XmlDictionaryWriter writer, object graph)
    {
        graph = fixBadStringsRecursive(graph);
        _innerSerializer.WriteObjectContent(writer, graph);
    }

    private object fixBadStringsRecursive(object graph)
    {
        var objType = graph.GetType();
        if (objType == typeof(string))
        {
            graph = removeInvalidCharacters(graph as string);
        }
        else if (graph is IEnumerable)
        {
            foreach (var item in graph as IEnumerable)
            {
                fixBadStringsRecursive(item);
            }
        }
        else if (objType.IsClass)
        {
            // Look through the properties of the object 
            foreach (var prop in graph.GetType().GetProperties())
            {
                var propParams = prop.GetIndexParameters();
                if ((propParams == null || propParams.Length == 0)
                    && prop.GetGetMethod() != null)
                {
                    var propVal = prop.GetValue(graph, null);
                    if (propVal != null)
                    {
                        propVal = fixBadStringsRecursive(propVal);
                        if (prop.GetSetMethod() != null)
                        {
                            prop.SetValue(graph, propVal, null);
                        }
                    }
                }
            }
        }
        return graph;
    }

    private static string removeInvalidCharacters(string source)
    {
        // This is per the W3C XML spec:
        // http://www.w3.org/TR/xml/#NT-Char
        return new string(
            (
                from ch in source
                where
                    ch == '\u0009' || ch == '\u000a' || ch == '\u000d'
                    || (ch >= '\u0020' && ch <= '\ud7ff')
                    || (ch >= '\ue000' && ch <= '\ufffd')
                select ch
            ).ToArray()
        );
    }

    public override void WriteStartObject(
        XmlDictionaryWriter writer, object graph)
    {
        _innerSerializer.WriteStartObject(writer, graph);
    }
}

Чтобы применить это поведение к моей операции, теперь я могу просто добавить созданный мной атрибут.

person Jacob    schedule 16.02.2011