Compartilhar via


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.

Comments

  • Anonymous
    November 13, 2006
    The comment has been removed

  • Anonymous
    May 08, 2007
    Hi Sowmy Thanks for sharing this with us. It is working while retrieve a collection of objects, which has cyclic references. I implemented your solution and knocked down fetching issues I had before. Now I am getting into trouble, while sending back the modified collection. If I modify only the parent and set the child to null, it is great. If modify the child and parent or if I modify the child then I am getting into IndexOutOfrange issue. Apparently I increased maxarraylength and maxItemsinObjectGraph, and started getting a different error message, Object graph for type BusinessEntity contains cycles and cannot be serialized if reference tracking is disabled. And the stack trace is showing me some inner classes of System.Runtime.Serialization.XmlObjectSerializerWriteContext.OnHandleReference(XmlWriterDelegator xmlWriter, Object obj, Boolean canContainCyclicReference) and so on. Could you please help me out in this?

  • Anonymous
    July 21, 2007
    PingBack from http://electroholic.com/?p=5

  • Anonymous
    July 24, 2007
    PingBack from http://whiletrue.nl/blog/?p=36

  • Anonymous
    October 24, 2007
    windows mobile free themes download

  • Anonymous
    December 12, 2007
    One of the challenges encountered when using DataContract serialization (the default for WCF web services)

  • Anonymous
    December 12, 2007
    One of the challenges encountered when using DataContract serialization (the default for WCF web services

  • Anonymous
    December 12, 2007
    PingBack from http://msdnrss.thecoderblogs.com/2007/12/13/datacontract-serialization-entity-framework-and-known-types/

  • Anonymous
    June 12, 2008
    Lorsque vous créez un service WCF, vous allez créer un contrat. Dans la plupart des cas, ce contrat est

  • Anonymous
    June 12, 2008
    Lorsque vous créez un service WCF, vous allez créer un contrat. Dans la plupart des cas, ce contrat est

  • Anonymous
    August 04, 2008
    Hi Sowmy, The article was quite informational but I did not understand the knownTypes definition under system.runtime.serialization. I tried including a declaration under system.runtime.serialization for a simple WCF service in a website for but it doesn't seem to be respected. Thanks

  • Anonymous
    April 30, 2009
    Hi Sowny, Great article, thanks for sharing. One comment: You mention this as a common case for using KnownTypes: "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." Which I totally agree is a common case, but I have to add that having ISerializable class being marshaled that is not an Exception is a very valid case. Later when you mention: "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." Here is where I disagree. What if the you don't control the type you want to serialize? What if this type is part of some 3rd-party library. Then using the KnownTypes with generic parameters is crucial if that 3rd-party class embeds even a generic collection. In my case, the 3rd-party class was a class from the Microsoft Synchronization framework, which sadly uses ISerializable instead of DataContract.

  • Anonymous
    August 11, 2009
    Hi sowmy, Excellent article. There is one question have though. What if the service method(Operation contract) takes a argument of type 'object' and from client we pass it any other 'user defined' type. In this case how can we manage using either KnownType attribute or config file? Thanx s.a.w

  • Anonymous
    June 24, 2010
    Hi, I tried all that and I am basically able to achieve the result without the following: I am consuming the service from Silverlight and I cannot have the types working dynamically, so the client is only aware of the types at the time when the proxy classes were generated from Add Service Reference from Visual Studio. Any idea how to solve that? Thanks in advance, Tim

  • Anonymous
    July 11, 2014
    I hope the code snippet below helps someone else as much as this article helped me: Method 2 is the best way.  Manual annotation of the base classes is a maintenance nightmare long term. But, you do not always have access to the call to construct the XmlSerializer (or DataContractSerializer) in order to provide a list of known types. Our application was a REST API with three parts Service, Common, Client where the bodies of the HTTP in the requests into and responses from to the service portion of the RESTful API are Xml serialized objects.  In this scenario the call to the constructor in the WCF auto-generated code (TypeLoader.cs) is not statically accessible to the developer of the REST API. Our solution was to use the ServiceKnownTypes annotation on the service contract.  The annotation resembles:    [ServiceContract(Name = Contract.Identifier)]    [ServiceKnownType("GetKnownTypes", typeof(KnownTypesProvider))]    [XmlSerializerFormat]    public interface IContract    {     // Service contract and WCF routing information here    } And to create the method, GetKnownTypes, to enumerated the known types programmaticly via reflection.    using System;    using System.Collections.Generic;    using System.Linq;    using System.Reflection;    using System.Text;    using System.Xml.Linq;    namespace Rest.API.Common    {      /// <summary>      ///      /// </summary>      /// <note1>      /// Somehow the XML serialization using XmlSerilaizer needs to be aware of all the classes to be serialized.      ///      /// As this article points out      /// www.johnsoer.com/blog      /// there are two ways to make the XmlSerializer aware of the type that it might be serailizing and deserializing.      ///      ///  1) Decorate appropriate classes with the information of derived classes that might be serailized. (Bad Idea and  maintanence nightmare long term)      ///  2) Add a list of known types to the construction of the instances of XMLSerializer      ///      /// This implements Method 2; inform the XML serializer of all known types at the time the serializer is contructed.        /// This is dificult because the call to the XmlSerialize constructor is done within the code auto-generated by the WCF.      /// But this auto generated code by the WCF (TypeLoader.cs)  provides a way for the code constructed by the WCF      /// to ask the calling applicationfor a list of known types.      ///

  • Anonymous
    July 11, 2014
    The comment has been removed

  • Anonymous
    July 22, 2014
    Thanks for useful info dude, I finally understood the issue :)

  • Anonymous
    January 03, 2015
    Hi, could you attach source code ? Thanks for the article,