Data Contract Known Types
The KnownTypeAttribute class allows you to specify, in advance, the types that should be included for consideration during deserialization. For a working example, see the Known Types example.
Normally, when passing parameters and return values between a client and a service, both endpoints share all of the data contracts of the data to be transmitted. However, this is not the case in the following circumstances:
The sent data contract is derived from the expected data contract. For more information, see the section about inheritance in Data Contract Equivalence). In that case, the transmitted data does not have the same data contract as expected by the receiving endpoint.
The declared type for the information to be transmitted is an interface, as opposed to a class, structure, or enumeration. Therefore, it cannot be known in advance which type that implements the interface is actually sent and therefore, the receiving endpoint cannot determine in advance the data contract for the transmitted data.
The declared type for the information to be transmitted is Object. Because every type inherits from Object, and it cannot be known in advance which type is actually sent, the receiving endpoint cannot determine in advance the data contract for the transmitted data. This is a special case of the first item: Every data contract derives from the default, a blank data contract that is generated for Object.
Some types, which include .NET Framework types, have members that are in one of the preceding three categories. For example, Hashtable uses Object to store the actual objects in the hash table. When serializing these types, the receiving side cannot determine in advance the data contract for these members.
The KnownTypeAttribute Class
When data arrives at a receiving endpoint, the WCF runtime attempts to deserialize the data into an instance of a common language runtime (CLR) type. The type that is instantiated for deserialization is chosen by first inspecting the incoming message to determine the data contract to which the contents of the message conform. The deserialization engine then attempts to find a CLR type that implements a data contract compatible with the message contents. The set of candidate types that the deserialization engine allows for during this process is referred to as the deserializer's set of "known types."
One way to let the deserialization engine know about a type is by using the KnownTypeAttribute. The attribute cannot be applied to individual data members, only to whole data contract types. The attribute is applied to an outer type that can be a class or a structure. In its most basic usage, applying the attribute specifies a type as a "known type." This causes the known type to be a part of the set of known types whenever an object of the outer type or any object referred to through its members is being deserialized. More than one KnownTypeAttribute attribute can be applied to the same type.
Known Types and Primitives
Primitive types, as well as certain types treated as primitives (for example, DateTime and XmlElement) are always "known" and never have to be added through this mechanism. However, arrays of primitive types have to be added explicitly. Most collections are considered equivalent to arrays. (Non-generic collections are considered equivalent to arrays of Object). For an example of the using primitives, primitive arrays, and primitive collections, see Example 4.
Note
Unlike other primitive types, the DateTimeOffset structure is not a known type by default, so it must be manually added to the list of known types.
Examples
The following examples show the KnownTypeAttribute class in use.
Example 1
There are three classes with an inheritance relationship.
[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
The following CompanyLogo
class can be serialized, but cannot be deserialized if the ShapeOfLogo
member is set to either a CircleType
or a TriangleType
object, because the deserialization engine does not recognize any types with data contract names "Circle" or "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
The correct way to write the CompanyLogo
type is shown in the following code.
[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
Whenever the outer type CompanyLogo2
is being deserialized, the deserialization engine knows about CircleType
and TriangleType
and, therefore, is able to find matching types for the "Circle" and "Triangle" data contracts.
Example 2
In the following example, even though both CustomerTypeA
and CustomerTypeB
have the Customer
data contract, an instance of CustomerTypeB
is created whenever a PurchaseOrder
is deserialized, because only CustomerTypeB
is known to the deserialization engine.
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
Example 3
In the following example, a Hashtable stores its contents internally as Object. To successfully deserialize a hash table, the deserialization engine must know the set of possible types that can occur there. In this case, we know in advance that only Book
and Magazine
objects are stored in the Catalog
, so those are added using the KnownTypeAttribute attribute.
[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
Example 4
In the following example, a data contract stores a number and an operation to perform on the number. The Numbers
data member can be an integer, an array of integers, or a List<T> that contains integers.
Caution
This will only work on the client side if SVCUTIL.EXE is used to generate a WCF proxy. SVCUTIL.EXE retrieves metadata from the service including any known types. Without this information a client will not be able to deserialize the types.
[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 the application code.
// 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
Known Types, Inheritance, and Interfaces
When a known type is associated with a particular type using the KnownTypeAttribute
attribute, the known type is also associated with all of the derived types of that type. For example, see the following code.
[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
The DoubleDrawing
class does not require the KnownTypeAttribute
attribute to use Square
and Circle
in the AdditionalShape
field, because the base class (Drawing
) already has these attributes applied.
Known types can be associated only with classes and structures, not interfaces.
Known Types Using Open Generic Methods
It may be necessary to add a generic type as a known type. However, an open generic type cannot be passed as a parameter to the KnownTypeAttribute
attribute.
This problem can be solved by using an alternative mechanism: Write a method that returns a list of types to add to the known types collection. The name of the method is then specified as a string argument to the KnownTypeAttribute
attribute due to some restrictions.
The method must exist on the type to which the KnownTypeAttribute
attribute is applied, must be static, must accept no parameters, and must return an object that can be assigned to IEnumerable of Type.
You cannot combine the KnownTypeAttribute
attribute with a method name and KnownTypeAttribute
attributes with actual types on the same type. Furthermore, you cannot apply more than one KnownTypeAttribute
with a method name to the same type.
See the following class.
[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
The theDrawing
field contains instances of a generic class ColorDrawing
and a generic class BlackAndWhiteDrawing
, both of which inherit from a generic class Drawing
. Normally, both must be added to known types, but the following is not valid syntax for attributes.
// 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)))>
Thus, a method must be created to return these types. The correct way to write this type, then, is shown in the following code.
[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
Additional Ways to Add Known Types
Additionally, known types can be added through a configuration file. This is useful when you do not control the type that requires known types for proper deserialization, such as when using third-party type libraries with Windows Communication Foundation (WCF).
The following configuration file shows how to specify a known type in a configuration file.
<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>
In the preceding configuration file a data contract type called MyCompany.Library.Shape
is declared to have MyCompany.Library.Circle
as a known type.