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.

ОНОВЛЕННЯ: Я просто хочу переконатися, що я відповів на питання ОП про видалення всіх просторів імен. Мій код вище буде працювати для цього; дозвольте мені показати, як. Тепер у наведеному вище прикладі ви дійсно не можете позбутися всіх просторів імен (тому що використовуються два простори імен). Десь у вашому документі 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просторів імен.


Цим я закінчую відповідь на інше питання. Але я хотів переконатися, що відповів на питання ОП щодо використання просторів імен, оскільки я вважаю, що ще не вирішив цього питання. Припустимо, що <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>

Для повноти, можливо, ви повинні включити правильну відповідь тут, а не просто посилатися на неї, а також мені цікаво знати, як ви зробите висновок, що це «непідтримувана поведінка».
Дейв Ван ден Ейнде

1
Сюди знову приїхав, щоб перевірити це, оскільки це найпростіше пояснення, яке я знайшов. Спасибо @fourpastmidnight
Андре Альбукерке

2
Я не розумію цього, для вашої остаточної відповіді ОП ви все ще використовуєте простір імен під час серіалізації "урна: Abracadabra" (конструктор), чому це не включено до кінцевого результату. Чи не слід використовувати ОП: XmlSerializerNamespaces EmptyXmlSerializerNamespaces = new XmlSerializerNamespaces (new [] {XmlQualifiedName.Empty});
dparkar

2
Це правильна відповідь, хоча не є найбільш голосовою. Онімі, що не спрацювало для мене, було те, що XmlTextWriter xtw = (XmlTextWriter)XmlTextWriter.Create(ms, xws);мені довелося замінити var xtw = XmlTextWriter.Create(memStm, xws);.
Леонель Санчес да Сільва

1
Минув час, коли я написав цю відповідь. XmlTextWriter.Createповертає (абстрактний?) XmlWriterекземпляр. Отже, @ Preza8 правильний, ви втратите можливість встановити інші XmlTextWriterспецифічні властивості (принаймні, не без того, щоб це робити вниз), отже, і конкретний склад XmlTextWriter.
чотирипаночі

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/… прямо написано, що ви не можете цього робити.
Ральф Лавель

Бул Ях! (Для подолання того, що повідомляють мс, ви не можете зробити)
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, цей серіалізатор буде використовувати простори імен, надані в екземплярі.


1. Я не бачу різниці. Ви все ще додаєте простір імен за замовчуванням до екземпляру XmlSerializerNamespaces.
Дейв Ван ден Ейнде

3
2. Це більше забруднює класи. Моя мета - не використовувати певний простір імен, моя мета - не використовувати простори імен взагалі.
Дейв Ван ден Ейнде

Я додав примітку про різницю між цим підходом та визначенням просторів XmlSerializerNames під час серіалізації.
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 значення "".


Хоча цьому питанню вже більше 10 років, то тоді його було вирішено створити тіло XML, яке взагалі не містило декларацій простору імен.
Дейв Ван ден Ейнде

1
Якщо я додам свою власну відповідь на питання, якому 10 років, це тому, що прийнята відповідь читається довше, ніж Біблія в її повному виданні.
Максенція

І найбільш голосована відповідь сприяє підходу (порожній простір імен), який не рекомендується.
Максенсія

Я не можу в цьому допомогти. Я можу зробити прийняту відповідь лише тією, яку я вважаю найбільш правильною.
Дейв Ван ден Ейнде
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.