views:

2723

answers:

3

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}")]
+5  A: 

From here...

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

Steve Dignan
+6  A: 

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.

baretta
Thanks Baretta. This concise answer worked perfectly, once I sorted out the issue with the name attribute (see my edit).
Drew Noakes
Thanks for your edit. This is a great answer and if I could vote for it twice, I would :)
Drew Noakes