Tipos conocidos de contratos de datos
La clase KnownTypeAttribute le permite especificar, de antemano, los tipos que deberían tenerse en cuenta durante la deserialización. Para ver un ejemplo ilustrativo, consulte el ejemplo Known Types .
Normalmente, al pasar parámetros y valores devueltos entre un cliente y un servicio, ambos extremos comparten todos los contratos de datos de los datos que se van a transmitir. Sin embargo, éste no es el caso en las siguientes circunstancias:
El contrato de datos enviados se deriva del contrato de datos esperados. Para más información, consulte la sección sobre la herencia en Equivalencia de contratos de datos. En ese caso, los datos transmitidos no tienen el mismo contrato de datos que espera el extremo receptor.
El tipo declarado de la información que se va a transmitir es una interfaz, en lugar de una clase, estructura o enumeración. En consecuencia, no se puede saber por adelantado qué tipo que implementa la interfaz se envía realmente y, por consiguiente, el extremo receptor no puede determinar de antemano el contrato de datos para los datos transmitidos.
El tipo declarado de la información que se va a transmitir es Object. Puesto que cada tipo hereda de Object, y no se puede conocer de antemano qué tipo se envía realmente, el extremo receptor no puede determinar de antemano el contrato de datos para los datos transmitidos. Éste es un caso especial del primer elemento: cada contrato de datos se deriva del valor predeterminado, un contrato de datos en blanco que se genera para Object.
Algunos tipos, entre los que se incluyen los de .NET Framework, tienen miembros que se encuentran en una de las tres categorías anteriores. Por ejemplo, Hashtable utiliza Object para almacenar los objetos reales en la tabla hash. Al serializar estos tipos, el lado receptor no puede determinar de antemano el contrato de datos de estos miembros.
La clase KnownTypeAttribute
Cuando los datos llegan a un punto de conexión receptor, el entorno de ejecución de WCF intenta deserializar los datos en una instancia de un tipo de Common Language Runtime (CLR). El tipo del que se crea una instancia para la deserialización se elige inspeccionando primero el mensaje entrante para determinar el contrato de datos al que se ajusta el contenido del mensaje. El motor de deserialización intenta a continuación encontrar un tipo CLR que implemente un contrato de datos compatible con el contenido del mensaje. El conjunto de tipos de candidatos que admite el motor de deserialización durante este proceso se conoce como el conjunto del deserializador de "tipos conocidos."
Una manera de permitir al motor de deserialización saber sobre un tipo consiste en utilizar el KnownTypeAttribute. El atributo no se puede aplicar a miembros de datos individuales, solo a tipos de contrato de datos enteros. El atributo se aplica a un tipo exterior que puede ser una clase o una estructura. En su uso más básico, la aplicación del atributo especifica un tipo como "tipo conocido". Esto hace que el tipo conocido forme parte del conjunto de tipos conocidos siempre que se deserialice un objeto del tipo externo o cualquier objeto al que se hace referencia mediante sus miembros. Se puede aplicar más de un atributo KnownTypeAttribute al mismo tipo.
Tipos conocidos y primitivos
Los tipos primitivos, así como ciertos tipos tratados como primitivos (como, por ejemplo, DateTime y XmlElement) son siempre “conocidos” y nunca tienen que agregarse mediante este mecanismo. Sin embargo, las matrices de tipos primitivos tienen que agregarse explícitamente. La mayoría de las colecciones se consideran equivalentes a las matrices. (Las colecciones no genéricas se consideran equivalentes a las matrices de Object). Para obtener un ejemplo del uso de primitivos, matrices de primitivos y colecciones de primitivos, vea el Ejemplo 4.
Nota
A diferencia de otros tipos primitivos, la estructura DateTimeOffset no es un tipo conocido de forma predeterminada, por lo que debe agregarse manualmente a la lista de tipos conocidos.
Ejemplos
En los siguientes ejemplos se muestra la clase KnownTypeAttribute en uso.
Ejemplo 1
Hay tres clases con una relación de herencia.
[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
Se puede serializar la siguiente clase CompanyLogo
, pero no se puede deserializar si el miembro ShapeOfLogo
está establecido en un objeto CircleType
o TriangleType
, porque el motor de deserialización no reconoce ningún tipo con nombres de contrato de datos "Circle" o "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
La forma correcta de escribir el tipo CompanyLogo
se muestra en el siguiente código.
[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
Cuando quiera que se deserializa el tipo exterior CompanyLogo2
, el motor de deserialización sabe de CircleType
y TriangleType
y, por consiguiente, puede encontrar tipos coincidentes para los contratos de datos "Circle" y "Triangle".
Ejemplo 2
En el siguiente ejemplo , aunque CustomerTypeA
y CustomerTypeB
tienen el contrato de datos Customer
, se crea una instancia de CustomerTypeB
siempre que se deserializa una PurchaseOrder
, porque el motor de deserialización solo conoce al 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
Ejemplo 3
En el siguiente ejemplo, una Hashtable almacena internamente su contenido como Object. Para deserializar correctamente una tabla hash, el motor de deserialización debe conocer el conjunto de tipos posibles que se pueden dar. En este caso, conocemos de antemano que solo los objetos Book
y Magazine
se almacenan en Catalog
, por lo que aquellos se agregan utilizando el atributo 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
Ejemplo 4
En el siguiente ejemplo, un contrato de datos almacena un número y una operación que se va a realizar sobre el número. El miembro de datos Numbers
puede ser un entero, una matriz de enteros, o una List<T> que contenga enteros.
Precaución
Esto solo funcionará en el lado del cliente si se usa SVCUTIL.EXE para generar un proxy WCF. SVCUTIL.EXE recupera metadatos del servicio, incluyendo los tipos conocidos. Sin esta información, un cliente no podrá deserializar los tipos.
[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
Éste es el código de aplicación.
// 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
Herencia, interfaces y tipos conocidos
Cuando un tipo conocido está asociado a un tipo determinado mediante el atributo KnownTypeAttribute
, el tipo conocido también está asociado a todos los tipos derivados de ese tipo. Por ejemplo, vea el siguiente código:
[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
La clase DoubleDrawing
no exige al atributo KnownTypeAttribute
que utilice Square
y Circle
en el campo AdditionalShape
, porque ya se han aplicado esos atributos a la clase base (Drawing
).
Los tipos conocidos solo pueden estar asociados a clases y estructuras, no a interfaces.
Tipos conocidos utilizando métodos genéricos abiertos
Puede que sea necesario agregar un tipo genérico como un tipo conocido. Sin embargo, un tipo genérico abierto no se puede pasar como un parámetro al atributo KnownTypeAttribute
.
Este problema se puede resolver utilizando un mecanismo alternativo: Escriba un método que devuelva una lista de tipos que se han de agregar a la colección de tipos conocidos. El nombre del método se especifica a continuación como un argumento de cadena al atributo KnownTypeAttribute
debido a algunas restricciones.
El método debe existir en el tipo al que se aplica el atributo KnownTypeAttribute
, debe ser estático, no debe aceptar parámetros y debe devolver un objeto que se pueda asignar a IEnumerable de Type.
No puede combinar el atributo KnownTypeAttribute
con un nombre de método y atributos KnownTypeAttribute
con tipos reales en el mismo tipo. Es más, no puede aplicar más de un KnownTypeAttribute
con un nombre de método al mismo tipo.
Vea la siguiente clase.
[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
El campo theDrawing
contiene instancias de una clase genérica ColorDrawing
y una clase genérica BlackAndWhiteDrawing
, las cuales heredan de una clase genérica Drawing
. Normalmente, ambos se deben agregar a los tipos conocidos, pero la siguiente sintaxis no es una sintaxis válida para atributos.
// 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)))>
De este modo, se debe crear un método para devolver estos tipos. La forma correcta de escribir este tipo, entonces, se muestra en el siguiente código.
[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
Maneras adicionales de agregar tipos conocidos
Además, los tipos conocidos se pueden agregar a través de un archivo de configuración. Esto es útil cuando no se controla el tipo que requiere tipos conocidos para la correcta deserialización, como cuando se usan bibliotecas de tipos de terceros con Windows Communication Foundation (WCF).
En el archivo de configuración siguiente se observa cómo se especifica un tipo conocido en un archivo de configuración.
<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>
En el archivo de configuración anterior, se declara que un tipo de contrato de datos denominado MyCompany.Library.Shape
tiene MyCompany.Library.Circle
como tipo conocido.