WPF + WCF 환경에서는 DataContract를 권장
개인적으로 WPF로 프로젝트를 진행하면서 가장 마음에 들었던 부분이라면, 바로 "데이터 바인딩"을 들 수 있겠습니다. 물론, WinForm 시절에도 가능했지만 WPF에서는 한층 더 발전된 양상을 보여주고 있기 때문입니다.
그런데, WPF 응용 프로그램에서 데이터 바인딩이 된 인스턴스를 WCF 메서드에 실어 보내다 보면 예기치 않은 문제가 발생하게 됩니다. 어디 ... 한번 살펴볼까요? ^^
예를 들어, INotifyPropertyChanged를 구현한 아래와 같은 타입이 있고,
[System.SerializableAttribute()]
public class MyData : INotifyPropertyChanged
{
private string name;
public string Name
{
get { return this.name; }
set
{
if (this.name == value)
{
return;
}
this.name = value;
OnPropertyChanged("Name");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if ((PropertyChanged != null))
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
이렇게 정의된 MyData를 이용하는 WCF 메서드가 다음과 같이 구현되어,
[ServiceContract]
public interface IServiceA
{
[OperationContract]
void SetData(MyData myData);
}
[ServiceBehavior]
public class ServiceAImp : IServiceA
{
public void SetData(MyData myData)
{
}
}
WPF 클라이언트에서는 다음과 같이 WCF 메서드를 호출하는 것이 가능하게 되지요.
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
ServiceReference1.ServiceAClient svc = new WpfApplication1.ServiceReference1.ServiceAClient();
MyData data = new MyData();
data.Name = "Test";
svc.SetData(data);
}
}
여기까지는 평범합니다. 하지만, 위의 코드에 WPF 데이터 바인딩이 더해지면 상황은 그리 녹녹치 않게 됩니다. 예를 들어 WPF 측의 코드에서 MyData data; 인스턴스를 아래와 같이 바인딩을 시켜 보면,
<TextBox Text="{Binding Path=Name}" Height="23" />
<Button Name="button1" Width="57" Click="button1_Click">Button</Button>
private void Window_Loaded(object sender, RoutedEventArgs e)
{
MyData data = new MyData();
data.Name = "Test";
this.DataContext = data;
}
private void button1_Click(object sender, RoutedEventArgs e)
{
ServiceReference1.ServiceAClient svc = new WpfApplication1.ServiceReference1.ServiceAClient();
MyData data = this.DataContext as MyData;
svc.SetData(data); <== 예외 발생
}
이번에는 svc.SetData 메서드 호출에서 아래와 같은 예외가 발생하게 됩니다.
There was an error while trying to serialize parameter http://tempuri.org/:myData.
The InnerException message was 'Type 'System.DelegateSerializationHolder+DelegateEntry'
with data contract name 'DelegateSerializationHolder.DelegateEntry:http://schemas.datacontract.org/2004/07/System' is not expected.
Add any types not known statically to the list of known types - for example,
by using the KnownTypeAttribute attribute or by adding them to the list of known types
passed to DataContractSerializer.'.
Please see InnerException for more details.
문제가 뭘까요?
그것은, [Serializable] 특성이 지정된 타입에 대한 DataContractSerializer 직렬화 방식에서 기인하게 됩니다. 이에 대한 자세한 설명은 다음의 토픽을 읽어보십시오.
Service Station - Serialization in Windows Communication Foundation
Serializing and Encoding
; https://docs.microsoft.com/en-us/archive/msdn-magazine/2006/august/service-station-serialization-in-windows-communication-foundation#S1
위의 기사에 보면 첫 번째 표에 각종 직렬화 방식에 대한 정리를 잘해 두었는데요. 편의상, 아래에 붙여놓고 설명하겠습니다.
Feature |
XmlSerializer |
DataContractSerializer NetDataContractSerializer |
Explicitness |
Opt-out |
Opt-in * Opt-out ** |
Default mapping |
Public fields/props |
All [DataMember]s * All fields ** |
Attribute required |
No |
Yes |
Default order |
Same as class |
Alphabetical |
XML Schema |
Extensive |
Constrained |
Code generator |
Xsd.exe |
SvcUtil.exe |
Override |
IXmlSerializable |
ISerializable |
Type fidelity |
No |
NetDataContractSerializer |
Versioning support |
No |
Yes |
Initialization |
Constructor |
Callbacks |
Compatibility |
ASMX |
.NET Remoting |
* Using [DataContract] ** Using [Serializable] |
위에서 강조해 놓은 부분을 곰곰이 생각해 보시면, 왜 WPF에서 데이터바인딩을 한 개체를 WCF 메서드에 실어 보낼 때 예외가 발생했는지를 알 수 있습니다. 즉, DataContractSerializer 개체는 주어진 데이터를 직렬화 할 때, "Opt-out" 원칙에 기반해서 PropertyChanged 이벤트(타입)도 직렬화 하려고 시도를 하기 때문에 오류가 발생하는 것은 어찌 보면 당연한 것입니다.
처음에는 이것을 피해가기 위해 "PropertyChanged" 이벤트에 "[NonSerialized]" 특성을 주면 어떨까 싶었는데, 불행히도 그 특성은 AttributeUsage가 AttributeTargets.Field로만 정해져 있어서 가능하지 않았습니다.
이쯤 되면... 뭐... ^^; 어쩔 수 없지요. "Opt-in" 원칙이 적용되는 DataContract 특성을 주는 수밖에.
[이 토픽에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]