2009-06-19 4 views
13

XmlSerializer 및 상속을 사용하여 일부 개체를 serialize하려고하지만 결과를 정렬하는 데 문제가 있습니다. 다음은 .NET Serialization Ordering

내가 설정이 것과 유사한 예입니다 ~

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> 

가능한지 또는 다른 방법을 알고 있습니까?

감사

XmlSerializer 클래스는 기본 유형을 직렬화하고 파생 된 형식을 순서대로 만 개별적으로 각 클래스 내 주문 속성을 존중한다처럼 보이는
+0

SOAP 메시지의 마지막 부분에 나타 내기 위해 파생 클래스의 속성을 필요로하는 것과 비슷한 문제가 발생했습니다. 내 솔루션은 기본 클래스의 내부 속성을 추가 한 다음 "새로운"키워드로 숨 깁니다. 파생 클래스에서. 내 대답 [여기] (http://stackoverflow.com/questions/22174311/wcf-serialization-order-issue/22177272#22177272)을 참조하십시오. 희망이 도움이됩니다. –

답변

3

. 순서가 원하는 것만은 아니지만 올바르게 비 직렬화되어야합니다. 만약 당신이 정말로 당신이 커스텀 XML 시리얼 라이저를 작성할 필요가있는 것과 같은 순서를 가져야 만한다면. NET XmlSerializer는 당신을 위해 특별한 처리를 많이합니다. 당신이 언급 한 순서대로 필요한 이유를 설명 할 수 있습니까?

3

편집 : 이 방법은 작동하지 않습니다.. 사람들이이 사고 방식을 피할 수 있도록 게시물을 남겼습니다.

시리얼 라이저는 재귀 적으로 작동합니다. 이 점에 이점이 있습니다. deserialization에서 deserialization 프로세스는 기본 클래스를, 그 다음 파생 클래스를 읽을 수 있습니다. 즉, 기본 클래스의 속성보다 파생 클래스의 속성이 설정되지 않아 문제가 발생할 수 있습니다. 정말 중요한 (그리고 나는 그것이 순서대로를하는 것이 중요 왜 확실하지 않다) 경우

당신이 시도 할 수 있습니다 -

1) 기본 클래스 '속성 1과 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; } 
    } 

} 

이렇게하면 가장 많이 파생 된 클래스에 속성의 구체적인 구현을 지정하고 순서를 준수해야합니다.

+0

시도해 보았습니다. 실제로 작동하지 않았습니다. –

+0

아, 미안 해요. 구체적인 구현이 포함 된 클래스를 살펴 보았지만 명확하게 이해하지 못했을 것입니다. 이 방법이 작동하지 않는다는 것을 나타 내기 위해 게시물을 업데이트 할 것입니다. –

+0

그것은 여전히 ​​좋은 생각이었습니다 :) –

15

기술적으로 순수한 xml 관점에서 볼 때, 이것은 아마도 좋지 않은 일이라고 말할 수 있습니다.

.NET에서는 XmlSerialization과 같은 복잡한 작업을 숨 깁니다.이 경우 직렬화 된 XML이 준수해야하는 스키마가 숨겨집니다.

유추 된 스키마는 시퀀스 유형을 사용하여 기본 유형과 확장 유형을 설명합니다. 이것은 디시리얼라이저가 덜 엄격하고 순서가 다른 요소를 받아들이더라도 엄격한 순서가 필요합니다.

xml 스키마에서 확장 유형을 정의 할 때 하위 클래스의 추가 요소는 기본 클래스의 요소 뒤에 이되어야합니다.

당신 (명확성을 위해 제거 XML-Y 태그)

base 
    sequence 
    prop1 
    prop3 

derived1 extends base 
    sequence 
    <empty> 

derived2 extends base 
    sequence 
    prop2 

을 나타 내기 위해 prop1과 prop3 사이에 자리를 고수 할 수있는 방법은 없습니다 같이 보입니다 스키마를 기본적으로 할 경우 파생의 특성 XML을 사용할 수 있습니다.

결국 데이터 형식과 비즈니스 개체가 일치하지 않습니다. 아마도 가장 좋은 대안은 XML serialization을 처리 할 객체를 정의하는 것입니다. 예를

[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로 복사 한 다음 직렬화하십시오.

기본적으로 expectation xml serialization 프레임 워크를 사용하지 않는 직렬화 된 XML 형식에 대한 특정 제어를 원할 경우 비즈니스 개체 디자인 (이 경우 상속 구조)과 책임을 분리해야합니다 해당 비즈니스 오브젝트의 직렬화에 사용됩니다.

+0

+1, 스키마는 중요하지만 쉽게 간과 할 수 있습니다. – shambulator

+1

주문이 왜 "예상대로"행동하지 않는지에 관해서는 매우 좋은 지적입니다. 왜냐하면 우리는 XML 스키마가 아니라 OOP의 관점에서 생각하기 때문입니다. 필자의 경우에는 느슨한 결합이 적절한 디자인이 아닙니다. (다시 말하지만 이것은 틈새입니다. 항상 느슨한 결합을 위해 노력합니다.) 그게 상황이라면, 언제든지 "자식"객체가 "부모"개체. 캡슐화와 재사용을 여전히 달성 할 수 있지만 자식에 대해 요소의 정확한 순서를 지정할 수도 있습니다. – fourpastmidnight

+0

@Nader이 질문에 대한 답변에서 사용자의 대답을 언급했습니다. 내 솔루션이 작동하는 동안, 당신은 분명히 우월합니다. 내 디자인에 느슨한 커플 링이 적절하지 않다고 말하는 것은 잘못된 것입니다. 리팩토링 할 기회가 있다면 추천서를 사용하고 있습니다. – fourpastmidnight

0

Nader가 말했듯이, 아마도 좀 더 느슨한 결합 디자인을 생각해보십시오. 그러나, 제 경우에는 느슨한 결합이 적절하지 않았습니다. 다음은 클래스 계층 구조와 사용자 지정 직렬화 또는 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; 
} 

그래서, 나는 단순히 포함 된 클래스의 속성 때를 사용하여 I가 포함 된 클래스 (들) 속성 세터/게터 내에있을 수 있습니다 사용자 정의 로직을 위임 할 수 있습니다 리프 클래스의 속성에 액세스합니다. 상속이 없으므로 리프 클래스의 모든 속성을 XmlElementAttribute 특성으로 꾸밀 수 있으며 적합하다고 판단되는 순서를 사용할 수 있습니다.


UPDATE : 클래스 상속을 사용하는 방법에 대한 내 디자인 결정이 날 또 다시 물어 왔기 ​​때문에이 글을 다시 돌아 왔어요

. 위의 솔루션이 작동하는 동안 나는 그것을 사용하고 있으며, 실제로 Nader의 솔루션이 최고라고 생각하고 내가 제시 한 솔루션보다 먼저 고려해야합니다. 사실, 나는 오늘 그를 +1하고있다! 나는 그의 대답을 정말로 좋아한다. 그리고 내가 현재 프로젝트를 리팩토링 할 기회가 있다면, 나는 코드를 단순화하고 더 쉽게 만들 수있는 상속에서 큰 이익을 얻을 수있는 객체에 대한 직렬화 로직과 비즈니스 객체를 분리 할 것이다. 다른 사람들이 사용하고 이해할 수 있도록

답장을 보내 주셔서 감사합니다. 많은 사람들이 매우 유익하고 유용하다고 생각합니다.

2

이 게시물은 꽤 오래되었지만 최근에 WCF에서 비슷한 문제가 있었고 위의 Steve Cooper와 비슷한 해결책을 찾았습니다.하지만 작동하는 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; } 
} 

이것은 es가 아닙니다. 결과적으로 XML을 검사하기보다는 모든 것을 컴파일 타임에 구현할 수 있는지 확인할 수 있다는 것을 의미합니다. 나는 원래 SerializableBase의 이러한 추상 속성을 만들었지 만, 처음에는 기본 클래스를 사용하여 직렬화합니다. 이제는 논리가 실현됩니다. 이하지 않는 경우, 그래서 난 단지 WCF에서이 테스트 한

public class SerializableObject : SerializableBase, ISerializableObjectEnsureProperties 

및 컴파일하지 않고 XML 직렬화에 대한 개념을 이식 한 :

는 위의 한 줄을 변경하여 일반적인 방법으로 호출 직장, 사과,하지만 같은 방식으로 행동 할 것으로 기대합니다 - 나는 누군가가 내게 알리지 않을 것이라고 확신합니다 ...

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> 

희망이 많은 다른 사람을 도움이!

+0

이것은 실제로 작동합니다. 그러나 Property2가 속하지 않는 클래스에 Property2를 추가했습니다 (xml에 표시되지 않을 수도 있음). – Ian1971