Вопрос по xml, c# – Порядок сериализации .NET

14

Я пытаюсь сериализовать некоторые объекты, используя XmlSerializer и наследование, но у меня возникают некоторые проблемы с упорядочением результата.

Ниже приведен пример, аналогичный тому, который я настроил: ~

public class SerializableBase
{
    [XmlElement(Order = 1)]
    public bool Property1 { get; set;}

    [XmlElement(Order = 3)]
    public bool Property3 { get; set;}
}

[XmlRoot("Object")]
public class SerializableObject1 : SerializableBase
{
}

[XmlRoot("Object")]
public class SerializableObject2 : SerializableBase
{
    [XmlElement(Order = 2)]
    public bool Property2 { get; set;}
}

Результат, который я хочу, заключается в следующем: ~

<Object>
    <Property1></Property1>
    <Property2></Property2>
    <Property3></Property3>
</Object>

Однако я получаю результат: ~

<Object>
    <Property1></Property1>
    <Property3></Property3>
    <Property2></Property2>
</Object>

Кто-нибудь знает, возможно ли это или какая-либо альтернатива?

Спасибо

У меня была проблема, похожая на эту, когда мне нужно, чтобы свойство в производном классе отображалось последним в сообщении SOAP, мое решение заключалось в том, чтобы добавить свойство как базовое в базовый класс, а затем скрыть его с помощью & quot; new & quot; Ключевое слово в производном классе. Смотри мой ответhere, Надеюсь, поможет. Shahin Dohan

Ваш Ответ

6   ответов
0

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

В моем проекте я создаю целую группу объектов для представления фрагментов XML-документа, который будет представлен через веб-сервис. Есть очень большое количество штук. Не все отправляются с каждым запросом (фактически, в этом примере я моделирую ответ, но концепции одинаковы). Эти части используются во многом как строительные блоки для сборки запроса (или дизассемблирования ответа, в данном случае). Итак, вот пример использования агрегации / инкапсуляции для достижения желаемого упорядочения, несмотря на иерархию наследования.

[Serializable]
public abstract class ElementBase
{
    // This constructor sets up the default namespace for all of my objects. Every
    // Xml Element class will inherit from this class.
    internal ElementBase()
    {
        this._namespaces = new XmlSerializerNamespaces(new XmlQualifiedName[] {
            new XmlQualifiedName(string.Empty, "urn:my-default-namespace:XSD:1")
        });
    }

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


[Serializable]
public abstract class ServiceBase : ElementBase
{
    private ServiceBase() { }

    public ServiceBase(Guid requestId, Guid? asyncRequestId = null, Identifier name = null)
    {
        this._requestId = requestId;
        this._asyncRequestId = asyncRequestId;
        this._name = name;
    }

    public Guid RequestId
    {
        get { return this._requestId;  }
        set { this._requestId = value;  }
    }
    private Guid _requestId;

    public Guid? AsyncRequestId
    {
        get { return this._asyncRequestId; }
        set { this._asyncRequestId = value; }
    }
    private Guid? _asyncRequestId;

    public bool AsyncRequestIdSpecified
    {
        get { return this._asyncRequestId == null && this._asyncRequestId.HasValue; }
        set { /* XmlSerializer requires both a getter and a setter.*/ ; }
    }

    public Identifier Name
    {
        get { return this._name; }
        set { this._name; }
    }
    private Identifier _name;
}


[Serializable]
public abstract class ServiceResponseBase : ServiceBase
{
    private ServiceBase _serviceBase;

    private ServiceResponseBase() { }

    public ServiceResponseBase(Guid requestId, Guid? asyncRequestId = null, Identifier name = null, Status status = null)
    {
        this._serviceBase = new ServiceBase(requestId, asyncRequestId, name);
        this._status = status;
    }

    public Guid RequestId
    {
        get { return this._serviceBase.RequestId; }
        set { this._serviceBase.RequestId = value; }
    }

    public Guid? AsyncRequestId
    {
        get { return this._serviceBase.AsyncRequestId; }
        set { this._serviceBase.AsyncRequestId = value; }
    }

    public bool AsynceRequestIdSpecified
    {
        get { return this._serviceBase.AsyncRequestIdSpecified; }
        set { ;  }
    }

    public Identifier Name
    {
        get { return this._serviceBase.Name; }
        set { this._serviceBase.Name = value; }
    }

    public Status Status
    {
        get { return this._status; }
        set { this._status = value; }
    }
}

[Serializable]
[XmlRoot(Namespace = "urn:my-default-namespace:XSD:1")]
public class BankServiceResponse : ServiceResponseBase
{
    // Determines if the class is being deserialized.
    private bool _isDeserializing;

    private ServiceResponseBase _serviceResponseBase;

    // Constructor used by XmlSerializer.
    // This is special because I require a non-null List<T> of items later on.
    private BankServiceResponse()
    { 
        this._isDeserializing = true;
        this._serviceResponseBase = new ServiceResponseBase();
    }

    // Constructor used for unit testing
    internal BankServiceResponse(bool isDeserializing = false)
    {
        this._isDeserializing = isDeserializing;
        this._serviceResponseBase = new ServiceResponseBase();
    }

    public BankServiceResponse(Guid requestId, List<BankResponse> responses, Guid? asyncRequestId = null, Identifier name = null, Status status = null)
    {
        if (responses == null || responses.Count == 0)
            throw new ArgumentNullException("The list cannot be null or empty", "responses");

        this._serviceResponseBase = new ServiceResponseBase(requestId, asyncRequestId, name, status);
        this._responses = responses;
    }

    [XmlElement(Order = 1)]
    public Status Status
    {
        get { return this._serviceResponseBase.Status; }
        set { this._serviceResponseBase.Status = value; }
    }

    [XmlElement(Order = 2)]
    public Guid RequestId
    {
        get { return this._serviceResponseBase.RequestId; }
        set { this._serviceResponseBase.RequestId = value; }
    }

    [XmlElement(Order = 3)]
    public Guid? AsyncRequestId
    {
        get { return this._serviceResponseBase.AsyncRequestId; }
        set { this._serviceResponseBase.AsyncRequestId = value; }
    }

    [XmlIgnore]
    public bool AsyncRequestIdSpecified
    {
        get { return this._serviceResponseBase.AsyncRequestIdSpecified; }
        set { ; } // Must have this for XmlSerializer.
    }

    [XmlElement(Order = 4)]
    public Identifer Name
    {
         get { return this._serviceResponseBase.Name; }
         set { this._serviceResponseBase.Name; }
    }

    [XmlElement(Order = 5)]
    public List<BankResponse> Responses
    {
        get { return this._responses; }
        set
        {
            if (this._isDeserializing && this._responses != null && this._responses.Count > 0)
                this._isDeserializing = false;

            if (!this._isDeserializing && (value == null || value.Count == 0))
                throw new ArgumentNullException("List cannot be null or empty.", "value");

            this._responses = value;
        }
    }
    private List<BankResponse> _responses;
}

Таким образом, хотя мне нужно создать свойства для всех содержащихся классов, я могу делегировать любую собственную логику, которая может быть у меня в установщиках / получателях свойств содержащихся классов, просто используя свойства содержащегося в нем класса, когда листовой класс свойства доступны. Поскольку наследования нет, я могу украсить все свойства листового класса с помощьюXmlElementAttribute атрибут и использовать любой порядок, который я считаю нужным.

UPDATE:

Я вернулся, чтобы вернуться к этой статье, потому что мои дизайнерские решения об использовании наследования классов вернулись, чтобы снова укусить меня. Хотя мое решение, приведенное выше, работает, я его использую, но я действительно считаю, что решение Nader является лучшим и должно быть рассмотрено до решения, которое я представил. Фактически, я сегодня его + 1! Мне очень нравится его ответ, и если мне когда-нибудь удастся реорганизовать мой текущий проект, я обязательно отделю бизнес-объект от логики сериализации для объектов, которые в противном случае получили бы большую выгоду от наследования, чтобы упростить код и упростить его. для других, чтобы использовать и понимать.

Спасибо за публикацию вашего ответа Nader, так как я думаю, что многие найдут его очень поучительным и полезным.

2

Тем не менее, вот решение этой проблемы:

Имя метода всегда должно начинаться с ShouldSerialize, а затем заканчиваться именем свойства. Затем вам просто нужно вернуть логическое значение, основанное на любых условных выражениях, относительно того, следует ли сериализовать значение или нет.

public class SerializableBase
{
    public bool Property1 { get; set;}
    public bool Property2 { get; set;}
    public bool Property3 { get; set;}

    public virtual bool ShouldSerializeProperty2 { get { return false; } }
}

[XmlRoot("Object")]
public class SerializableObject1 : SerializableBase
{        
}

[XmlRoot("Object")]
public class SerializableObject2 : SerializableBase
{
    public override bool ShouldSerializeProperty2 { get { return true; } }
}

Результат при использовании SerializableObject2: ~

<Object>
    <Property1></Property1>
    <Property2></Property2>
    <Property3></Property3>
</Object>

Результат при использовании SerializableObject1: ~

<Object>
    <Property1></Property1>
    <Property3></Property3>
</Object>

Надеюсь, что это помогает многим другим!

Error: User Rate Limit Exceeded
3

класс XmlSerializer сериализует базовый тип, а затем производные типы в указанном порядке и учитывает только свойство Order в каждом классе в отдельности. Даже если заказ не совсем то, что вы хотите, он все равно должен десериализоваться должным образом. Если вам действительно нужен такой порядок, вам нужно написать собственный сериализатор xml. Я хотел бы предостеречь против этого, потому что .NET XmlSerializer делает много специальной обработки для вас. Можете ли вы описать, почему вам нужны вещи в указанном вами порядке?

17

с точки зрения чистого XML, я бы сказал, что это, вероятно, плохая вещь, которую хочется делать.

.NET скрывает большую часть сложности таких вещей, как XmlSerialization - в этом случае она скрывает схему, которой должен соответствовать ваш сериализованный XML.

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

В XML-схемах при определении типов расширений должны присутствовать дополнительные элементы из дочернего класса.after элементы из базового класса.

по сути, вы бы имели схему, которая выглядит примерно так (теги xml-y удалены для ясности)

base
  sequence
    prop1
    prop3

derived1 extends base
  sequence
    <empty>

derived2 extends base
  sequence
    prop2

Нет способа поместить заполнитель между prop1 и prop3, чтобы указать, куда могут идти свойства из производного xml.

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

Например

[XmlRoot("Object")
public class SerializableObjectForPersistance
{
    [XmlElement(Order = 1)]
    public bool Property1 { get; set; }

    [XmlElement(Order = 2, IsNullable=true)]
    public bool Property2 { get; set; }

    [XmlElement(Order = 3)]
    public bool Property3 { get; set; }
}

Это отделяет ваш код сериализации xml от вашей объектной модели. Скопируйте все значения из SerializableObject1 или SerializableObject2 в SerializableObjectForPersistance, а затем сериализуйте его.

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

Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
Error: User Rate Limit ExceededcontainsError: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
4

This approach doesn't work, Я оставил пост, чтобы люди могли избежать такого мышления.

Сериализатор действует рекурсивно. В этом есть преимущество; при десериализации процесс десериализации может считывать базовый класс, а затем производный класс. Это означает, что свойство производного класса не установлено перед свойствами базы, что может привести к проблемам.

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

1) сделать базовый класс & apos; Property1 и Property3 виртуальные. 2) переопределите их с тривиальными свойствами в вашем производном классе. Например

public class SerializableBase
{
    [XmlElement(Order = 1)]
    public virtual bool Property1 { get; set;}

    [XmlElement(Order = 3)]
    public virtual bool Property3 { get; set;}
}

[XmlRoot("Object")]
public class SerializableObject1 : SerializableBase
{
}

[XmlRoot("Object")]
public class SerializableObject2 : SerializableBase
{
    [XmlElement(Order = 1)]
    public override bool Property1 
    { 
      get { return base.Property1; }
      set { base.Property1 = value; }
    }

    [XmlElement(Order = 2)]
    public bool Property2 { get; set;}

    [XmlElement(Order = 3)]
    public override bool Property3
    { 
      get { return base.Property3; }
      set { base.Property3 = value; }
    }

}

Это помещает конкретную реализацию свойства в самый производный класс, и порядок должен соблюдаться.

Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
3

но у меня недавно была похожая проблема в WCF, и я нашел решение, похожее на описанное выше Стивом Купером, но такое, которое действительно работает, и предположительно будет работать и для сериализации XML.

Если вы удалите атрибуты XmlElement из базового класса и добавите копию каждого свойства с другим именем в производные классы, которые обращаются к базовому значению через get / set, копии могут быть сериализованы с соответствующим именем, назначенным с использованием XmlElementAttribute. и, надеюсь, будет сериализован в порядке по умолчанию:

public class SerializableBase
{
   public bool Property1 { get; set;}
   public bool Property3 { get; set;}
}

[XmlRoot("Object")]
public class SerializableObject : SerializableBase
{
  [XmlElement("Property1")]
  public bool copyOfProperty1 
  { 
    get { return base.Property1; }
    set { base.Property1 = value; }
  }

  [XmlElement]
  public bool Property2 { get; set;}

  [XmlElement("Property3")]
  public bool copyOfProperty3
  { 
    get { return base.Property3; }
    set { base.Property3 = value; }
  }
}

Я также добавил интерфейс для добавления к производным классам, чтобы сделать копии обязательными:

interface ISerializableObjectEnsureProperties
{
  bool copyOfProperty1  { get; set; }
  bool copyOfProperty2  { get; set; }
}

Это не существенно, но означает, что я могу проверить, все ли реализовано во время компиляции, а не проверять результирующий XML. Первоначально я создал эти абстрактные свойства SerializableBase, но затем они сначала сериализуются (с базовым классом), что, как я теперь понимаю, логично.

Это вызывается обычным способом, изменяя одну строку выше:

public class SerializableObject : SerializableBase, ISerializableObjectEnsureProperties

Я только протестировал это в WCF и перенес эту концепцию в сериализацию XML без компиляции, поэтому, если это не сработает, извините, но я ожидаю, что она будет вести себя таким же образом - я уверен, что кто-то позволит мне знаю, если нет ...

Похожие вопросы