DataContract Surrogate
This sample demonstrates how processes like serialization, deserialization, schema export, and schema import can be customized using a data contract surrogate class. This sample shows how to use a surrogate in a client and server scenario where data is serialized and transmitted between a Windows Communication Foundation (WCF) client and service.
Note
The setup procedure and build instructions for this sample are located at the end of this topic.
The sample uses the following service contract:
[ServiceContract(Namespace = "http://Microsoft.ServiceModel.Samples")]
[AllowNonSerializableTypes]
public interface IPersonnelDataService
{
[OperationContract]
void AddEmployee(Employee employee);
[OperationContract]
Employee GetEmployee(string name);
}
Note
The AllowNonSerializableTypes attribute is a custom attribute that is defined in the sample and is not a part of the .NET Framework.
The AddEmployee
operation allows users to add data about new employees and the GetEmployee
operation supports search for employees based on name.
These operations use the following data type:
[DataContract(Namespace = "http://Microsoft.ServiceModel.Samples")]
class Employee
{
[DataMember]
public DateTime dateHired;
[DataMember]
public Decimal salary;
[DataMember]
public Person person;
}
In the Employee
type, the Person
class (shown in the following sample code) cannot be serialized by the DataContractSerializer because it is not a valid data contract class.
public class Person
{
public string firstName;
public string lastName;
public int age;
public Person() { }
}
You can apply the DataContract
attribute to the Person
class, but this is not always possible. For example, the Person
class can be defined in a separate assembly over which you have no control.
Given this restriction, one way to serialize the Person
class is to substitute it with another class that is marked with DataContractAttribute
and copy over necessary data to the new class. The objective is to make the Person
class appear as a DataContract to the DataContractSerializer. Note that this is one way to serialize non-data contract classes.
The sample logically replaces the Person
class with a different class named PersonSurrogated
.
[DataContract(Name="Person", Namespace = "http://Microsoft.ServiceModel.Samples")]
public class PersonSurrogated
{
[DataMember]
public string FirstName;
[DataMember]
public string LastName;
[DataMember]
public int Age;
}
The data contract surrogate is used to achieve this replacement. A data contract surrogate is a class that implements IDataContractSurrogate. In this sample, the AllowNonSerializableTypesSurrogate
class implements this interface.
In the interface implementation, the first task is to establish a type mapping from Person
to PersonSurrogated
. This is used both at serialization time as well as at schema export time. This mapping is achieved by implementing the GetDataContractType method.
public Type GetDataContractType(Type type)
{
if (typeof(Person).IsAssignableFrom(type))
{
return typeof(PersonSurrogated);
}
return type;
}
The GetObjectToSerialize method maps a Person
instance to a PersonSurrogated
instance during serialization, as shown in the following sample code.
public object GetObjectToSerialize(object obj, Type targetType)
{
if (obj is Person)
{
Person person = (Person)obj;
PersonSurrogated personSurrogated = new PersonSurrogated();
personSurrogated.FirstName = person.firstName;
personSurrogated.LastName = person.lastName;
personSurrogated.Age = person.age;
return personSurrogated;
}
return obj;
}
The GetDeserializedObject method provides the reverse mapping for deserialization, as shown in the following sample code.
public object GetDeserializedObject(object obj,
Type targetType)
{
if (obj is PersonSurrogated)
{
PersonSurrogated personSurrogated = (PersonSurrogated)obj;
Person person = new Person();
person.firstName = personSurrogated.FirstName;
person.lastName = personSurrogated.LastName;
person.age = personSurrogated.Age;
return person;
}
return obj;
}
To map the PersonSurrogated
data contract to the existing Person
class during schema import, the sample implements the GetReferencedTypeOnImport method, as shown in the following sample code.
public Type GetReferencedTypeOnImport(string typeName,
string typeNamespace, object customData)
{
if (
typeNamespace.Equals("http://schemas.datacontract.org/2004/07/DCSurrogateSample")
)
{
if (typeName.Equals("PersonSurrogated"))
{
return typeof(Person);
}
}
return null;
}
The following sample code completes the implementation of the IDataContractSurrogate interface.
public System.CodeDom.CodeTypeDeclaration ProcessImportedType(
System.CodeDom.CodeTypeDeclaration typeDeclaration,
System.CodeDom.CodeCompileUnit compileUnit)
{
return typeDeclaration;
}
public object GetCustomDataToExport(Type clrType,
Type dataContractType)
{
return null;
}
public object GetCustomDataToExport(
System.Reflection.MemberInfo memberInfo, Type dataContractType)
{
return null;
}
public void GetKnownCustomDataTypes(
KnownTypeCollection customDataTypes)
{
// It does not matter what we do here.
throw new NotImplementedException();
}
In this sample, the surrogate is enabled in ServiceModel by an attribute called AllowNonSerializableTypesAttribute
. Developers would need to apply this attribute on their service contract as shown on the IPersonnelDataService
service contract above. This attribute implements IContractBehavior
and sets up the surrogate on operations in its ApplyClientBehavior
and ApplyDispatchBehavior
methods.
The attribute is not necessary in this case - it is used for demonstration purposes in this sample. Users can alternatively enable a surrogate by manually adding a similar IContractBehavior
, IEndpointBehavior
or IOperationBehavior
using code or using configuration.
The IContractBehavior
implementation looks for operations that use DataContract by checking if they have a DataContractSerializerOperationBehavior
registered. If they do, it sets the DataContractSurrogate
property on that behavior. The following sample code shows how this is done. Setting the surrogate on this operation behavior enables it for serialization and deserialization.
public void ApplyClientBehavior(ContractDescription description, ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.ClientRuntime proxy)
{
foreach (OperationDescription opDesc in description.Operations)
{
ApplyDataContractSurrogate(opDesc);
}
}
public void ApplyDispatchBehavior(ContractDescription description, ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.DispatchRuntime dispatch)
{
foreach (OperationDescription opDesc in description.Operations)
{
ApplyDataContractSurrogate(opDesc);
}
}
private static void ApplyDataContractSurrogate(OperationDescription description)
{
DataContractSerializerOperationBehavior dcsOperationBehavior = description.Behaviors.Find<DataContractSerializerOperationBehavior>();
if (dcsOperationBehavior != null)
{
if (dcsOperationBehavior.DataContractSurrogate == null)
dcsOperationBehavior.DataContractSurrogate = new AllowNonSerializableTypesSurrogate();
}
}
Additional steps need to be taken to plug in the surrogate for use during metadata generation. One mechanism to do this is to provide an IWsdlExportExtension
which is what this sample demonstrates. Another way is to modify the WsdlExporter
directly.
The AllowNonSerializableTypesAttribute
attribute implements IWsdlExportExtension
and IContractBehavior
. The extension can be either an IContractBehavior
or IEndpointBehavior
in this case. Its IWsdlExportExtension.ExportContract
method implementation enables the surrogate by adding it to the XsdDataContractExporter
used during schema generation for DataContract. The following code snippet shows how to do this.
public void ExportContract(WsdlExporter exporter, WsdlContractConversionContext context)
{
if (exporter == null)
throw new ArgumentNullException("exporter");
object dataContractExporter;
XsdDataContractExporter xsdDCExporter;
if (!exporter.State.TryGetValue(typeof(XsdDataContractExporter), out dataContractExporter))
{
xsdDCExporter = new XsdDataContractExporter(exporter.GeneratedXmlSchemas);
exporter.State.Add(typeof(XsdDataContractExporter), xsdDCExporter);
}
else
{
xsdDCExporter = (XsdDataContractExporter)dataContractExporter;
}
if (xsdDCExporter.Options == null)
xsdDCExporter.Options = new ExportOptions();
if (xsdDCExporter.Options.DataContractSurrogate == null)
xsdDCExporter.Options.DataContractSurrogate = new AllowNonSerializableTypesSurrogate();
}
When you run the sample, the client calls AddEmployee followed by a GetEmployee call to check if the first call was successful. The result of the GetEmployee operation request is displayed in the client console window. The GetEmployee operation must succeed in finding the employee and print “found”.
Note
This sample shows how to plug in a surrogate for serialize, deserialize and metadata generation. It does not show how to plug in a surrogate for code generation from metadata. To see a sample of how a surrogate can be used to plug into client code generation, see the Custom WSDL Publication sample.
To set up, build, and run the sample
Ensure that you have performed the One-Time Setup Procedure for the Windows Communication Foundation Samples.
To build the C# edition of the solution, follow the instructions in Building the Windows Communication Foundation Samples.
To run the sample in a single- or cross-machine configuration, follow the instructions in Running the Windows Communication Foundation Samples.
Note: |
---|
The samples may already be installed on your machine. Check for the following (default) directory before continuing.
<InstallDrive>:\WF_WCF_Samples
If this directory does not exist, go to Windows Communication Foundation (WCF) and Windows Workflow Foundation (WF) Samples for .NET Framework 4 to download all Windows Communication Foundation (WCF) and WF samples. This sample is located in the following directory.
<InstallDrive>:\WF_WCF_Samples\WCF\Extensibility\DataContract
|
Build Date: 2011-08-19