XmlSerializer: удалите ненужные пространства имен xsi и xsd

132

Есть ли способ настроить XmlSerializer так, чтобы он не записывал пространства имен по умолчанию в корневой элемент?

Я получаю вот что:

<?xml ...>
<rootelement xmlns:xsi="..." xmlns:xsd="...">
</rootelement>

и я хочу удалить оба объявления xmlns.

Дубликат : Как сериализовать объект в XML, не получая xmlns = ”…”?

Дэйв Ван ден Эйнде
источник

Ответы:

63

Поскольку Дэйв попросил меня повторить мой ответ на вопрос « Пропуск всех пространств имен xsi и xsd при сериализации объекта в .NET» , я обновил этот пост и повторил свой ответ здесь из вышеупомянутой ссылки. В этом ответе использован тот же пример, что и для другого вопроса. Все, что следует ниже, скопировано дословно.


Прочитав документацию Microsoft и несколько решений в Интернете, я нашел решение этой проблемы. Он работает как со встроенной, так XmlSerializerи с настраиваемой сериализацией XML через IXmlSerialiazble.

Для начала я буду использовать тот же MyTypeWithNamespacesобразец XML, который до сих пор использовался в ответах на этот вопрос.

[XmlRoot("MyTypeWithNamespaces", Namespace="urn:Abracadabra", IsNullable=false)]
public class MyTypeWithNamespaces
{
    // As noted below, per Microsoft's documentation, if the class exposes a public
    // member of type XmlSerializerNamespaces decorated with the 
    // XmlNamespacesDeclarationAttribute, then the XmlSerializer will utilize those
    // namespaces during serialization.
    public MyTypeWithNamespaces( )
    {
        this._namespaces = new XmlSerializerNamespaces(new XmlQualifiedName[] {
            // Don't do this!! Microsoft's documentation explicitly says it's not supported.
            // It doesn't throw any exceptions, but in my testing, it didn't always work.

            // new XmlQualifiedName(string.Empty, string.Empty),  // And don't do this:
            // new XmlQualifiedName("", "")

            // DO THIS:
            new XmlQualifiedName(string.Empty, "urn:Abracadabra") // Default Namespace
            // Add any other namespaces, with prefixes, here.
        });
    }

    // If you have other constructors, make sure to call the default constructor.
    public MyTypeWithNamespaces(string label, int epoch) : this( )
    {
        this._label = label;
        this._epoch = epoch;
    }

    // An element with a declared namespace different than the namespace
    // of the enclosing type.
    [XmlElement(Namespace="urn:Whoohoo")]
    public string Label
    {
        get { return this._label; }
        set { this._label = value; }
    }
    private string _label;

    // An element whose tag will be the same name as the property name.
    // Also, this element will inherit the namespace of the enclosing type.
    public int Epoch
    {
        get { return this._epoch; }
        set { this._epoch = value; }
    }
    private int _epoch;

    // Per Microsoft's documentation, you can add some public member that
    // returns a XmlSerializerNamespaces object. They use a public field,
    // but that's sloppy. So I'll use a private backed-field with a public
    // getter property. Also, per the documentation, for this to work with
    // the XmlSerializer, decorate it with the XmlNamespaceDeclarations
    // attribute.
    [XmlNamespaceDeclarations]
    public XmlSerializerNamespaces Namespaces
    {
        get { return this._namespaces; }
    }
    private XmlSerializerNamespaces _namespaces;
}

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

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

MyTypeWithNamespaces myType = new MyTypeWithNamespaces("myLabel", 42);

/******
   OK, I just figured I could do this to make the code shorter, so I commented out the
   below and replaced it with what follows:

// You have to use this constructor in order for the root element to have the right namespaces.
// If you need to do custom serialization of inner objects, you can use a shortened constructor.
XmlSerializer xs = new XmlSerializer(typeof(MyTypeWithNamespaces), new XmlAttributeOverrides(),
    new Type[]{}, new XmlRootAttribute("MyTypeWithNamespaces"), "urn:Abracadabra");

******/
XmlSerializer xs = new XmlSerializer(typeof(MyTypeWithNamespaces),
    new XmlRootAttribute("MyTypeWithNamespaces") { Namespace="urn:Abracadabra" });

// I'll use a MemoryStream as my backing store.
MemoryStream ms = new MemoryStream();

// This is extra! If you want to change the settings for the XmlSerializer, you have to create
// a separate XmlWriterSettings object and use the XmlTextWriter.Create(...) factory method.
// So, in this case, I want to omit the XML declaration.
XmlWriterSettings xws = new XmlWriterSettings();
xws.OmitXmlDeclaration = true;
xws.Encoding = Encoding.UTF8; // This is probably the default
// You could use the XmlWriterSetting to set indenting and new line options, but the
// XmlTextWriter class has a much easier method to accomplish that.

// The factory method returns a XmlWriter, not a XmlTextWriter, so cast it.
XmlTextWriter xtw = (XmlTextWriter)XmlTextWriter.Create(ms, xws);
// Then we can set our indenting options (this is, of course, optional).
xtw.Formatting = Formatting.Indented;

// Now serialize our object.
xs.Serialize(xtw, myType, myType.Namespaces);

Как только вы это сделаете, вы должны получить следующий результат:

<MyTypeWithNamespaces>
    <Label xmlns="urn:Whoohoo">myLabel</Label>
    <Epoch>42</Epoch>
</MyTypeWithNamespaces>

Я успешно использовал этот метод в недавнем проекте с глубокой иерархией классов, сериализованных в XML для вызовов веб-служб. В документации Microsoft не очень ясно, что делать с общедоступным XmlSerializerNamespacesучастником после его создания, и многие думают, что это бесполезно. Но, следуя их документации и используя ее описанным выше способом, вы можете настроить, как XmlSerializer генерирует XML для ваших классов, не прибегая к неподдерживаемому поведению или «собственной» сериализации путем реализации IXmlSerializable.

Я надеюсь, что этот ответ раз и навсегда положит конец тому, как избавиться от стандартных пространств xsiи xsdпространств имен, сгенерированных XmlSerializer.

ОБНОВЛЕНИЕ: я просто хочу убедиться, что ответил на вопрос OP об удалении всех пространств имен. Мой код выше подойдет для этого; позвольте мне показать вам, как это сделать. Теперь, в приведенном выше примере, вы действительно не можете избавиться от всех пространств имен (потому что используются два пространства имен). Где-то в вашем XML-документе вам понадобится что-то вроде xmlns="urn:Abracadabra" xmlns:w="urn:Whoohoo. Если класс в примере является частью более крупного документа, то где-то выше должно быть объявлено пространство имен для одного из (или обоих) Abracadbraи Whoohoo. Если нет, то элемент в одном или обоих пространствах имен должен быть украшен каким-либо префиксом (у вас не может быть двух пространств имен по умолчанию, верно?). Итак, для этого примера Abracadabraэто пространство имен по умолчанию. Я мог бы внутри своего MyTypeWithNamespacesкласса добавить префикс пространства имен для Whoohooпространства имен следующим образом:

public MyTypeWithNamespaces
{
    this._namespaces = new XmlSerializerNamespaces(new XmlQualifiedName[] {
        new XmlQualifiedName(string.Empty, "urn:Abracadabra"), // Default Namespace
        new XmlQualifiedName("w", "urn:Whoohoo")
    });
}

Теперь в определении моего класса я указал, что <Label/>элемент находится в пространстве имен "urn:Whoohoo", поэтому мне больше ничего не нужно делать. Когда я сериализую класс, используя приведенный выше код сериализации без изменений, вот результат:

<MyTypeWithNamespaces xmlns:w="urn:Whoohoo">
    <w:Label>myLabel</w:Label>
    <Epoch>42</Epoch>
</MyTypeWithNamespaces>

Поскольку он <Label>находится в пространстве имен, отличном от остального документа, он должен каким-то образом быть «украшен» пространством имен. Обратите внимание , что до сих пор нет xsiи xsdпространств имен.


На этом мой ответ на другой вопрос заканчивается. Но я хотел убедиться, что я ответил на вопрос OP об использовании пространств имен, так как мне кажется, что я еще не рассмотрел этот вопрос. Предположим, что <Label>это часть того же пространства имен, что и остальная часть документа, в этом случае urn:Abracadabra:

<MyTypeWithNamespaces>
    <Label>myLabel<Label>
    <Epoch>42</Epoch>
</MyTypeWithNamespaces>

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

// As noted below, per Microsoft's documentation, if the class exposes a public
// member of type XmlSerializerNamespaces decorated with the 
// XmlNamespacesDeclarationAttribute, then the XmlSerializer will utilize those
// namespaces during serialization.
public MyTypeWithNamespaces( )
{
    this._namespaces = new XmlSerializerNamespaces(new XmlQualifiedName[] {
        new XmlQualifiedName(string.Empty, "urn:Abracadabra") // Default Namespace
    });
}

[XmlNamespaceDeclarations]
public XmlSerializerNamespaces Namespaces
{
    get { return this._namespaces; }
}
private XmlSerializerNamespaces _namespaces;

Затем, позже, в вашем коде, который использует MyTypeWithNamespacesобъект для его сериализации, вы назовете его, как я сделал выше:

MyTypeWithNamespaces myType = new MyTypeWithNamespaces("myLabel", 42);

XmlSerializer xs = new XmlSerializer(typeof(MyTypeWithNamespaces),
    new XmlRootAttribute("MyTypeWithNamespaces") { Namespace="urn:Abracadabra" });

...

// Above, you'd setup your XmlTextWriter.

// Now serialize our object.
xs.Serialize(xtw, myType, myType.Namespaces);

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

<MyTypeWithNamespaces>
    <Label>myLabel<Label>
    <Epoch>42</Epoch>
</MyTypeWithNamespaces>
fourpastmidnight
источник
Для полноты картины, возможно, вам следует включить здесь правильный ответ, а не просто ссылаться на него, а также мне интересно узнать, как вы пришли к выводу, что это «неподдерживаемое поведение».
Dave Van den Eynde,
1
Пришел сюда снова, чтобы проверить это, так как это наиболее простое объяснение, которое я нашел. Спасибо @fourpastmidnight
Андре Альбукерке
2
Я не понимаю, что касается вашего окончательного ответа OP, вы все еще используете пространство имен во время сериализации «urn: Abracadabra» (конструктор), почему это не включено в окончательный вывод. Не следует использовать OP: XmlSerializerNamespaces EmptyXmlSerializerNamespaces = new XmlSerializerNamespaces (new [] {XmlQualifiedName.Empty});
dparkar 01
2
Это правильный ответ, хотя и не самый популярный. Единственное, что у меня не сработало, так это то, что мне XmlTextWriter xtw = (XmlTextWriter)XmlTextWriter.Create(ms, xws);пришлось заменить на var xtw = XmlTextWriter.Create(memStm, xws);.
Леонель Санчес да Силва,
1
Я давно не писал этот ответ. XmlTextWriter.Createвозвращает (абстрактный?) XmlWriterэкземпляр. Итак, @ Preza8 верен, вы потеряете возможность устанавливать другие XmlTextWriterсвойства (по крайней мере, не без его преобразования), следовательно, конкретное приведение к XmlTextWriter.
fourpastmidnight
257
//Create our own namespaces for the output
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();

//Add an empty namespace and empty value
ns.Add("", "");

//Create the serializer
XmlSerializer slz = new XmlSerializer(someType);

//Serialize the object with our own namespaces (notice the overload)
slz.Serialize(myXmlTextWriter, someObject, ns)
Джереми
источник
24
Хммм ... вы, ребята, мятежники. На msdn.microsoft.com/en-us/library/… прямо сказано, что вы не можете этого сделать.
Ralph Lavelle
Бул, ага! (Для преодоления того, что ms говорит, что вы не можете сделать)
granadaCoder
3
Я не уверен, почему он «не поддерживается», но он делает именно то, что я хотел.
Дэн Бечард
8
Этот ответ генерирует пространства имен «xmlns: d1p1» и «xmlns: q1». Что это?
Леонель Санчес да Силва
2
Что ж, этот код работает для действительно действительно простых сериализаций без других определений пространств имен. Для нескольких определений пространств имен рабочий ответ - принятый.
Леонель Санчес да Силва,
6

Есть альтернатива - вы можете предоставить член типа XmlSerializerNamespaces в типе, который будет сериализован. Украсьте его атрибутом XmlNamespaceDeclarations . Добавьте префиксы пространства имен и URI к этому члену. Затем любая сериализация, которая явно не предоставляет XmlSerializerNamespaces, будет использовать пары префикс пространства имен + URI, которые вы поместили в свой тип.

Пример кода, предположим, это ваш тип:

[XmlRoot(Namespace = "urn:mycompany.2009")]
public class Person {
  [XmlAttribute] 
  public bool Known;
  [XmlElement]
  public string Name;
  [XmlNamespaceDeclarations]
  public XmlSerializerNamespaces xmlns;
}

Ты можешь сделать это:

var p = new Person
  { 
      Name = "Charley",
      Known = false, 
      xmlns = new XmlSerializerNamespaces()
  }
p.xmlns.Add("",""); // default namespace is emoty
p.xmlns.Add("c", "urn:mycompany.2009");

И это будет означать, что любая сериализация этого экземпляра, в которой не указан собственный набор пар префикс + URI, будет использовать префикс «p» для пространства имен «urn: mycompany.2009». Также будут опущены пространства имен xsi и xsd.

Разница здесь в том, что вы добавляете XmlSerializerNamespaces к самому типу, а не используете его явно при вызове XmlSerializer.Serialize (). Это означает, что если экземпляр вашего типа сериализуется кодом, которым вы не владеете (например, в стеке веб-сервисов), и этот код явно не предоставляет XmlSerializerNamespaces, этот сериализатор будет использовать пространства имен, предоставленные в экземпляре.

Cheeso
источник
1. Не вижу разницы. Вы по-прежнему добавляете пространство имен по умолчанию в экземпляр XmlSerializerNamespaces.
Дэйв Ван ден Эйнде
3
2. Это еще больше загрязняет классы. Моя цель - не использовать определенное пространство имен, моя цель - вообще не использовать пространства имен.
Дэйв Ван ден Эйнде
Я добавил примечание о различии между этим подходом и указанием XmlSerializerNamespaces только во время сериализации.
Cheeso
0

Я использую:

public class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        const string DEFAULT_NAMESPACE = "http://www.something.org/schema";
        var serializer = new XmlSerializer(typeof(Person), DEFAULT_NAMESPACE);
        var namespaces = new XmlSerializerNamespaces();
        namespaces.Add("", DEFAULT_NAMESPACE);

        using (var stream = new MemoryStream())
        {
            var someone = new Person
            {
                FirstName = "Donald",
                LastName = "Duck"
            };
            serializer.Serialize(stream, someone, namespaces);
            stream.Position = 0;
            using (var reader = new StreamReader(stream))
            {
                Console.WriteLine(reader.ReadToEnd());
            }
        }
    }
}

Чтобы получить следующий XML:

<?xml version="1.0"?>
<Person xmlns="http://www.something.org/schema">
  <FirstName>Donald</FirstName>
  <LastName>Duck</LastName>
</Person>

Если вам не нужно пространство имен, просто установите DEFAULT_NAMESPACE в "".

Maxence
источник
Хотя этому вопросу более 10 лет, тогда его суть заключалась в том, чтобы иметь тело XML, которое вообще не содержало никаких объявлений пространств имен.
Дэйв Ван ден Эйнде,
1
Если я добавлю свой ответ на вопрос, которому 10 лет, это потому, что принятый ответ длиннее для чтения, чем Библия в ее полном издании.
Максенс,
И ответ, получивший наибольшее количество голосов, продвигает подход (пустое пространство имен), который не рекомендуется.
Максенс,
Я ничего не могу поделать. Я могу дать только тот принятый ответ, который считаю наиболее правильным.
Дэйв Ван ден Эйнде,