Как добавить префикс пространства имен для типа IXmlSerializable

У меня есть следующее определение класса

[XmlRoot(ElementName = "person",Namespace = "MyNamespace")]
public class Person : IXmlSerializable
{
    public string FirstName { get; set; }
    [XmlNamespaceDeclarations]
    public XmlSerializerNamespaces Namespaces
    {
        get
        {
            var xmlSerializerNamespaces = new XmlSerializerNamespaces();
            xmlSerializerNamespaces.Add("My", "MyNamespace");
            return xmlSerializerNamespaces;
        }
    }

    public string LastName { get; set; }

    public XmlSchema GetSchema()
    {
        return null;
    }

    /// <exception cref="NotSupportedException"/>
    public void ReadXml(XmlReader reader)
    {
        throw new NotSupportedException();
    }

    public void WriteXml(XmlWriter writer)
    {
        writer.WriteElementString("firstName",FirstName);
        writer.WriteElementString("lastName", LastName);
    }
}

Я хочу сериализовать его с префиксом My: MyNamespace, поэтому, когда я вызываю код

var xmlSerializer = new XmlSerializer(typeof(Person));
var person = new Person
            { FirstName = "John",LastName = "Doe"};            
xmlSerializer.Serialize(Console.Out, person, person.Namespaces);

Я ожидаю следующий результат:

<?xml version="1.0" encoding="ibm850"?>
<My:person xmlns:My="MyNamespace">
    <My:firstName>John</My:firstName>
    <My:lastName>Doe</My:lastName>
</My:person>

Но вместо этого я получаю следующий вывод:

<?xml version="1.0" encoding="ibm850"?>
<person xmlns="MyNamespace">
  <firstName>John</firstName>
  <lastName>Doe</lastName>
</person>

Я знаю, что написание префиксов работает, когда я использую атрибут SerializableAttribute и не наследую от IXmlSerializable, но мой класс в проекте намного сложнее, и я не могу использовать стандартный XmlSerializer.

Ответ 1

Тип XmlSerializer не предлагает ничего XmlSerializer для обработки этого.
Если вам действительно нужно использовать XmlSerializer, вы в конечном итоге получите пользовательскую реализацию XmlSerializer, которая не совсем открыта для расширения. По этой причине реализация ниже является скорее доказательством концепции, просто чтобы дать вам идею или отправную точку.
Для краткости я пропустил обработку ошибок и только сосредоточился на классе Person в вашем вопросе. Для обработки любых вложенных сложных свойств еще предстоит выполнить некоторую работу.

Поскольку методы Serialize не являются virtual нам придется их затенять. Основная идея заключается в том, чтобы перенаправить все перегрузки на один, имеющий пользовательскую реализацию.

Из-за настройки мы должны быть более явными в классе Person при написании элементов xml для своих свойств, указав пространство имен xml, которое будет применяться.

Код ниже

PrefixedXmlSerializer xmlSerializer = new PrefixedXmlSerializer(typeof(Person));
Person person = new Person { 
    FirstName = "John", 
    LastName = "Doe" 
    };
xmlSerializer.Serialize(Console.Out, person, person.Namespaces);

приводит к

<My:person xmlns:My="MyNamespace">
    <My:firstName>John</My:firstName>
    <My:lastName>Doe</My:lastName>
</My:person>

Вам решать, приемлемо ли это все.
В конце концов, <My:person xmlns:My="MyNamespace"> equals <person xmlns="MyNamespace">.

Человек

[XmlRoot(ElementName = "person", Namespace = NAMESPACE)]
public class Person : IXmlSerializable
{
    private const string NAMESPACE = "MyNamespace";

    public string FirstName { get; set; }

    [XmlNamespaceDeclarations]
    public XmlSerializerNamespaces Namespaces
    {
        get
        {
            var xmlSerializerNamespaces = new XmlSerializerNamespaces();
            xmlSerializerNamespaces.Add("My", NAMESPACE);                
            return xmlSerializerNamespaces;
        }
    }

    public string LastName { get; set; }

    public XmlSchema GetSchema()
    {
        return null;
    }

    /// <exception cref="NotSupportedException"/>
    public void ReadXml(XmlReader reader)
    {
        throw new NotSupportedException();
    }   

    public void WriteXml(XmlWriter writer)
    {
        // Specify the xml namespace.
        writer.WriteElementString("firstName", NAMESPACE, FirstName);
        writer.WriteElementString("lastName", NAMESPACE, LastName);
    }
}

PrefixedXmlSerializer

public class PrefixedXmlSerializer : XmlSerializer
{
    XmlRootAttribute _xmlRootAttribute;


    public PrefixedXmlSerializer(Type type) : base(type)
    {
        this._xmlRootAttribute = type.GetCustomAttribute<XmlRootAttribute>();        
    }


    public new void Serialize(TextWriter textWriter, Object o, XmlSerializerNamespaces namespaces)
    {
        // Out-of-the-box implementation.
        XmlTextWriter xmlTextWriter = new XmlTextWriter(textWriter);
        xmlTextWriter.Formatting = Formatting.Indented;
        xmlTextWriter.Indentation = 2;

        // Call the shadowed version. 
        this.Serialize(xmlTextWriter, o, namespaces, null, null);
    }


    public new void Serialize(XmlWriter xmlWriter, Object o, XmlSerializerNamespaces namespaces, String encodingStyle, String id)
    {
        // Lookup the xml namespace and prefix to apply.
        XmlQualifiedName[] xmlNamespaces = namespaces.ToArray();                                
        XmlQualifiedName xmlRootNamespace =
            xmlNamespaces
                .Where(ns => ns.Namespace == this._xmlRootAttribute.Namespace)
                .FirstOrDefault();

        // Write the prefixed root element with its xml namespace declaration.
        xmlWriter.WriteStartElement(xmlRootNamespace.Name, this._xmlRootAttribute.ElementName, xmlRootNamespace.Namespace);            

        // Write the xml namespaces; duplicates will be taken care of automatically.
        foreach (XmlQualifiedName xmlNamespace in xmlNamespaces)
        {
            xmlWriter.WriteAttributeString("xmlns", xmlNamespace.Name , null, xmlNamespace.Namespace);
        }

        // Write the actual object xml.
        ((IXmlSerializable)o).WriteXml(xmlWriter);

        xmlWriter.WriteEndElement();       
    }
}

Ответ 2

Попробуйте следующее.

public void WriteXml(XmlWriter writer)
{
    writer.WriteAttributeString("xmlns", "my", null, "MyNamespace");
    writer.WriteElementString("firstName", FirstName);
    writer.WriteElementString("lastName", LastName);
}

Ответ 3

Вам нужно использовать XmlSerializer? Если нет, попробуйте выполнить следующий код:

Person.cs

Добавить новый метод:

public void Serialize(XmlWriter writer)
{
    writer.WriteStartDocument();
    writer.WriteStartElement("My", "Person", "MyNamespace");
    writer.WriteElementString("My", "FirstName", "MyNamespace", FirstName);
    writer.WriteElementString("My", "LastName", "MyNamespace", LastName);
    writer.WriteEndElement();
    writer.WriteEndDocument();
}

Применение

var person = new Person { FirstName = "John", LastName = "Doe" };
person.Serialize(new XmlTextWriter(Console.Out));