Известные типы контрактов данных

Класс KnownTypeAttribute позволяет заранее задавать типы, которые следует рассматривать при десериализации. Рабочий пример см. в разделе Known Types .

Обычно при передаче параметров и возвращаемых значений между клиентом и службой обе конечные точки совместно используют все контракты данных, относящиеся к передаваемым данным. Однако в следующих ситуациях это не так:

  • Контракт отправляемых данных унаследован из контракта ожидаемых данных. Дополнительные сведения см. в разделе о наследовании в эквивалентности контракта данных. В этом случае контракт передаваемых данных отличается от контракта данных, ожидаемого принимающей конечной точкой;

  • объявленный тип передаваемых данных является интерфейсом, а не классом, структурой или перечислением. Поэтому невозможно заранее знать, какой именно из реализуемых этим интерфейсом типов будет передан, а следовательно принимающая конечная точка не может заранее определить контракт передаваемых данных;

  • объявлен тип передаваемых данных Object. Поскольку каждый тип наследуется от класса Object, невозможно заранее знать, какой именно тип будет передан, а следовательно принимающая конечная точка не может заранее определить контракт передаваемых данных. Это частный случай первого варианта: каждый контракт данных наследует от контракта данных по умолчанию (пустого контракта), который создается для класса Object;

  • Некоторые типы, которые включают платформа .NET Framework типы, имеют члены, которые находятся в одной из предыдущих трех категорий. Например, класс Hashtable использует класс Object для хранения фактических объектов в хэш-таблице. При сериализации таких типов принимающая сторона не может заранее определить контракт данных таких членов.

Класс KnownTypeAttribute

Когда данные поступают в получаемую конечную точку, среда выполнения WCF пытается десериализировать данные в экземпляр типа clR. Тип, экземпляр которого создается в результате десериализации, выбирается в первую очередь по результатам проверки входящего сообщения с целью определения контракта данных, которому соответствует содержимое сообщения. После этого система десериализации пытается найти тип среды CLR, который реализует контракт данных, совместимый с содержимым сообщения. Набор потенциальных типов, которые система десериализации считает допустимыми в результате выполнения этих операций, называется набором "известных типов" десериализатора.

Один из способов уведомления десериализатора о типе - использовать класс KnownTypeAttribute. Этот атрибут нельзя применять к отдельным членам, но следует применять только к целым типам контрактов данных. Атрибут применяется к внешнему типу , который может быть классом или структурой. В большинстве случаев применение атрибута указывает тип как "известный тип". Это приводит к тому, что известный тип является частью набора известных типов всякий раз, когда объект внешнего типа или любой объект, на который ссылается его члены, десериализируется. К одному и тому же типу можно применить несколько атрибутов KnownTypeAttribute .

Известные типы и примитивы

Типы-примитивы, а также некоторые типы, с которыми обращаются как с примитивами (например, DateTime и XmlElement), всегда являются "известными", и их никогда не нужно добавлять с помощью описанного механизма. Однако массивы типов-примитивов нужно добавлять в явном виде. Большинство коллекций считаются эквивалентами массивов. (Неуниверсальные коллекции считаются эквивалентами массивов Object.) Пример использования примитивов, массивов примитивов и коллекций примитивов см. в примере 4.

Примечание.

В отличие от других типов-примитивов структура DateTimeOffset по умолчанию не является известным типом, поэтому ее необходимо вручную добавлять в список известных типов.

Примеры

Ниже приведены примеры использования класса KnownTypeAttribute .

Пример 1

Имеется три класса, связанных отношениями наследования.

[DataContract]
public class Shape { }

[DataContract(Name = "Circle")]
public class CircleType : Shape { }

[DataContract(Name = "Triangle")]
public class TriangleType : Shape { }
<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

Показанный ниже класс CompanyLogo можно сериализовать; однако он не может быть сериализован, если член ShapeOfLogo имеет значение CircleType или TriangleType , поскольку система десериализации не распознает типы с именами контрактов данных "Circle" и "Triangle".

[DataContract]
public class CompanyLogo
{
    [DataMember]
    private Shape ShapeOfLogo;
    [DataMember]
    private int ColorOfLogo;
}
<DataContract()> _
Public Class CompanyLogo
    <DataMember()> _
    Private ShapeOfLogo As Shape
    <DataMember()> _
    Private ColorOfLogo As Integer
End Class

В следующем примере кода показано, как правильно описать тип CompanyLogo .

[DataContract]
[KnownType(typeof(CircleType))]
[KnownType(typeof(TriangleType))]
public class CompanyLogo2
{
    [DataMember]
    private Shape ShapeOfLogo;
    [DataMember]
    private int ColorOfLogo;
}
<DataContract(), KnownType(GetType(CircleType)), KnownType(GetType(TriangleType))> _
Public Class CompanyLogo2
    <DataMember()> _
    Private ShapeOfLogo As Shape
    <DataMember()> _
    Private ColorOfLogo As Integer
End Class

Когда происходит десериализация внешнего типа CompanyLogo2 , система десериализации знает о типах CircleType и TriangleType и поэтому может найти контракты данных, соответствующие типам "Circle" и "Triangle".

Пример 2

В следующем примере, даже если оба типа CustomerTypeA и CustomerTypeB имеют контракт данных Customer , при десериализации типа CustomerTypeB будет создаваться экземпляр PurchaseOrder , поскольку системе десериализации известен только тип CustomerTypeB .

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;
}
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

Пример 3

В следующем примере тип Hashtable хранит содержимое в виде Object. Для десериализации хэш-таблицы системе десериализации должен быть известен набор возможных типов, которые представляются этим типом. В данном случае нам известно, что в объекте Book хранятся только объекты Magazine и Catalog, поэтому они добавляются с помощью атрибута KnownTypeAttribute .

[DataContract]
public class Book { }

[DataContract]
public class Magazine { }

[DataContract]
[KnownType(typeof(Book))]
[KnownType(typeof(Magazine))]
public class LibraryCatalog
{
    [DataMember]
    System.Collections.Hashtable theCatalog;
}
<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

Пример 4

В следующем примере контракт данных хранит число и операцию, которую следует выполнить над этим числом. Член данных Numbers может быть целым числом, массивом целых чисел или объектом List<T> , содержащим целые числа.

Внимание

Работает только на стороне клиента, если SVCUTIL.EXE используется для формирования учетной записи-посредника WCF. SVCUTIL.EXE извлекает метаданные из службы, включая любые известные типы. Без этой информации клиент не сможет десериализовать типы.

[DataContract]
[KnownType(typeof(int[]))]
public class MathOperationData
{
    private object numberValue;
    [DataMember]
    public object Numbers
    {
        get { return numberValue; }
        set { numberValue = value; }
    }
    //[DataMember]
    //public Operation Operation;
}
<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

Ниже показан код приложения.

// 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;
}
' 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

Известные типы, наследование и интерфейсы

Когда известный тип связывается с конкретным типом с помощью атрибута KnownTypeAttribute , он также связывается со всеми производными типами этого типа. См., например, следующий код.

[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;
}
<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

Класс DoubleDrawing не требует, чтобы атрибут KnownTypeAttribute использовал типы Square и Circle в поле AdditionalShape , поскольку эти атрибуты уже применены в базовом классе (Drawing).

Известные типы можно связывать только с классами и структурами, но не с интерфейсами.

Известные типы и открытые универсальные методы

Может возникнуть необходимость добавить в качестве известного типа универсальный тип. Однако открытый универсальный тип невозможно передать в атрибут KnownTypeAttribute в качестве параметра.

Эту проблему можно решить с помощью альтернативного механизма: напишите метод, возвращающий типы, которые следует добавить в коллекцию известных типов. После этого имя метода задается в качестве строкового аргумента атрибута KnownTypeAttribute с некоторыми ограничениями.

Этот метод должен существовать в типе, к которому применяется атрибут KnownTypeAttribute , должен быть статическим, не должен принимать параметров и должен возвращать объект, который можно присвоить интерфейсу IEnumerable типа Type.

Нельзя объединять атрибут KnownTypeAttribute с именем метода и атрибутами KnownTypeAttribute с реальными типами в рамках одного типа. Более того, нельзя применять более одного атрибута KnownTypeAttribute с именем метода к одному и тому же типу.

См. приведенный ниже класс.

[DataContract]
public class DrawingRecord<T>
{
    [DataMember]
    private T theData;
    [DataMember]
    private GenericDrawing<T> theDrawing;
}
<DataContract()> _
Public Class DrawingRecord(Of T)
    <DataMember()> _
    Private theData As T
    <DataMember()> _
    Private theDrawing As GenericDrawing(Of T)
End Class

Поле 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<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;
    }
}
<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

Дополнительные способы добавления известных типов

Кроме того, известные типы можно добавлять с помощью файла конфигурации. Это полезно, если вы не управляете типом, требующим известных типов для правильной десериализации, например при использовании сторонних библиотек типов с 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 .

См. также