Пространства имен xsi и xsd отсутствуют при использовании пользовательского IXmlSerializable

Я сталкиваюсь со следующей проблемой. Всякий раз, когда я использую XML-сериализацию по умолчанию в своем классе C#, пространства имен xsi и xsd автоматически добавляются механизмом сериализации .NET. Однако, когда сериализация определена через IXmlSerializable, пространства имен не добавляются.

Пример: этот код:

class Program
{
    static void Main(string[] args)
    {
        OutputSerialized(new Outer() { Inner = new Inner() });
        OutputSerialized(new OuterCustom() { Inner = new Inner() });
    }

    static void OutputSerialized<T>(T t)
    {
        var sb = new StringBuilder();
        using (var textwriter = new StringWriter(sb))
            new XmlSerializer(typeof(T)).Serialize(textwriter, t);
        Console.WriteLine(sb.ToString());
    }
}

[Serializable] public class Inner { }

[Serializable] public class Outer { public Inner Inner { get; set; } }

public class OuterCustom : IXmlSerializable
{
    public Inner Inner;

    public void WriteXml(System.Xml.XmlWriter writer)
    {
        writer.WriteStartElement("Inner");
        new XmlSerializer(typeof(Inner)).Serialize(writer, Inner);
        writer.WriteEndElement();
    }

    public System.Xml.Schema.XmlSchema GetSchema() { return null; }
    public void ReadXml(System.Xml.XmlReader reader) { /**/ }
}

производит следующий вывод:

<?xml version="1.0" encoding="utf-16"?>
<Outer xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Inner />
</Outer>
<?xml version="1.0" encoding="utf-16"?>
<OuterCustom>
  <Inner>
    <Inner xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" />
  </Inner>
</OuterCustom>

Вы можете видеть, что в сериализованной форме OuterCustom отсутствуют пространства имен xsd и xsi.

Как сделать так, чтобы мой OuterCustom вел себя так же, как Outer? Я что-то упустил в своем коде? Я неправильно делаю пользовательскую сериализацию?

Здесь на SO много вопросов о том, как избавиться от дополнительных пространств имен, но, кажется, никто не спрашивал о том, как вернуть их обратно.


person Vlad    schedule 27.11.2013    source источник
comment
Нет никакой разницы между двумя образцами XML с точки зрения XML... Вы пытаетесь выполнить поиск строк/регулярных выражений в XML?   -  person Alexei Levenkov    schedule 28.11.2013
comment
@Alexei Levenkov: Ну, я пытаюсь украсить вывод XML: в реальном коде у меня много Inner классов, каждый из которых содержит дополнительные пространства имен (что выглядит довольно уродливо). В любом случае, мне интересно, почему XML-сериализатор делает разные вещи в двух случаях, они кажутся идентичными.   -  person Vlad    schedule 28.11.2013
comment
Пытались ли вы вручную написать атрибуты xmlns на OuterCustom с помощью WriteAttributeString?   -  person Alexei Levenkov    schedule 28.11.2013
comment
@Алексей Левенков: только что попробовал. Кажется, помогает, но мне нужно жестко закодировать URI, такие как http://www.w3.org/2001/XMLSchema - это действительно хороший способ?   -  person Vlad    schedule 28.11.2013
comment
Да, это хорошо известные пространства имен, определенные W3C для XML. Совершенно безопасны для жесткого кода (для них могут быть даже существующие константы).   -  person Alexei Levenkov    schedule 28.11.2013
comment
@Alexei Levenkov: ну, с этим подходом есть еще одна проблема: дополнительные атрибуты записываются, когда OuterCustom используется внутри какого-то другого элемента. Пример: ideone.com/11u23u   -  person Vlad    schedule 28.11.2013
comment
Если бы мне поручили улучшить XML, я бы просто постобработал весь результат и переписал XML по своему вкусу, вместо того, чтобы пытаться убедить код сериализации делать то, что я хочу. Все версии XML, которые у вас есть до сих пор, эквивалентны и в равной степени удобочитаемы для меня. Вы можете покопаться в классе XmlWriter и посмотреть, сможете ли вы получить больше информации (например, LookupPrefix), чтобы сопоставить XML с вашими правилами украшения.   -  person Alexei Levenkov    schedule 28.11.2013
comment
@Alexei Levenkov: использование LookupPrefix — хорошее предложение, я не могу найти случай, который работает не так, как ожидалось: ideone.com/qyZh6t< /а>. Не могли бы вы подвести итоги обсуждения в качестве ответа?   -  person Vlad    schedule 28.11.2013


Ответы (1)


Во-первых, нет никакой разницы между указанными версиями XML для совместимого парсера XML. На самом деле не имеет значения, определены ли/как/где префиксы пространств имен, если узлы имеют правильные связанные пространства имен.

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

Можно писать атрибуты xmlns везде, где это необходимо, если они не конфликтуют на одном и том же узле. Используйте метод XmlWriter.WriteAttributeString, чтобы добавить их:

writer.WriteAttributeString(
  "xmlns", "prefix", null, "urn:mynamespace");

Чтобы избежать повторяющихся объявлений, проверьте, определен ли уже префикс для данного пространства имен, используя XmlWriter.LookupPrefix

Пример кода для обеспечения префиксов xsd и xsi (от Влада):

const string xsiNamespace = System.Xml.Schema.XmlSchema.InstanceNamespace;
const string xsdNamespace = System.Xml.Schema.XmlSchema.Namespace;

public static void EnsureDefaultNamespaces(System.Xml.XmlWriter writer)
{
   if (writer.LookupPrefix(xsiNamespace) == null)
       writer.WriteAttributeString("xmlns", "xsi", null, xsiNamespace);
   if (writer.LookupPrefix(xsdNamespace) == null)
       writer.WriteAttributeString("xmlns", "xsd", null, xsdNamespace);
}

Альтернативный подход, если требуется больше изменений для получения красивого XML, заключается в том, чтобы разрешить завершение сериализации, а затем обработать XML для настройки определений префиксов (т.е. нормализовать до желаемых префиксов, собрать все пространства имен и определить все сверху). Можно либо читать XML с кодом C#, либо даже создавать преобразование XSLT.

person Alexei Levenkov    schedule 28.11.2013