Как определить несколько имен для поля XmlElement?

У меня есть XML-документ, предоставленный клиентскими приложениями для моего приложения С#. Именно так клиент отправляет XML файл:

<?xml version="1.0" encoding="utf-8"?>
<SomeAccount>
    <parentId>2380983</parentId>
    <!-- more elements -->
</SomeAccount>

И класс С#, который поддерживает десериализацию XML:

[XmlRoot]
public class SomeAccount
{
    [XmlElement("parentId")]
    public long ParentId { get; set; }
    //rest of fields...
}

Но есть некоторые клиенты, чья система отправляет XML таким образом (обратите внимание на верхний регистр в LeParentId):

<?xml version="1.0" encoding="utf-8"?>
<SomeAccount>
    <LeParentId>2380983</LeParentId>
    <!-- similar for the other elements -->
</SomeAccount>

Как я могу создать это поле (и другие) для поддержки имен XML parentId и LeParentId?

Это метод, который я использую для десериализации XML:

public sealed class XmlSerializationUtil
{
    public static T Deserialize<T>(string xml)
    {
        if (xml == null)
            return default(T);
        XmlSerializer serializer = new XmlSerializer(typeof(T));
        StringReader stringReader = new StringReader(xml);
        return (T)serializer.Deserialize(stringReader);
    }
}

Я попытался добавить [XmlElement] дважды в поле, по одному на имя элемента, но это не сработало.

Ответ 1

Возьмите 2 - дайте возможность реализовать это сами, используя событие обработки неизвестного элемента (см. ниже комментарии для некоторых ограничений):

public class XmlSynonymDeserializer : XmlSerializer
{
    public class SynonymsAttribute : Attribute
    {
        public readonly ISet<string> Names;

        public SynonymsAttribute(params string[] names)
        {
            this.Names = new HashSet<string>(names);
        }

        public static MemberInfo GetMember(object obj, string name)
        {
            Type type = obj.GetType();

            var result = type.GetProperty(name);
            if (result != null)
                return result;

            foreach (MemberInfo member in type.GetProperties().Cast<MemberInfo>().Union(type.GetFields()))
                foreach (var attr in member.GetCustomAttributes(typeof(SynonymsAttribute), true))
                    if (attr is SynonymsAttribute && ((SynonymsAttribute)attr).Names.Contains(name))
                        return member;

            return null;
        }
    }

    public XmlSynonymDeserializer(Type type)
        : base(type)
    {
        this.UnknownElement += this.SynonymHandler;
    }

    public XmlSynonymDeserializer(Type type, XmlRootAttribute root)
        : base(type, root)
    {
        this.UnknownElement += this.SynonymHandler;
    }

    protected void SynonymHandler(object sender, XmlElementEventArgs e)
    {
        var member = SynonymsAttribute.GetMember(e.ObjectBeingDeserialized, e.Element.Name);
        Type memberType;

        if (member != null && member is FieldInfo)
            memberType = ((FieldInfo)member).FieldType;
        else if (member != null && member is PropertyInfo)
            memberType = ((PropertyInfo)member).PropertyType;
        else
            return;

        if (member != null)
        {
            object value;
            XmlSynonymDeserializer serializer = new XmlSynonymDeserializer(memberType, new XmlRootAttribute(e.Element.Name));
            using (System.IO.StringReader reader = new System.IO.StringReader(e.Element.OuterXml))
                value = serializer.Deserialize(reader);

            if (member is FieldInfo)
                ((FieldInfo)member).SetValue(e.ObjectBeingDeserialized, value);
            else if (member is PropertyInfo)
                ((PropertyInfo)member).SetValue(e.ObjectBeingDeserialized, value);
        }
    }
}

И теперь фактический код класса будет:

[XmlRoot]
public class SomeAccount
{
    [XmlElement("parentId")]
    [XmlSynonymDeserializer.Synonyms("LeParentId", "AnotherGreatName")]
    public long ParentId { get; set; }
    //rest of fields...
}

Чтобы десериализовать, просто используйте XmlSynonymDeserializer вместо обычного XmlSerializer. Это должно работать для большинства основных потребностей.

Известные ограничения:

  • Эта реализация поддерживает только элементы с несколькими именами; расширение его для атрибутов должно быть тривиальным.
  • Поддержка обработки свойств/полей в случаях, когда объекты, наследуемые друг от друга, не тестируются
  • Эта реализация не проверяет ошибки программирования (имеет атрибут только для чтения/константы/свойства, несколько членов с одинаковыми синонимами и т.д.)

Ответ 2

Я знаю, что это старый пост, но, возможно, это поможет кому-то еще иметь такую ​​же проблему. То, что вы могли бы использовать для этой проблемы, - XmlChoiceIdentifier.

[XmlRoot]
public class SomeAccount
{
    [XmlIgnore]
    public ItemChoiceType EnumType;

    [XmlChoiceIdentifier("EnumType")]
    [XmlElement("LeParentId")]
    [XmlElement("parentId")]
    public long ParentId { get; set; }

    //rest of fields...
} 
[XmlType(IncludeInSchema = false)]
public enum ItemChoiceType
{
    LeParentId,
    parentId
}

Теперь, если у вас есть новая xml-версия и новое имя XmlElement, вы просто добавляете это имя в перечисление ItemChoiceType и новый XmlElement для этого свойства.

Ответ 3

Если вам нужно только одно имя, вот быстрое (и довольно уродливое) решение, которое мы развернули в нескольких случаях в моей работе, когда нам нужно было только читать XML (это будет проблематично для сериализации обратно в XML) потому что это проще и проще понять:

[XmlRoot]
public class SomeAccount
{
    [XmlElement("parentId")]
    public long ParentId { get; set; }
    [XmlElement("LeParentId")]
    public long LeParentId { get { return this.ParentId; } set { this.ParentId = value; } }
    //rest of fields...
}