All About KnownTypes

<This turned out to be longer than what I had intended. Sorry about it>

One of the common errors in Deserialization is “Element ''https://mycompany.com/:shape'' contains data of the 'https://mycompany.com/:Circle' data contract. The deserializer has no knowledge of any type that maps to this contract. Add the type corresponding to 'Circle' to the list of known types - for example, by using the KnownTypeAttribute attribute or by adding it to the list of known types passed to DataContractSerializer.” The following code will throw such an exception.

[DataContract(Namespace = “https://mycompany.com/”)]

public class Shape{…}

[DataContract(Namespace = “https://mycompany.com/”)]

public class Circle : Shape {…}

  

[ServiceContract]

public interface IMyServer

{

    [OperationContract]

    bool AddShape(Shape shape);

}

IMyServer client = new ChannelFactory<IMyServer>(binding, endPoint).CreateChannel();

client.AddShape(new Circle());

The contract specifies Shape but the client passes Circle a subtype of Shape. Since Circle is not a part of the contract the DataContractSerializer fails to deserialize.

Shared Contract vs Shared Type Model:

Remoting users would have never faced this problem. This is due to the fact that, in remoting, the CLR type name is serialized to the wire. The serialized type name is of the following format.

MyCompany.Library.Circle, MyAssembly, Version=2.0.0.0, Culture=neutral, PublicKeyToken=XXXXXX, processorArchitecture=MSIL

The reader uses the Assembly.Load API to load the assembly referred in the wire. In this approach the writer controls the type loaded by the reader. While this removes the hassle of the reader having to know the type being deserialized it has a couple of drawbacks.

1. It disallows the writer and reader using different types which is common in an interoperable webservice scenario.

2. Letting the writer load an arbitrary type into the reader’s appdomain is not a sound idea from security standpoint.

NetDataContractSerializer, shipped in WCF, supports sharing types as described above. DataContractSerializer (which is used by default in WCF services) follows a different approach. Like the XmlSerializer (which is used by the ASMX webservices), the DataContractSerializer shares contracts instead of types. This allows the reader to control the deserializing type. From the webservice contract the reader specifies the type to deserialize. In the derived type scenario the contract name (i.e the XSD schema type name) is written on the wire. The contract name is a XML qualified name like 'https://mycompany.com/:Circle'.

While contract name is inferable from the CLR type the reverse is not possible since there can be multiple CLR types with the same contract name. To enable this reverse lookup the user needs to pass a set of types called the KnownTypes. The deserializer resolves the type qname in the wire using these types. It must be noted that unlike the remoting scenario the deserializer never loads the type based on the information from the wire.

Declaring Known types:

ASMX uses XmlInclude and SoapInclude attribute to denote known types. WCF allows more than one way to specify the known types.

1. The base type can specify the derived as a known type using the KnownTypeAttribute as shown below.

[DataContract(Namespace = “https://mycompany.com/”)]

[KnownType(typeof(Circle))]

public class Shape{…}

2. Known types can be specified in the config as shown below.

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

The config defines Circle as a known type for Shape is used. This is equivalent to defining KnownType in the base type as described in #1.

3. Known types can also be passed to the constructor of the DataContractSerializer

4. In service model known types can be added via the ServiceKnownType attribute along with either ServiceContract or OperationContract.

[ServiceContract]

public interface IMyServer

{

    [OperationContract]

[ServiceKnownType(typeof(Circle))]

    bool AddShape(Shape shape);

}

Declaring ServiceKnownType on an operation causes the known type to be applied for all the parameters for the operation. Declaring on the service contract causes known type to be applied for all the parameters of allthe operations.

5. The known types specified via ServiceKnownType attribute end up in the OperationDescription. Known types can also be added to OperationDescription.KnownTypes imperatively. Known types in the description are passed to the constructor of DataContractSerializer. These known types are also passed as extra types to XmlSerializer in the XmlSerializerFormat mode.

Known Type Scope:

Deserialization is a process of constructing the object tree. Known types passed to the constructor of DataContractSerializer apply to all nodes of the tree. A KnownType declared on a class not only applies to the class itself but for the entire subtree.

[DataContract(Namespace = “https://mycompany.com/”)]

[KnownType(typeof(Square))]

[KnownType(typeof(Circle))]

public class Shapes

{

  [DataMember]

  Shape[] shapeList;

}

In the above example the types Square and Circle are ‘in scope’ KnownType not only when Shapes is deserialized but also when the Shape[] is deserialized.

It is an error to have more than one KnownType with the same contract name at a given scope (i.e in the list of KnownTypes on a class or the types passed to the constructor of the DataContractSerializer). However there could be such duplication across scopes. In such cases the type in the inner most scope is used. To understand the scoping rules consider the class Shape being defined as follows.

[DataContract(Namespace = “https://mycompany.com/”)]

[KnownType(typeof(AnotherSquare))]

[KnownType(typeof(AnotherCircle))]

public class Shape

{

}

If AnotherSquare has the same contract name as Square then AnotherSquare will be used when the contract name of Square is seen on the wire.

To summarize the order in which known types are applied is shown below.

  1. Data contract primitive type
  2. Known types provided via KnownTypeAttribute from the inner most scope to the outer most.
  3. Known types passed to the constructor of the DataContractSerializer
  4. The declared member type.
  5. The root type passed to the constructor of DataContractSerializer

Known Types for Generic Types:

Consider Circle and Shape in the previous example to be generic type. Ideally one would want to define the known type as follows.

[DataContract(Namespace = “https://mycompany.com/”)]

[KnownType(typeof(Circle<>))] //NOT ALLOWED

public class Shape<T>{…}

Unfortunately the CLR does not allow generic types in attributes. Thus the above declaration will not work. Fortunately the DataContractSerializer supports another way to provide generic known type. Instead of specifying the known type directly in the KnownTypeAttrribute the user can also point a method from KnownTypeAttribute. The method can return an array of KnownTypes. This will allow generic known types as shown below.

    [KnownType("GetKnownTypes")]

    public class Shape<T>

    {

        static Type[] GetKnownTypes()

        {

            return new Type[] { typeof(Circle<T>) };

        }

    }

The method approach also allows the type authors to be able to control the known types dynamically at runtime. The method pointed by KnownType attribute must be static, take no parameters and return an IEnumerable<Type>. It is an error to mix a static known type with the method based dynamic known type for a given class.

ServiceKnownType supports a similar model to specify dynamic known types as shown below.

[ServiceContract]

[ServiceKnownType(Method = “GetKnownTypes”, Type = typeof(KnownTypeProvider))]

public interface IMyServer<T>

{

    [OperationContract]

    bool AddShape(Shape shape);

}

static class KnownTypesProvider

{

  static Type[] GetKnownTypes(ICustomAttributeProvider knownTypeAttributeTarget)

  {

    Type contractType = (Type)knownTypeAttributeTarget;

    return new Type[]{contractType.GetGenericArguments()[0]};

  }

}

Note that the attribute refers to a method name and a type name. The type name is needed since the ServiceKnownType attribute can go on interface or interface methods and the Known type method cannot be implemented in the interface. The other difference between ServiceKnownType and KnownType is in the method definition. In ServiceKnownType the method takes an additional ICustomAttributeProvider as parameter. DataContractSerializer passes the ServiceContract class or MethodInfo of the OperationContract method when invoking this method.

Generic Types in Config

Generic known types can also be defined in config as shown below.

<configuration>

  <system.runtime.serialization>

    <dataContractSerializer>

      <declaredTypes>

         <add type="MyCompany.Library.Shape`1,

              MyAssembly, Version=2.0.0.0, Culture=neutral,

          PublicKeyToken=XXXXXX, processorArchitecture=MSIL">

            <knownType type="MyCompany.Library.Circle`1,

                       MyAssembly, Version=2.0.0.0, Culture=neutral,

                       PublicKeyToken=XXXXXX, processorArchitecture=MSIL">

                    <parameter index="0"/>

            </knownType>

         </add>

      </declaredTypes>

    </dataContractSerializer>

  </system.runtime.serialization>

</configuration>

The above config specifies that the generic parameter for Circle is the same as the generic parameter for the declared type Shape. The config allows the definition of known type of arbitrary complexity. For example if it is needed to define Circle<Dictionary<string, T>> as the known type of Shape<T> (of course this is purely academic) it can be done as follows.

<configuration>

  <system.runtime.serialization>

    <dataContractSerializer>

      <declaredTypes>

         <add type="MyCompany.Library.Shape`1,

              MyAssembly, Version=2.0.0.0, Culture=neutral,

    PublicKeyToken=XXXXXX, processorArchitecture=MSIL">

            <knownType type="MyCompany.Library.Circle`1,

                       MyAssembly, Version=2.0.0.0, Culture=neutral,

                       PublicKeyToken=XXXXXX, processorArchitecture=MSIL">

                   <parameter type="System.Collections.Generic.Dictionary`2">

                      <parameter type="System.String"/>

                      <parameter index="0"/>

                   </parameter>

            </knownType>

         </add>

      </declaredTypes>

    </dataContractSerializer>

  </system.runtime.serialization>

</configuration>

Note the use config element “parameter” with the attributes ‘type’ and ‘index’.

Common KnownType Scenarios:

In addition to the inherited scenario described earlier, there are some typical scenarios that require known types.

  1. When passing a DataContract class instance as an item to non generic collection class such as ArrayList. This due to the fact that ArrayList is projected as an array of objects.
  2. When passing a DataContract class instance as an item in ISerializable class such as System.Exception. This is due to the fact that ISerializable is an untyped (System.Object based) bag.

While these can also be solved by declaring known type, it is recommended to consider other approaches. Scenario #1 can be worked around by using a generic collection. Scenario #2 will never arise if the user follows the fault contract guidance and explicitly declare fault types as FaultContract and not use exception for faults. In general it is recommended to avoid ISerializable in a contract oriented application.