Суррогат DataContract
В примере DataContract показано, как процессы, такие как сериализация, десериализация, экспорт схемы и импорт схемы, можно настроить с помощью суррогатного класса контракта данных. В этом примере показано, как использовать суррогат в сценарии клиента и сервера, где данные сериализуются и передаются между клиентом и службой Windows Communication Foundation (WCF).
Примечание.
Процедура настройки и инструкции по построению для данного образца приведены в конце этого раздела.
В этом образце используется следующий контракт службы.
[ServiceContract(Namespace = "http://Microsoft.ServiceModel.Samples")]
[AllowNonSerializableTypes]
public interface IPersonnelDataService
{
[OperationContract]
void AddEmployee(Employee employee);
[OperationContract]
Employee GetEmployee(string name);
}
Операция AddEmployee
позволяет пользователям добавлять данные о новых сотрудниках, а операция GetEmployee
поддерживает поиск сотрудников по имени.
В этих операциях используется следующий тип данных.
[DataContract(Namespace = "http://Microsoft.ServiceModel.Samples")]
class Employee
{
[DataMember]
public DateTime dateHired;
[DataMember]
public Decimal salary;
[DataMember]
public Person person;
}
В типе Employee
класс Person
(показан в следующем образце кода) не может быть сериализован с помощью DataContractSerializer, поскольку он не является допустимым классом контракта данных.
public class Person
{
public string firstName;
public string lastName;
public int age;
public Person() { }
}
Можно применить атрибут DataContractAttribute к классу Person
, но это не всегда возможно. Например, класс Person
может быть определен в отдельной сборке, управлять которой невозможно.
С учетом этого ограничения одним из способов сериализации класса Person
является его замена на другой класс, отмеченный атрибутом DataContractAttribute, и копирование необходимых данных в новый класс. Целью является отображение класса Person
как DataContract в сериализаторе DataContractSerializer. Обратите внимание, что это один способ сериализации классов, отличных от классов контракта данных.
В этом образце класс Person
логически заменяется на другой класс с именем PersonSurrogated
.
[DataContract(Name="Person", Namespace = "http://Microsoft.ServiceModel.Samples")]
public class PersonSurrogated
{
[DataMember]
public string FirstName;
[DataMember]
public string LastName;
[DataMember]
public int Age;
}
Заменяющий класс контракта данных используется для обеспечения такой замены. Заменяющий класс контракта данных является классом, который реализует IDataContractSurrogate. В этом образце класс AllowNonSerializableTypesSurrogate
реализует этот интерфейс.
В реализации интерфейса первой задачей является установка сопоставления типов из класса Person
с классом PersonSurrogated
. Используется как во время сериализации, так и во время экспорта схемы. Такое сопоставление обеспечивается путем реализации метода GetDataContractType(Type).
public Type GetDataContractType(Type type)
{
if (typeof(Person).IsAssignableFrom(type))
{
return typeof(PersonSurrogated);
}
return type;
}
Метод GetObjectToSerialize(Object, Type) сопоставляет экземпляр класса Person
с экземпляром класса PersonSurrogated
во время сериализации, как показано в следующем образце кода.
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;
}
Метод GetDeserializedObject(Object, Type) обеспечивает обратное сопоставление для десериализации, как показано в следующем образце кода.
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;
}
Чтобы сопоставить контракт данных PersonSurrogated
с существующим классом Person
во время импорта схемы, в образце реализуется метод GetReferencedTypeOnImport(String, String, Object), как показано в следующем образце кода.
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;
}
В следующем образце кода показано завершение реализации интерфейса IDataContractSurrogate.
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();
}
В этом образце заменяющий класс включается в ServiceModel с помощью атрибута AllowNonSerializableTypesAttribute
. Разработчикам пришлось бы применить этот атрибут к контракту службы, как показано в контракте службы IPersonnelDataService
выше. Этот атрибут реализует IContractBehavior
и настраивает заменяющий класс в операциях методов ApplyClientBehavior
и ApplyDispatchBehavior
.
В этом случае необходимости в атрибуте нет. В этом образце он используется в демонстрационных целях. Пользователи могут также включить заменяющий класс вручную, добавив похожее поведение IContractBehavior
, IEndpointBehavior
или IOperationBehavior
с использованием кода или конфигурации.
Реализация IContractBehavior
ищет операции, в которых используется DataContract, проверяя в них наличие зарегистрированного поведения DataContractSerializerOperationBehavior
. При его наличии она задает свойство DataContractSurrogate
для этого поведения. В следующем образце кода показано, как это можно сделать. Задание заменяющего класса для этого поведения операции позволяет ему участвовать в сериализации и десериализации.
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();
}
}
Чтобы подключить заменяющий класс для использования при создании метаданных, необходимо выполнить дополнительные действия. Одним из механизмов реализации этого является предоставление расширения IWsdlExportExtension
, как показано в этом образце. Еще одним способом является непосредственное изменение WsdlExporter
.
Атрибут AllowNonSerializableTypesAttribute
реализует IWsdlExportExtension
и IContractBehavior
. Расширение может быть либо или IContractBehavior
IEndpointBehavior
в данном случае. Реализация метода IWsdlExportExtension.ExportContract
включает заменяющий класс путем его добавления в XsdDataContractExporter
, используемый во время создания схемы для DataContract. В следующем фрагменте кода показано, как это сделать.
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();
}
При выполнении образца клиент вызывает операцию AddEmployee после вызова операции GetEmployee, чтобы проверить, выполнен ли первый вызов успешно. Результат запроса операции GetEmployee отображается в окне консоли клиента. Операция GetEmployee должна успешно найти сотрудника и распечатать "найдено".
Примечание.
В этом примере показано, как подключить заменяющий класс для сериализации, десериализации и создания метаданных. В нем не показано, как подключить заменяющий класс для создания кода из метаданных. Дополнительные сведения о том, как суррогат можно использовать для подключения к клиентскому коду, см. в примере пользовательской публикации WSDL.
Настройка, сборка и выполнение образца
Убедитесь, что вы выполнили процедуру однократной установки для примеров Windows Communication Foundation.
Чтобы создать выпуск решения на C#, следуйте инструкциям в статье "Создание примеров Windows Communication Foundation".
Чтобы запустить пример в конфигурации с одним или несколькими компьютерами, следуйте инструкциям в разделе "Примеры Windows Communication Foundation".