Известные типы контрактов данных
Класс KnownTypeAttribute позволяет заранее задавать типы, которые следует рассматривать при десериализации. Рабочий пример см. в разделе Известные типы.
Обычно при передаче параметров и возвращаемых значений между клиентом и службой обе конечные точки совместно используют все контракты данных, относящиеся к передаваемым данным. Однако в следующих ситуациях это не так:
контракт отправляемых данных наследует контракту ожидаемых данных. Дополнительные сведения см. в разделе в подразделе, посвященном наследованию, в разделе Эквивалентность контрактов данных). В этом случае контракт передаваемых данных отличается от контракта данных, ожидаемого принимающей конечной точкой;
объявленный тип передаваемых данных является интерфейсом, а не классом, структурой или перечислением. Поэтому невозможно заранее знать, какой именно из реализуемых этим интерфейсом типов будет передан, а следовательно принимающая конечная точка не может заранее определить контракт передаваемых данных;
объявлен тип передаваемых данных Object. Поскольку каждый тип наследуется от класса Object, невозможно заранее знать, какой именно тип будет передан, а следовательно принимающая конечная точка не может заранее определить контракт передаваемых данных. Это частный случай первого варианта: каждый контракт данных наследует от контракта данных по умолчанию (пустого контракта), который создается для класса Object;
у некоторых типов, включающих типы .NET Framework, имеются члены, относящиеся к одной из указанных выше трех категорий. Например, класс Hashtable использует класс Object для хранения фактических объектов в хэш-таблице. При сериализации таких типов принимающая сторона не может заранее определить контракт данных таких членов.
Класс KnownTypeAttribute
Когда данные прибывают в принимающую конечную точку, среда выполнения WCF предпринимает попытку десериализовать данные в экземпляр типа среды CLR. Тип, экземпляр которого создается в результате десериализации, выбирается в первую очередь по результатам проверки входящего сообщения с целью определения контракта данных, которому соответствует содержимое сообщения. После этого система десериализации пытается найти тип среды CLR, который реализует контракт данных, совместимый с содержимым сообщения. Набор потенциальных типов, которые система десериализации считает допустимыми в результате выполнения этих операций, называется набором "известных типов" десериализатора.
Один из способов уведомления десериализатора о типе — использовать класс KnownTypeAttribute. Этот атрибут нельзя применять к отдельным членам, но следует применять только к целым типам контрактов данных. Атрибут применяется к внешнему типу, который может быть классом или структурой. На самом базовом уровне применение этого атрибута помечает тип в качестве "известного типа". В результате известный тип становится частью набора известных типов всякий раз, когда происходит десериализация объекта внешнего типа или любого объекта, являющегося членом известного типа. К одному и тому же типу можно применить несколько атрибутов KnownTypeAttribute.
Известные типы и примитивы
Типы-примитивы, а также некоторые типы, с которыми обращаются как с примитивами (например, DateTime и XmlElement), всегда являются "известными", и их никогда не нужно добавлять с помощью описанного механизма. Однако массивы типов-примитивов нужно добавлять в явном виде. Большинство коллекций считаются эквивалентами массивов. (Неуниверсальные коллекции считаются эквивалентами массивов Object.) Пример использования примитивов, массивов примитивов и коллекций примитивов см. в примере 4.
Примечание |
---|
В отличие от других типов-примитивов структура DateTimeOffset по умолчанию не является известным типом, поэтому ее необходимо вручную добавлять в список известных типов. |
Примеры
Ниже приведены примеры использования класса KnownTypeAttribute.
Пример 1
Имеется три класса, связанных отношениями наследования.
<DataContract()> _
Public Class Shape
End Class
<DataContract(Name:="Circle")> _
Public Class CircleType
Inherits Shape
End Class
<DataContract(Name:="Triangle")> _
Public Class TriangleType
Inherits Shape
End Class
[DataContract]
public class Shape { }
[DataContract(Name = "Circle")]
public class CircleType : Shape { }
[DataContract(Name = "Triangle")]
public class TriangleType : Shape { }
Показанный ниже класс CompanyLogo
можно сериализовать; однако он не может быть сериализован, если член ShapeOfLogo
имеет значение CircleType
или TriangleType
, поскольку система десериализации не распознает типы с именами контрактов данных "Circle" и "Triangle".
<DataContract()> _
Public Class CompanyLogo
<DataMember()> _
Private ShapeOfLogo As Shape
<DataMember()> _
Private ColorOfLogo As Integer
End Class
[DataContract]
public class CompanyLogo
{
[DataMember]
private Shape ShapeOfLogo;
[DataMember]
private int ColorOfLogo;
}
В следующем примере кода показано, как правильно описать тип CompanyLogo
.
<DataContract(), KnownType(GetType(CircleType)), KnownType(GetType(TriangleType))> _
Public Class CompanyLogo2
<DataMember()> _
Private ShapeOfLogo As Shape
<DataMember()> _
Private ColorOfLogo As Integer
End Class
[DataContract]
[KnownType(typeof(CircleType))]
[KnownType(typeof(TriangleType))]
public class CompanyLogo2
{
[DataMember]
private Shape ShapeOfLogo;
[DataMember]
private int ColorOfLogo;
}
Когда происходит десериализация внешнего типа CompanyLogo2
, система десериализации знает о типах CircleType
и TriangleType
и поэтому может найти контракты данных, соответствующие типам "Circle" и "Triangle".
Пример 2
В следующем примере, даже если оба типа CustomerTypeA
и CustomerTypeB
имеют контракт данных Customer
, при десериализации типа PurchaseOrder
будет создаваться экземпляр CustomerTypeB
, поскольку системе десериализации известен только тип CustomerTypeB
.
Public Interface ICustomerInfo
Function ReturnCustomerName() As String
End Interface
<DataContract(Name:="Customer")> _
Public Class CustomerTypeA
Implements ICustomerInfo
Public Function ReturnCustomerName() _
As String Implements ICustomerInfo.ReturnCustomerName
Return "no name"
End Function
End Class
<DataContract(Name:="Customer")> _
Public Class CustomerTypeB
Implements ICustomerInfo
Public Function ReturnCustomerName() _
As String Implements ICustomerInfo.ReturnCustomerName
Return "no name"
End Function
End Class
<DataContract(), KnownType(GetType(CustomerTypeB))> _
Public Class PurchaseOrder
<DataMember()> _
Private buyer As ICustomerInfo
<DataMember()> _
Private amount As Integer
End Class
public interface ICustomerInfo
{
string ReturnCustomerName();
}
[DataContract(Name = "Customer")]
public class CustomerTypeA : ICustomerInfo
{
public string ReturnCustomerName()
{
return "no name";
}
}
[DataContract(Name = "Customer")]
public class CustomerTypeB : ICustomerInfo
{
public string ReturnCustomerName()
{
return "no name";
}
}
[DataContract]
[KnownType(typeof(CustomerTypeB))]
public class PurchaseOrder
{
[DataMember]
ICustomerInfo buyer;
[DataMember]
int amount;
}
Пример 3
В следующем примере тип Hashtable хранит содержимое в виде Object. Для десериализации хэш-таблицы системе десериализации должен быть известен набор возможных типов, которые представляются этим типом. В данном случае нам известно, что в объекте Catalog
хранятся только объекты Book
и Magazine
, поэтому они добавляются с помощью атрибута KnownTypeAttribute.
<DataContract()> _
Public Class Book
End Class
<DataContract()> _
Public Class Magazine
End Class
<DataContract(), KnownType(GetType(Book)), KnownType(GetType(Magazine))> _
Public Class LibraryCatalog
<DataMember()> _
Private theCatalog As System.Collections.Hashtable
End Class
[DataContract]
public class Book { }
[DataContract]
public class Magazine { }
[DataContract]
[KnownType(typeof(Book))]
[KnownType(typeof(Magazine))]
public class LibraryCatalog
{
[DataMember]
System.Collections.Hashtable theCatalog;
}
Пример 4
В следующем примере контракт данных хранит число и операцию, которую следует выполнить над этим числом. Элемент данных Numbers
может быть целым числом, массивом целых чисел или объектом List, содержащим целые числа.
Внимание! |
---|
Работает только на стороне клиента, если SVCUTIL.EXE используется для формирования учетной записи-посредника WCF. SVCUTIL.EXE извлекает метаданные из службы, включая любые известные типы. Без этой информации клиент не сможет десериализовать типы. |
<DataContract(), KnownType(GetType(Integer()))> _
Public Class MathOperationData
Private numberValue As Object
<DataMember()> _
Public Property Numbers() As Object
Get
Return numberValue
End Get
Set(ByVal value As Object)
numberValue = value
End Set
End Property
End Class
[DataContract]
[KnownType(typeof(int[]))]
public class MathOperationData
{
private object numberValue;
[DataMember]
public object Numbers
{
get { return numberValue; }
set { numberValue = value; }
}
//[DataMember]
//public Operation Operation;
}
Ниже показан код приложения.
' This is in the service application code:
Shared Sub Run()
Dim md As New MathOperationData()
' This will serialize and deserialize successfully because primitive
' types like int are always known.
Dim a As Integer = 100
md.Numbers = a
' This will serialize and deserialize successfully because the array of
' integers was added to known types.
Dim b(99) As Integer
md.Numbers = b
' This will serialize and deserialize successfully because the generic
' List(Of Integer) is equivalent to Integer(), which was added to known types.
Dim c As List(Of Integer) = New List(Of Integer)()
md.Numbers = c
' This will serialize but will not deserialize successfully because
' ArrayList is a non-generic collection, which is equivalent to
' an array of type object. To make it succeed, object[]
' must be added to the known types.
Dim d As New ArrayList()
md.Numbers = d
End Sub
// This is in the service application code:
static void Run()
{
MathOperationData md = new MathOperationData();
// This will serialize and deserialize successfully because primitive
// types like int are always known.
int a = 100;
md.Numbers = a;
// This will serialize and deserialize successfully because the array of
// integers was added to known types.
int[] b = new int[100];
md.Numbers = b;
// This will serialize and deserialize successfully because the generic
// List<int> is equivalent to int[], which was added to known types.
List<int> c = new List<int>();
md.Numbers = c;
// This will serialize but will not deserialize successfully because
// ArrayList is a non-generic collection, which is equivalent to
// an array of type object. To make it succeed, object[]
// must be added to the known types.
ArrayList d = new ArrayList();
md.Numbers = d;
}
Известные типы, наследование и интерфейсы
Когда известный тип связывается с конкретным типом с помощью атрибута KnownTypeAttribute, он также связывается со всеми производными типами этого типа. См., например, следующий код.
<DataContract(), KnownType(GetType(Square)), KnownType(GetType(Circle))> _
Public Class MyDrawing
<DataMember()> _
Private Shape As Object
<DataMember()> _
Private Color As Integer
End Class
<DataContract()> _
Public Class DoubleDrawing
Inherits MyDrawing
<DataMember()> _
Private additionalShape As Object
End Class
[DataContract]
[KnownType(typeof(Square))]
[KnownType(typeof(Circle))]
public class MyDrawing
{
[DataMember]
private object Shape;
[DataMember]
private int Color;
}
[DataContract]
public class DoubleDrawing : MyDrawing
{
[DataMember]
private object additionalShape;
}
Класс DoubleDrawing
не требует, чтобы атрибут KnownTypeAttribute использовал типы Square
и Circle
в поле AdditionalShape
, поскольку эти атрибуты уже применены в базовом классе (Drawing
).
Известные типы можно связывать только с классами и структурами, но не с интерфейсами.
Известные типы и открытые универсальные методы
Может возникнуть необходимость добавить в качестве известного типа универсальный тип. Однако открытый универсальный тип невозможно передать в атрибут KnownTypeAttribute в качестве параметра.
Эту проблему можно решить с помощью альтернативного механизма: напишите метод, возвращающий типы, которые следует добавить в коллекцию известных типов. После этого имя метода задается в качестве строкового аргумента атрибута KnownTypeAttribute с некоторыми ограничениями.
Этот метод должен существовать в типе, к которому применяется атрибут KnownTypeAttribute, должен быть статическим, не должен принимать параметров и должен возвращать объект, который можно присвоить интерфейсу IEnumerable типа Type.
Нельзя объединять атрибут KnownTypeAttribute с именем метода и атрибутами KnownTypeAttribute с реальными типами в рамках одного типа. Более того, нельзя применять более одного атрибута KnownTypeAttribute с именем метода к одному и тому же типу.
См. приведенный ниже класс.
<DataContract()> _
Public Class DrawingRecord(Of T)
<DataMember()> _
Private theData As T
<DataMember()> _
Private theDrawing As GenericDrawing(Of T)
End Class
[DataContract]
public class DrawingRecord<T>
{
[DataMember]
private T theData;
[DataMember]
private GenericDrawing<T> theDrawing;
}
Поле theDrawing
содержит экземпляры универсального класса ColorDrawing
и универсального класса BlackAndWhiteDrawing
, которые оба наследуют универсальному классу Drawing
. В список известных типов должны быть добавлены оба типа, но следующий синтаксис является недопустимым для атрибутов.
// Invalid syntax for attributes:
// [KnownType(typeof(ColorDrawing<T>))]
// [KnownType(typeof(BlackAndWhiteDrawing<T>))]
' Invalid syntax for attributes:
' <KnownType(GetType(ColorDrawing(Of T))), _
' KnownType(GetType(BlackAndWhiteDrawing(Of T)))>
Поэтому необходимо создать метод, который бы возвращал эти типы. В следующем примере кода показано, как правильно описать этот тип.
<DataContract(), KnownType("GetKnownType")> _
Public Class DrawingRecord2(Of T)
Private TheData As T
Private TheDrawing As GenericDrawing(Of T)
Private Shared Function GetKnownType() As Type()
Dim t(1) As Type
t(0) = GetType(ColorDrawing(Of T))
t(1) = GetType(BlackAndWhiteDrawing(Of T))
Return t
End Function
End Class
[DataContract]
[KnownType("GetKnownType")]
public class DrawingRecord2<T>
{
[DataMember]
private T TheData;
[DataMember]
private GenericDrawing<T> TheDrawing;
private static Type[] GetKnownType()
{
Type[] t = new Type[2];
t[0] = typeof(ColorDrawing<T>);
t[1] = typeof(BlackAndWhiteDrawing<T>);
return t;
}
}
Дополнительные способы добавления известных типов
Типы также можно добавлять в коллекцию ReadOnlyCollection, доступ к которой осуществляется через свойство KnownTypes интерфейса DataContractSerializer.
Кроме того, известные типы можно добавлять с помощью файла конфигурации. Это бывает удобно, если нет возможности управлять типом, требующим для правильной сериализации применения списка известных типов, например при использовании в Windows Communication Foundation (WCF) сторонних библиотек.
В следующем файле конфигурации показано, как задать известный тип в файле конфигурации.
<configuration>
<system.runtime.serialization>
<dataContractSerializer>
<declaredTypes>
<add type="MyCompany.Library.Shape,
MyAssembly, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=XXXXXX, processorArchitecture=MSIL">
<knownType type="MyCompany.Library.Circle,
MyAssembly, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=XXXXXX, processorArchitecture=MSIL"/>
</add>
</declaredTypes>
</dataContractSerializer>
</system.runtime.serialization>
</configuration>
В приведенном выше файле конфигурации объявлено, что тип контракта данных MyCompany.Library.Shape
должен содержать известный тип MyCompany.Library.Circle
.
См. также
Задачи
Справочник
KnownTypeAttribute
Hashtable
Object
DataContractSerializer
KnownTypes