Как сказал Надер, возможно, подумайте о создании более несвязанного дизайна. Однако, в моем случае, ослабление связи было неприемлемым. Вот моя иерархия классов и как я предлагаю решить проблему без использования пользовательской сериализации или 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 его сегодня! Мне очень нравится его ответ, и если у меня когда-нибудь будет возможность реорганизовать мой текущий проект, я определенно буду отделять бизнес-объект от логики сериализации для объектов, которые в противном случае выиграли бы от наследования, чтобы упростить код и упростить его для других использовать и понимать.
Спасибо за сообщение вашего ответа. Надер, как я думаю, многие найдут его очень поучительным и полезным.
У меня была проблема, подобная этому, когда мне нужно, чтобы свойство в производном классе появлялось последним в сообщении SOAP, моим решением было добавить свойство как внутреннее в базовый класс, а затем скрыть его с помощью «нового» ключевого слова в производном классе. См. Мой ответ [здесь] (http://stackoverflow.com/questions/22174311/wcf-serialization-order-issue/22177272#22177272). Надеюсь, поможет. –