Удалить пространства имен xml из спокойного ответа WCF

Я использую WCF для возврата вызывающему объекту простого старого документа XML (POX). Я использую средство форматирования XML Serializer для преобразования объектов в XML.

В возвращенном документе у меня есть некоторые посторонние ссылки на пространство имен xml (которых не было в версии ASMX) для XML-схемы и экземпляра. Я видел в Интернете различные аргументы в пользу того, что их не следует удалять, и я не согласен с возвратом простого XML-документа.

Каков самый простой способ удалить эти ссылки xmlns из возвращаемого XML-документа в WCF?

Подпись выглядит так:

public ResponseInfo Process(string input) {
}

person Duncan    schedule 27.01.2009    source источник


Ответы (9)


Вы можете удалить пространство имен XML, задав для параметра Namespace атрибута DataContract пустую строку, например:

[DataContract(Namespace = "")]
public class ResponseInfo
{
    // ...
}

Надеюсь, это поможет...

person Kevin Babcock    schedule 03.03.2009
comment
Это работает, но вам может потребоваться поместить атрибуты [DataMember] в поля, чтобы убедиться, что xml не пуст. - person Simon Munro; 30.04.2009
comment
Из моих тестов этот метод не избавляется от xmlns:i=w3.org/2001/ Объявление экземпляра XMLSchema - person Darrel Miller; 24.04.2010

У меня такая же проблема. Мне помогло добавление BodyStyle:=WebMessageBodyStyle.Bare в WebInvoke. Ответ больше не упаковывается в метаданные.

person user565923    schedule 06.01.2011
comment
У меня был BodyStyl=WebMessageBodyStyle.Wrapper, и изменение его на BodyStyle=WebMessageBodyStyle.Bare решило мою проблему. - person YoYoMyo; 30.12.2011
comment
Отличный Скотт! это потрясающе. Спасибо за полезный тег! - person Nathan Tregillus; 08.04.2012
comment
Это не влияет на наличие пространства имен - person Jack Ukleja; 02.06.2016

Я предполагаю, что вы пытаетесь вместо того, чтобы получить что-то вроде этого в начале вашего xml:

<ResponseInfo 
   xmlns="http://schemas.datacontract.org/2004/07/ResponseInfo"
   xmlns:i="http://www.w3.org/2001/XMLSchema-instance" >

Вы хотите просто:

<ResponseInfo>

К сожалению, я еще не видел простого способа удалить эти поля. Я искал решения, и большинство вариантов его удаления требуют создания собственного инспектора сообщений или собственного кодировщика.

person christophercotton    schedule 05.02.2009

Если вы хотите изменить Xml, одним из способов является использование XslTransform. У меня был аналогичный случай, когда мне нужно было удалить атрибуты xmlns из запроса Xml Post.

В WCF вы можете «перехватывать» сообщения Xml перед отправкой или до того, как они будут обработаны по пути, путем реализации либо IClientMessageInspector, либо IDispatchMessageInspector, в зависимости от того, нужно ли вам это на стороне клиента или на стороне сервера.

Например, чтобы удалить атрибуты пространства имен из исходящего сообщения Xml для веб-службы, я реализовал IClientMessageInspector, используя следующий код:

#region IClientMessageInspector Members
    public void AfterReceiveReply(ref Message reply, object correlationState)
    {   
        //Console.WriteLine(reply.ToString());
    }

    private XslCompiledTransform xt = null;

    public object BeforeSendRequest(ref Message request, IClientChannel channel)
    {
        Console.WriteLine(request.ToString());
        if (!request.IsEmpty)
        {
            XmlReader bodyReader =
                request.GetReaderAtBodyContents().ReadSubtree();

            MemoryStream ms = new MemoryStream();
            XmlWriter xw = XmlWriter.Create(ms);

            if (xt == null)
            {
                xt = new XslCompiledTransform(true);
                xt.Load("StripXmlnsi.xslt");
            }
            xt.Transform(bodyReader, xw);

            ms.Flush();
            ms.Seek(0, SeekOrigin.Begin);

            bodyReader = XmlReader.Create(ms);

            Message changedMessage = Message.CreateMessage(request.Version, null, bodyReader);
            changedMessage.Headers.CopyHeadersFrom(request.Headers);
            changedMessage.Properties.CopyProperties(request.Properties);
            request = changedMessage;
        }
        return null;
    }
    #endregion

и использовал следующее преобразование:

    <?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="*">
    <!-- remove element prefix (if any) -->
    <xsl:element name="{local-name()}">
      <!-- process attributes -->
      <xsl:for-each select="@*">
        <!-- remove attribute prefix (if any) -->
        <xsl:attribute name="{local-name()}">
          <xsl:value-of select="." />
        </xsl:attribute>
      </xsl:for-each>
      <xsl:apply-templates />
    </xsl:element>
  </xsl:template>
</xsl:stylesheet>

Надеюсь, это полезно.

person Bernie    schedule 14.03.2009
comment
Это ужасное решение. Вы действительно думаете, что можно написать весь этот код только для того, чтобы удалить пространство имен? - person user1662812; 03.10.2012
comment
Да, маленький размер не равно красота. Если у вас есть более простое решение, пожалуйста, опубликуйте его, но просто отклонить что-то, потому что оно занимает более 3 строк кода, — это не то, что сделает порядочный инженер. - person Bernie; 02.03.2013

Я нашел хорошее решение этой проблемы, которое позволяет внедрить собственный XmlSerializer в WCF, который используется при сериализации и десериализации запросов. Этот XmlSerializer можно настроить так, чтобы полностью исключить пространства имен XML (включая xmlns:i="w3.org/2001/XMLSchema-instance") или любым другим способом по вашему желанию.

В моем решении используется WcfRestContrib. Вы могли бы почти использовать прилагаемый форматер POX, но в нашем случае мы хотели поддержки атрибутов, поэтому мы написали свой собственный простой модуль форматирования.

Инструкции:

1) Ссылка на WcfRestContrib из вашего проекта.

2) Создайте реализацию IWebFormatter:

public class NamespacelessXmlFormatter : IWebFormatter {
    public object Deserialize(WebFormatterDeserializationContext context, Type type) {
        if (context.ContentFormat != WebFormatterDeserializationContext.DeserializationFormat.Xml) {
            throw new InvalidDataException("Data must be in xml format.");
        }

        return NamespacelessXmlSerializer.Deserialize(context.XmlReader, type);
    }

    public WebFormatterSerializationContext Serialize(object data, Type type) {
        using (var stream = NamespacelessXmlSerializer.Serialize(data, type)) {
            using (var binaryReader = new BinaryReader(stream)) {
                byte[] bytes = binaryReader.ReadBytes((int)stream.Length);
                return WebFormatterSerializationContext.CreateBinary(bytes);
            }
        }
    }
}

Который использует XmlSerializer, который соответствует вашим потребностям (вот наш, который просто опускает все пространства имен):

public static class NamespacelessXmlSerializer {

    private static readonly XmlSerializerNamespaces _customNamespace = new XmlSerializerNamespaces();

    private static readonly XmlWriterSettings _xmlSettings = new XmlWriterSettings {
        OmitXmlDeclaration = true
    };

    static NamespacelessXmlSerializer() {
        // to make sure .NET serializer doesn't add namespaces
        _customNamespace.Add(String.Empty, String.Empty);
    }

    /// <summary>
    /// Deserializes object from its XML representation.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="stream"></param>
    /// <returns></returns>
    public static T Deserialize<T>(Stream stream) {
        return (T)Deserialize(stream, typeof(T));
    }

    /// <summary>
    /// Deserializes object from its XML representation.
    /// </summary>
    public static object Deserialize(Stream stream, Type type) {
        var ds = new XmlSerializer(type);
        var d = ds.Deserialize(stream);
        return d;
    }

    public static object Deserialize(XmlDictionaryReader xmlReader, Type type) {
        var ds = new XmlSerializer(type);
        var d = ds.Deserialize(xmlReader);
        return d;
    }

    /// <summary>
    /// Serializes object to XML representation.
    /// </summary>
    /// <exception cref="InvalidOperationException">
    /// Is thrown when there was an error generating XML document. This can happen 
    /// for example if the object has string with invalid XML characters:
    /// http://www.w3.org/TR/2004/REC-xml-20040204/#charsets.
    /// See this article for other potential issues:
    /// http://msdn.microsoft.com/en-us/library/aa302290.aspx
    /// </exception>
    public static Stream Serialize<T>(T objectToSerialize) {
        return Serialize(objectToSerialize, typeof(T));
    }

    /// <summary>
    /// Serializes object to XML representation.
    /// </summary>
    /// <exception cref="InvalidOperationException">
    /// Is thrown when there was an error generating XML document. This can happen 
    /// for example if the object has string with invalid XML characters:
    /// http://www.w3.org/TR/2004/REC-xml-20040204/#charsets.
    /// See this article for other potential issues:
    /// http://msdn.microsoft.com/en-us/library/aa302290.aspx
    /// </exception>
    public static Stream Serialize(object objectToSerialize, Type type) {
        var stream = new MemoryStream();

        XmlWriter writer = XmlWriter.Create(stream, _xmlSettings);
        var x = new XmlSerializer(type);
        x.Serialize(writer, objectToSerialize, _customNamespace);

        stream.Position = 0;

        return stream;
    }
}

3) Примените атрибуты WebDispatchFormatter... к своей службе, используя собственную реализацию в качестве типа (на основе этого документация):

[WebDispatchFormatterConfiguration("application/xml")]
[WebDispatchFormatterMimeType(typeof(NamespacelessXmlFormatter), "application/xml")]

4) Примените атрибут WebDispatchFormatter ко всем вашим методам службы (на основе этой документации).

5) Вот и все. Протестируйте свою службу и убедитесь, что теперь она ведет себя так, как ожидалось.

person Lawrence Johnston    schedule 13.02.2012
comment
Это похоже на ответ для меня. - person Christian Findlay; 26.10.2017

Просто чтобы дать другую перспективу, если пространство имен уникально для вашего проекта, например:

http://mycompany.com/myapi/

то его следует сохранить.

Такие пространства имен являются лучшей практикой, они добавят менее 1 строки шаблона к любым вызовам XPath (которые вы можете превратить во вспомогательный метод) и потребуют около 15 строк вспомогательного кода для создания карты префикса/URI, но это ЕДИНСТВЕННЫЙ недостаток, и вы не всегда будете сталкиваться с этим.

Взамен вы получаете однозначные имена для каждого элемента, а это означает, что вы можете безнаказанно создавать сторонние пространства имен, например. теоретически вы можете вернуть XHTML напрямую без кодирования на уровне приложения.

Появление других пространств имен вряд ли станет проблемой, так как они вряд ли будут использоваться в каких-либо тегах, которые вы определили в своем проекте. На самом деле, если они есть, то где-то есть баг. Вероятным объяснением их существования является то, что фреймворк добавил их на тот случай, если ей нужно было добавить элемент где-то ниже места, где они объявлены, что может быть не тем местом, о котором вам нужно заботиться.

В другом ответе упоминается http://www.w3.org/2001/XMLSchema-instance. что немного особенно, возможно, вы могли бы сказать, какие пространства имен были добавлены.

person Simon Gibbs    schedule 14.04.2009

Не уверен, что это поможет, но у нас была похожая проблема. Вместо оформления тысяч элементов данных с помощью атрибутов DataContract/DataMember и использования (по умолчанию) DataContractSerializer мы обнаружили, что если бы наша служба WCF вместо этого использовала XmlSerializerFormat, мы могли бы легко десериализовать наши объекты.

[System.ServiceModel.ServiceContract]
public interface IRestService
{
    [System.ServiceModel.OperationContract]
    // Added this attribute to use XmlSerializer instead of DataContractSerializer
    [System.ServiceModel.XmlSerializerFormat(
        Style=System.ServiceModel.OperationFormatStyle.Document)]
    [System.ServiceModel.Web.WebGet(
        ResponseFormat = System.ServiceModel.Web.WebMessageFormat.Xml,
        UriTemplate = "xml/objects/{myObjectIdentifier}")]
    MyObject GetMyObject(int myObjectIdentifier);
}

Вот как мы десериализуем объекты:

public static T DeserializeTypedObjectFromXmlString<T>(string input)
{
    T result;

    try
    {
        System.Xml.Serialization.XmlSerializer xs = new System.Xml.Serialization.XmlSerializer(typeof(T));
        using (System.IO.TextReader textReader = new System.IO.StringReader(input))
        {
            result = (T)xs.Deserialize(textReader);
        }
    }
    catch
    {
        throw;
    }

    return result;
}
person Steven King    schedule 30.11.2011

В моем сервисе RESTful WCF, который я написал до стартового набора WCF RESTful, я сделал следующее, что дало мне хорошие, чистые результаты.

Во-первых, убедитесь, что установлено поведение конечной точки webHttp:

  <endpointBehaviors>
    <behavior name="Web">
      <webHttp/>
    </behavior>
  </endpointBehaviors>

Ваша конечная точка должна выглядеть примерно так (без контракта для простоты):

<endpoint address="" binding="webHttpBinding" behaviorConfiguration="Web" />

В моем сервисном контракте есть следующие операционные контракты:

    [WebGet(UriTemplate="tasks", ResponseFormat=WebMessageFormat.Xml )]
    [OperationContract]
    Task[] GetTasks();

    [WebGet(UriTemplate="tasks/{id}", ResponseFormat=WebMessageFormat.Xml)]
    [OperationContract]
    Task GetTask(string id);

В самой реализации сервиса нет ничего особенного. Вы можете попробовать изменить WebMessageFormat, но единственным другим элементом в перечислении является «json».

person Sailing Judo    schedule 01.02.2009
comment
Было бы неплохо, если бы downvoter оставил комментарий. Мой ответ на 100% правильный и работоспособный. Возможно, это не совсем то, что нужно человеку, который спрашивает, или, возможно, есть лучший способ, но он определенно работает. - person Sailing Judo; 01.03.2010
comment
я не был против, но, хотя ваш ответ может быть правильным, он не отвечает на этот конкретный вопрос. - person jm.; 17.04.2014

У меня такая же проблема, когда я работаю с клиентами ASMX, и для меня это решает проблему:

Добавьте в свой сервисный интерфейс:

[XmlSerializerFormat(Use = OperationFormatUse.Literal, Style = OperationFormatStyle.Document)]

Добавить в операции:

[OperationContract(Action = "http://www.YourNameSpace.com/ActionName",ReplyAction = "http://www.YourNameSpace.com/ActionName")]
person Qais Khateeb    schedule 04.09.2016