다음을 통해 공유


Serialization 및 Deserialization

WCF(Windows Communication Foundation)에는 새로운 serialization 엔진인 DataContractSerializer가 포함되어 있습니다. DataContractSerializer는 .NET Framework 개체를 XML로, XML을 .NET Framework 개체로 변환합니다. 이 항목에서는 serializer가 작동하는 방식에 대해 설명합니다.

.NET Framework 개체를 serialize할 때 serializer에서는 새 데이터 계약 모델을 포함하여 다양한 serialization 프로그래밍 모델을 인식합니다. 지원되는 형식의 전체 목록은 데이터 계약 Serializer에서 지원하는 형식을 참조하십시오. 데이터 계약에 대한 소개는 데이터 계약 사용을 참조하십시오.

XML을 deserialize할 때 serializer에서는 XmlReaderXmlWriter 클래스를 사용합니다. 또한 XmlDictionaryReaderXmlDictionaryWriter 클래스를 지원하여 WCF 이진 XML 형식을 사용하는 경우와 같은 일부 경우에 최적화된 XML을 생성할 수 있습니다.

또한 WCF에는 도우미 serializer인 NetDataContractSerializer가 있습니다. NetDataContractSerializer는 .NET Framework 형식 이름을 serialize된 데이터의 일부로 내보내므로 BinaryFormatterSoapFormatter serializer와 유사합니다. 그리고 serialize 측과 deserialize 측에서 동일한 형식을 공유할 때 사용됩니다. DataContractSerializerNetDataContractSerializer는 모두 공통 기본 클래스인 XmlObjectSerializer에서 파생됩니다.

DataContractSerializer 인스턴스 만들기

DataContractSerializer의 인스턴스를 구성하는 작업은 중요한 단계입니다. 구성한 후에는 어떤 설정도 변경할 수 없습니다.

루트 형식 지정

루트 형식은 serialize 또는 deserialize되는 인스턴스의 형식입니다. DataContractSerializer에는 여러 개의 생성자 오버로드가 있지만, 최소한 type 매개 변수를 사용하여 루트 형식을 지정해야 합니다.

특정 루트 형식에 대해 만들어진 serializer는 해당 형식이 루트 형식에서 파생된 경우가 아니라면 다른 형식을 serialize하거나 deserialize하는 데 사용할 수 없습니다. 다음 예제에서는 두 개의 클래스가 나옵니다.

이 코드에서는 Person 클래스의 인스턴스를 serialize하거나 deserialize하는 데에만 사용할 수 있는 DataContractSerializer의 인스턴스를 구성합니다.

알려진 형식 지정

Serialize되는 형식 중 KnownTypeAttribute 특성이나 일부 다른 메커니즘을 사용하여 아직 처리되지 않은 형식에 다형성이 포함되는 경우, 알려진 가능한 형식 목록을 knownTypes 매개 변수를 사용하여 serializer의 생성자에게 전달해야 합니다. 알려진 형식에 대한 자세한 내용은 데이터 계약 알려진 형식을 참조하십시오.

다음 예제에서는 특정 형식의 컬렉션인 LibraryItem이 포함된 클래스 LibraryPatron을 보여 줍니다. 두 번째 클래스는 LibraryItem 형식을 정의합니다. 세 번째와 네 번째 클래스인 BookNewspaperLibraryItem 클래스에서 상속됩니다.

다음 코드에서는 knownTypes 매개 변수를 사용하여 serializer의 인스턴스를 구성합니다.

기본 루트 이름 및 네임스페이스 지정

일반적으로 개체가 serialize되면 데이터 계약 이름과 네임스페이스에 따라 가장 바깥쪽 XML 요소의 기본 이름과 네임스페이스가 결정됩니다. 모든 내부 요소의 이름은 데이터 멤버 이름을 따르며 해당 네임스페이스는 데이터 계약의 네임스페이스와 동일합니다. 다음 예제에서는 DataContractAttributeDataMemberAttribute 클래스의 생성자에 NameNamespace 값을 설정합니다.

Person 클래스의 인스턴스를 serialize하면 다음과 유사한 XML이 생성됩니다.

<PersonContract xmlns="http://schemas.contoso.com">
  <AddressMember>
    <StreetMember>123 Main Street</StreetMember>
   </AddressMember>
</PersonContract>

그러나 rootNamerootNamespace 매개 변수의 값을 DataContractSerializer 생성자에 전달하여 루트 요소의 기본 이름과 네임스페이스를 사용자 지정할 수 있습니다. rootNamespace는 데이터 멤버에 해당하는 포함된 요소의 네임스페이스에 영향을 주지 않고, 가장 바깥쪽 요소의 네임스페이스에만 영향을 줍니다.

이러한 값은 XmlDictionaryString 클래스의 인스턴스나 문자열로 전달하여, 이진 XML 형식을 통해 최적화할 수 있습니다.

최대 개체 할당량 설정

일부 DataContractSerializer 생성자 오버로드에는 maxItemsInObjectGraph 매개 변수가 있습니다. 이 매개 변수는 serializer가 단일 ReadObject 메서드 호출에서 serialize하거나 deserialize하는 최대 개체 수를 결정합니다. 이 메서드는 항상 하나의 루트 개체를 읽지만 이 개체의 데이터 멤버에 다른 개체가 있을 수 있습니다. 마찬가지로 그러한 개체에도 다른 개체가 있을 수 있습니다. 기본값은 65536입니다. 배열을 serialize하거나 deserialize할 때 모든 배열 항목은 개별 개체로 계산됩니다. 또한 일부 개체에는 큰 메모리 표현이 있을 수 있으므로 이 할당량만으로 서비스 거부 공격을 방지하지 못할 수 있습니다. 자세한 내용은 데이터에 대한 보안 고려 사항을 참조하십시오. 이 할당량을 기본값보다 크게 늘려야 할 경우 데이터를 읽고 쓸 때 보내는 측(serialize)과 받는 측(deserialize)에 모두 적용되므로 양측에서 값을 늘려야 합니다.

라운드트립

라운드트립은 개체가 한 번에 deserialize 및 다시 serialize될 때 발생합니다. 예를 들면 XML에서 개체 인스턴스로 이동하고 다시 XML 스트림으로 이동합니다.

일부 DataContractSerializer 생성자 오버로드에는 기본적으로 false로 설정된 ignoreExtensionDataObject 매개 변수가 있습니다. 이 기본 모드에서는 데이터 계약에서 IExtensibleDataObject 인터페이스를 구현하는 한, 라운드트립 시 이전 버전을 통해 최신 버전의 데이터 계약에서 다시 이 최신 버전으로 데이터 손실 없이 데이터를 보낼 수 있습니다. 예를 들어 Person 데이터 계약의 버전 1에 NamePhoneNumber 데이터 멤버가 들어 있고 버전 2에서 Nickname 멤버를 추가한다고 가정합니다. IExtensibleDataObject가 구현되면, 버전 2에서 버전 1로 정보를 보낼 때 Nickname 데이터를 저장하고 데이터가 다시 serialize될 때 다시 내보냅니다. 따라서 라운드트립 시 데이터가 손실되지 않습니다. 자세한 내용은 이후 버전과 호환되는 데이터 계약데이터 계약 버전 관리를 참조하십시오.

라운드트립과 관련된 보안 및 스키마 유효성 검사 문제

라운드트립 시 보안 관련 문제가 있을 수 있습니다. 예를 들어 잘못 사용된 엄청난 양의 데이터를 deserialize하고 저장하면 보안상 위험이 따를 수 있습니다. 특히 디지털 서명이 포함된 경우 확인할 방법이 없는 이러한 데이터를 다시 내보낼 경우 보안 문제가 있을 수 있습니다. 예를 들면 이전 시나리오의 경우 버전 1 끝점에서 악의적인 데이터가 포함된 Nickname 값을 서명할 수 있습니다. 마지막으로 스키마 유효성 검사 문제가 있을 수 있는데, 끝점에서 명시된 계약을 엄격히 준수하는 데이터를 항상 내보내고 다른 값은 내보내지 않을 수 있습니다. 이전 예제의 경우 버전 1 끝점 계약에는 NamePhoneNumber만 내보낸다고 명시되어 있는데, 스키마 유효성 검사를 사용 중일 때 추가 Nickname 값을 내보내면 유효성 검사에 실패하게 됩니다.

라운드트립 사용 및 사용 안 함

라운드트립을 해제하려면 IExtensibleDataObject 인터페이스를 구현하지 않도록 합니다. 형식에 대한 제어 권한이 없을 경우 ignoreExtensionDataObject 매개 변수를 true로 설정하여 동일한 결과를 얻을 수 있습니다.

개체 그래프 유지

다음 코드에서처럼 serializer는 일반적으로 개체 ID를 고려하지 않습니다.

다음 코드에서는 구매 주문서를 만듭니다.

billToshipTo 필드는 동일한 개체 인스턴스에 설정됩니다. 그러나 생성된 XML은 복제된 정보를 복제하는데, 다음과 유사한 XML이 생성됩니다.

<PurchaseOrder>
  <billTo><street>123 Main St.</street></billTo>
  <shipTo><street>123 Main St.</street></shipTo>
</PurchaseOrder>

그러나 이 방법은 다음과 같이 바람직하지 않은 특성을 갖습니다.

  • 성능. 데이터 복제가 비효율적입니다.
  • 순환 참조. 개체가 다른 개체를 통해 자신을 참조하는 경우에도, 복제로 serialize하면 무한 루프가 발생합니다. 이 경우 serializer가 SerializationException을 throw합니다.
  • 의미. 경우에 따라 두 개의 참조가 똑같은 두 개의 개체로 이루어지는 것이 아니라, 하나의 동일한 개체로 이루어지도록 하는 것이 중요합니다.

이러한 이유로 인해, 일부 DataContractSerializer 생성자 오버로드에는 기본값이 falsepreserveObjectReferences 매개 변수가 있습니다. 이 매개 변수를 true로 설정하면, WCF에서만 인식하는 개체 참조를 인코딩하는 특수한 방법이 사용됩니다. true로 설정된 경우 XML 코드 예제는 이제 다음과 유사합니다.

<PurchaseOrder ser:id="1">
  <billTo ser:id="2"><street ser:id="3">123 Main St.</street></billTo>
  <shipTo ser:ref="2"/>
</PurchaseOrder>

"ser" 네임스페이스는 표준 serialization 네임스페이스(https://schemas.microsoft.com/2003/10/Serialization/)를 참조합니다. 각 데이터 부분은 단 한 번만 serialize되고 이 데이터에 ID 번호가 제공됩니다. 이후 사용에서는 이미 serialize된 데이터를 참조하게 됩니다.

데이터 계약 XMLElement에 "id" 및 "ref" 특성이 모두 있으면 "ref" 특성이 적용되는 "id" 특성은 무시됩니다.

이 모드의 제한 사항을 파악하는 것도 중요합니다.

  • preserveObjectReferencestrue로 설정된 상태에서, DataContractSerializer에서 생성한 XML은 다른 기술과 상호 운용할 수 없고, 또한 preserveObjectReferencestrue로 설정된 상태에서는 다른 DataContractSerializer 인스턴스에서만 이 XML에 액세스할 수 있습니다.
  • 이 기능에 대해서는 메타데이터(스키마)가 지원되지 않습니다. 생성된 스키마는 preserveObjectReferencesfalse로 설정된 경우에만 유효합니다.
  • 이 기능으로 인해 serialization 및 deserialization 프로세스 실행 속도가 느려질 수 있습니다. 데이터를 복제하지 않아도 되지만 이 모드에서 추가 개체 비교를 수행해야 합니다.

경고

preserveObjectReferences 모드를 사용하는 경우, maxItemsInObjectGraph 값을 올바른 할당량으로 설정하는 것이 특히 중요합니다. 이 모드에서 배열이 처리되는 방식 때문에, 공격자가 maxItemsInObjectGraph 할당량에 의해서만 제한되는 과다한 메모리 소비를 일으키는 작은 악의적인 메시지를 쉽게 작성할 수 있습니다.

데이터 계약 서로게이트 지정

일부 DataContractSerializer 생성자 오버로드에는 null로 설정될 수 있는 dataContractSurrogate 매개 변수가 있습니다. 그렇지 않으면 이 오버로드를 사용하여 IDataContractSurrogate 인터페이스를 구현하는 형식인 데이터 계약 서로게이트를 지정할 수 있습니다. 그러면 인터페이스를 사용하여 serialization 및 deserialization 프로세스를 사용자 지정할 수 있습니다. 자세한 내용은 데이터 계약 서로게이트를 참조하십시오.

Serialization

다음과 같은 정보는 DataContractSerializerNetDataContractSerializer 클래스를 포함하여, XmlObjectSerializer에서 상속되는 모든 클래스에 적용됩니다.

간단한 Serialization

개체를 serialize하는 가장 기본적인 방법은 개체를 WriteObject 메서드에 전달하는 것입니다. 그리고 세 개의 오버로드가 있는데, 각각 Stream, XmlWriter 또는 XmlDictionaryWriter에 쓰기 위한 오버로드입니다. Stream 오버로드를 사용하면 UTF-8 인코딩 형식의 XML로 출력됩니다. XmlDictionaryWriter 오버로드를 사용하면 serializer는 이진 XML에 대한 출력을 최적화합니다.

WriteObject 메서드를 사용할 때, serializer는 래퍼 요소에 기본 이름 및 네임스페이스를 사용하고, 콘텐츠와 함께 래퍼 요소를 작성합니다. 이전 단원인 "기본 루트 이름 및 네임스페이스 지정"을 참조하십시오.

다음 예제에서는 XmlDictionaryWriter를 사용하여 작성하는 방법에 대해 설명합니다.

이렇게 하면 다음과 유사한 XML이 생성됩니다.

<Person>
  <Name>Jay Hamlin</Name>
  <Address>123 Main St.</Address>
</Person>

단계별 Serialization

각각 끝 요소를 작성하고 개체 내용을 작성하며 래퍼 요소를 닫으려면, WriteStartObject, WriteObjectContentWriteEndObject 메서드를 사용합니다.

참고

이러한 메서드의 Stream 오버로드가 없습니다.

이러한 단계별 serialization을 사용하는 방법은 일반적으로 두 가지가 있습니다. 한 가지 방법은 다음 예제와 같이, 특성 또는 설명 등의 내용을 WriteStartObjectWriteObjectContent 사이에 삽입하는 방법입니다.

이렇게 하면 다음과 유사한 XML이 생성됩니다.

<Person serializedBy="myCode">
  <Name>Jay Hamlin</Name>
  <Address>123 Main St.</Address>
</Person>

또 다른 일반적인 방법은 다음 코드에서처럼, WriteStartObjectWriteEndObject를 전혀 사용하지 않고 사용자 지정 래퍼 요소를 작성하거나 래퍼 작성도 함께 건너뛰는 방법입니다.

이렇게 하면 다음과 유사한 XML이 생성됩니다.

<MyCustomWrapper>
  <Name>Jay Hamlin</Name>
  <Address>123 Main St.</Address>
</MyCustomWrapper>

참고

단계별 serialization을 사용하면 스키마에 유효하지 않은 XML이 생성될 수 있습니다.

Deserialization

다음과 같은 정보는 DataContractSerializerNetDataContractSerializer 클래스를 포함하여, XmlObjectSerializer에서 상속되는 모든 클래스에 적용됩니다.

개체를 deserialize하는 가장 기본적인 방법은 ReadObject 메서드 오버로드 중 하나를 호출하는 것입니다. 그리고 세 개의 오버로드가 있는데, 이 각 오버로드는 XmlDictionaryReader, XmlReader 또는 Stream을 사용하여 읽기 위한 오버로드입니다. Stream 오버로드는 할당량으로 보호되지 않고 신뢰할 수 있는 데이터를 읽는 데만 사용해야 하는 텍스트 XmlDictionaryReader를 만듭니다.

또한 ReadObject 메서드가 반환하는 개체는 적합한 형식으로 캐스팅되어야 합니다.

다음 코드에서는 XmlDictionaryReaderDataContractSerializer의 인스턴스를 구성하고 Person 인스턴스를 deserialize합니다.

ReadObject 메서드를 호출하기 전에, 래퍼 요소나 래퍼 요소 앞에 오는 비콘텐츠 노드에 XML 판독기를 배치합니다. 이렇게 하려면 다음 코드에서처럼, XmlReaderRead 메서드 또는 해당 파생 항목을 호출하고 NodeType을 테스트하면 됩니다.

ReadObject로 판독기를 전달하기 전에 이 래퍼 요소의 특성을 읽을 수 있습니다.

간단한 ReadObject 오버로드 중 하나를 사용할 때, deserializer는 래퍼 요소의 기본 이름과 네임스페이스를 찾고(이전 단원 "기본 루트 이름 및 네임스페이스 지정" 참조) 알 수 없는 요소를 찾은 경우 예외를 throw합니다. 이전 예에서는 <Person> 래퍼 요소가 필요합니다. 판독기가 명명된 요소에 예상한 대로 배치되었는지 확인하기 위해 IsStartObject 메서드가 호출됩니다.

이 래퍼 요소 이름을 확인하지 않도록 설정하는 방법이 있습니다. ReadObject 메서드의 일부 오버로드는 기본적으로 true로 설정된 부울 매개 변수 verifyObjectName을 가져옵니다. false로 설정하면 래퍼 요소의 이름과 네임스페이스를 무시합니다. 이는 이전에 설명한 단계별 serialization 메커니즘을 사용하여 작성된 XML을 읽을 때 유용합니다.

NetDataContractSerializer 사용

DataContractSerializerNetDataContractSerializer의 주요 차이점은 DataContractSerializer는 데이터 계약 이름을 사용하는 반면, NetDataContractSerializer는 serialize된 XML로 전체 .NET Framework 어셈블리와 형식 이름을 출력합니다. 즉, 정확히 동일한 형식은 serialization 끝점과 deserialization 끝점 간에 공유되어야 합니다. 다시 말하면 deserialize할 정확한 형식을 이미 알고 있으므로 알려진 형식 메커니즘은 NetDataContractSerializer에 필요하지 않습니다.

그러나 여러 가지 문제가 발생할 수 있습니다.

  • 보안. XML에 deserialize되고 있는 모든 형식이 로드됩니다. 이를 악용해 악의적인 형식이 로드될 수 있습니다. Binder 속성 또는 생성자 매개 변수로 Serialization 바인더를 사용하는 경우에만, 신뢰할 수 없는 데이터에 NetDataContractSerializer를 사용해야 합니다. 바인더는 안전한 형식만 로드할 수 있도록 허용합니다. 바인더 메커니즘은 System.Runtime.Serialization 네임스페이스의 형식에서 사용하는 메커니즘과 동일합니다.
  • 버전 관리. XML에 전체 형식 및 어셈블리 이름을 사용하면 형식 버전을 관리할 수 있는 방식이 제한됩니다. 형식 이름, 네임스페이스, 어셈블리 이름 및 어셈블리 버전은 변경할 수 없습니다. AssemblyFormat 속성이나 생성자 매개 변수를 Full의 기본값 대신 Simple로 설정하면, 어셈블리 버전을 변경할 수 있지만 일반 매개 변수의 형식 버전은 변경할 수 없습니다.
  • 상호 운용성. .NET Framework 형식 및 어셈블리 이름은 XML에 포함되어 있기 때문에 .NET Framework 이외의 다른 플랫폼에서는 결과 데이터에 액세스할 수 없습니다.
  • 성능. 형식 및 어셈블리 이름을 작성하면 생성되는 XML의 크기가 크게 늘어납니다.

이 메커니즘은 .NET Framework 원격 서비스에서 사용하는 이진 또는 SOAP serialization과 비슷합니다(특히 BinaryFormatterSoapFormatter).

NetDataContractSerializer 사용 방식은 DataContractSerializer 사용 방식과 비슷하지만 다음과 같은 차이점이 있습니다.

  • 생성자를 사용하기 위해 루트 형식을 지정하지 않아도 됩니다. NetDataContractSerializer의 같은 인스턴스를 사용하여 형식을 serialize할 수 있습니다.
  • 생성자는 알려진 형식의 목록을 허용하지 않습니다. 형식 이름이 XML로 serialize되는 경우 알려진 형식 메커니즘은 필요하지 않습니다.
  • 생성자는 데이터 계약 서로게이트를 허용하지 않습니다. 대신 SurrogateSelector 속성에 매핑되는 surrogateSelector라고 하는 ISurrogateSelector 매개 변수를 허용합니다. 이것이 바로 레거시 서로게이트 메커니즘입니다.
  • 생성자는 AssemblyFormat 속성에 매핑되는 FormatterAssemblyStyleassemblyFormat이라는 매개 변수를 허용합니다. 이전에 설명한 대로 이 메커니즘은 serializer의 버전 관리 기능을 향상시키는 데 사용할 수 있으며, 이진 또는 SOAP serialization의 FormatterAssemblyStyle 메커니즘과 동일합니다.
  • 생성자는 Context 속성에 매핑되는 context라는 StreamingContext 매개 변수를 허용합니다. 이 메커니즘은 정보를 serialize되고 있는 형식으로 전달할 수 있으며, 이 사용 방법은 다른 System.Runtime.Serialization 클래스에 사용된 StreamingContext 메커니즘의 사용 방법과 동일합니다.
  • SerializeDeserialize 메서드는 WriteObjectReadObject 메서드의 별칭입니다. 이러한 메서드는 이진 또는 SOAP serialization에 보다 일관된 프로그래밍 모델을 제공하기 위해 존재합니다.

이러한 기능에 대한 자세한 내용은 Binary Serialization을 참조하십시오.

NetDataContractSerializerDataContractSerializer에서 사용하는 XML 형식은 일반적으로 호환되지 않습니다. 즉, 이러한 serializer 중 하나로 serialize하고 다른 serializer로 deserialize를 시도할 수 없습니다.

또한 NetDataContractSerializer는 개체 그래프에 각 노드에 대한 전체 .NET Framework 형식 및 어셈블리 이름을 출력하지 않습니다. 모호한 정보만을 출력하는데, 즉, 루트 개체 수준에서 다형적 경우에 대해 출력합니다.

참고 항목

참조

DataContractSerializer
NetDataContractSerializer
XmlObjectSerializer

개념

데이터 계약 Serializer에서 지원하는 형식

기타 리소스

Binary Serialization