How to specify a WCF known type in config that is generic?

Drew Noakes picture Drew Noakes · Jun 10, 2009 · Viewed 9k times · Source

I have a type, let's call it Data<TKey>. I also have a WCF service contract that accepts a type (lets call it Wrapper) with a property of type Object (for reasons I won't go into, this isn't optional).

[DataContract]
public class Data<TKey> { ... }

[DataContract]
public class Wrapper
{
    [DataMember]
    public object DataItem { get; set; }
}

Right now I'm sending two classes IntData and LongData:

[DataContract]
public class IntData : Data<int> { /*empty*/ }

[DataContract]
public class LongData : Data<long> { /*empty*/ }

They're both configured in the known types config file. The config resembles something like this:

<configuration>
  <system.runtime.serialization>
    <dataContractSerializer>
      <declaredTypes>
        <add type="Wrapper, TheirAssembly">
          <knownType type="IntData, MyAssembly"/>
          <knownType type="LongData, MyAssembly"/>
        </add>
      </declaredTypes>
    </dataContractSerializer>
  </system.runtime.serialization>
</configuration>

At this point, everything works fine.

But I'm about to add a third type and I don't like having the unnecessary, empty .NET classes IntData and LongData. They only exist because...

I don't know how to specify generic types in WCF configuration!

I want to do something like this, but don't know the exact syntax.

<configuration>
  <system.runtime.serialization>
    <dataContractSerializer>
      <declaredTypes>
        <add type="Wrapper, TheirAssembly">
          <!-- this syntax is wrong -->
          <knownType type="Data{System.Int32}, MyAssembly"/>
          <knownType type="Data{System.Int64}, MyAssembly"/>
        </add>
      </declaredTypes>
    </dataContractSerializer>
  </system.runtime.serialization>
</configuration>

What is the correct syntax for this?

(Note too that I cannot put [KnownType(...)] attributes on Wrapper as it's not my type. Config seems to be the only way.)

EDIT

@baretta's answer worked nicely. Note however that initially I received this error:

Type 'MyAssembly.Data`1[System.Int64]' cannot be added to list of known types since another type 'MyAssembly.Data`1[System.Int32]' with the same data contract name 'http://www.mycompany.com/MyAssembly:Data' is already present.

I didn't mention it in the original question, but my type has an explicit data contract name. Something like this:

[DataContract(Name = "Data")]
public class Data<TKey> { ... }

The above error occurred until I removed the Name property value from the attribute. Hope that helps someone else out too. I don't know what format works in this scenario. These didn't:

[DataContract(Name = "Data\`1")]
[DataContract(Name = "Data{TKey}")]

Anyone know how to do this?

EDIT 2

Thanks again to @baretta who pointed out that the correct syntax is in fact:

[DataContract(Name = "Data{0}")]

Answer

baretta picture baretta · Jun 10, 2009

A generic type is instantiable from a string, if the string follows this pattern: Class name followed by a "`" character, followed by the number of type parameters(in this case it's 1), followed by the type parameters enclosed within "[]", and using comma as type parameter separator.

<configuration>
  <system.runtime.serialization>
    <dataContractSerializer>
      <declaredTypes>
        <add type="Wrapper, TheirAssembly">
          <!-- this syntax is all good -->
          <knownType type="Data`1[System.Int32], MyAssembly"/>
          <knownType type="Data`1[System.Int64], MyAssembly"/>
        </add>
      </declaredTypes>
    </dataContractSerializer>
  </system.runtime.serialization>
</configuration>

Edit: I might also add, that if assembly information needs to be specified for the type parameters(althoug it's not the case for stuff in mscorlib), then nested "[]" is used.

<knownType type="Data`1[[System.Int32, mscorlib]], MyAssembly"/>

Edit: You can customize names of generic types in data contracts, using the string format pattern.

[DataContract(Name = "Data{0}")]
public class Data<TKey>
{...}

By default, the name generated for the Data<Int32> type is something like "DataOfInt32HJ67AK7Y", where "HJ67AK7Y" is a hash generated from the string "urn:default", or the namespace of your class, if you have any. But "Data{0}" would give it the name "DataInt32".

More here. Have a look at the "Customizing Data Contract Names for Generic Types" part down the page.