Поделиться через


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

Класс 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 { }

[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" и "Треугольник".

Пример 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, должен быть статическим, не должен принимать параметры и должен возвращать объект, который можно присвоить IEnumerableType.

Нельзя комбинировать атрибут 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 как известного типа.

См. также