Generic return types with interface type params in WCF

generics, interface, json, serialization, wcf

Solution

After much trial and error, I've finally got this working with minimal changes:

- My service operations now return `ServiceResult<T>` instead of `IServiceResult<T>`. In fact, `IServiceResult<T>` is now gone entirely.

`GetServiceKnownTypes()` no longer returns all the variants of `ServiceResult<T>`. I only return the `DataContract`s used as `T`.

#region My service interface
[ServiceContract]
[ServiceKnownType("GetServiceKnownTypes", typeof(ServiceKnownTypesHelper))]
public interface IMyService
{
    [WebGet(UriTemplate="count")]
    [OperationContract]
    ServiceResult<int> GetCount();

    [WebGet(UriTemplate="desc")]
    [OperationContract]
    ServiceResult<string> GetDescription();

    [WebGet(UriTemplate="foo")]
    [OperationContract]
    ServiceResult<IFooData> GetFooData();

    [WebGet(UriTemplate="bar")]
    [OperationContract]
    ServiceResult<IBarData> GetBarData();
}
#endregion
#region My service implementation (minus bodies)
public class MyService : IMyService
{
    public ServiceResult<int> GetCount() {}
    public ServiceResult<string> GetDescription() {}
    public ServiceResult<IFooData> GetFooData() {}
    public ServiceResult<IBarData> GetBarData() {}
}
#endregion
#region ServiceKnownTypes
// My list of ServiceKnownTypes is now much shorter and simpler. I was feeding the service too much information
public static class ServiceKnownTypesHelper
{
    private static IList<Type> serviceKnownTypes = new List<Type>()
        {
            typeof(FooData),
            typeof(BarData),
            //typeof(ServiceResult<int>),
            //typeof(ServiceResult<string>),
            //typeof(ServiceResult<IFooData>),
            //typeof(ServiceResult<IBarData>),
        };

    // Remaining implementation is the same as before
}
#endregion
#region ServiceResult<T> with no interface (it's not used or needed)
[DataContract]
public class ServiceResult<T> //: IServiceResult<T>
{
    // implementation is the same as before
}
#endregion

I can now invoke all of those methods and get back lists of generic types referred to by their interfaces in the `ServiceContract`.

Problem

How can I return interface types in generic type parameters from the `OperationContracts` in my WCF REST service? More specifically, it works for one operation but not when I add a second operation with a generic `T` that is an interface. I'm using JSON as the request and response format, feeding a non-WCF client that parses the JSON responses for the data it needs. I am not using SOAP, or the WSDL generated by the service. My service interface: ``` [ServiceContract] [ServiceKnownType("GetServiceKnownTypes", typeof(ServiceKnownTypesHelper))] public interface IMyService { [WebGet(UriTemplate="count")] [OperationContract] IServiceResult<int> GetCount(); [WebGet(UriTemplate="desc")] [OperationContract] IServiceResult<string> GetDescription(); [WebGet(UriTemplate="foo")] [OperationContract] IServiceResult<IFooData> GetFooData(); // Fails when I invoke either method if I uncomment this operation. //[WebGet(UriTemplate="bar")] //[OperationContract] //IServiceResult<IBarData> GetBarData(); } ``` I left `GetCount()` and `GetDescription()` in the example to point out that those two generic results work fine, but obviously they are concrete types. And even `GetFooData()` works fine until I add a second method of `IServiceResult<T>` where `T` is an interface. The return types of `GetFooData()` and `GetBarData()` are not the same, nor are the concrete classes that implement them. You may imagine that I've reduced the implementation to a skeleton as I don't think the implementation is the heart of the problem: ``` #region My service implementation public class MyService : IMyService { public IServiceResult<int> GetCount() { return new ServiceResult<int>(42); } public IServiceResult<string> GetDescription() { return new ServiceResult<string>("Muffins"); } public IServiceResult<IFooData> GetFooData() { return new ServiceResult<IFooData>(new FooData() { Foo = 99 }); } public IServiceResult<IBarData> GetBarData() { return new ServiceResult<IBarData>(new BarData() { Bar = "Elvis was here" }); } } #endregion #region ServiceKnownTypesHelper.GetServiceKnownTypes(): public static class ServiceKnownTypesHelper { private static IList<Type> serviceKnownTypes = new List<Type>() { typeof(FooData), typeof(BarData), typeof(ServiceResult<int>), typeof(ServiceResult<string>), typeof(ServiceResult<IFooData>), typeof(ServiceResult<IBarData>), }; public static IEnumerable<Type> GetServiceKnownTypes(ICustomAttributeProvider paramIgnored) { return serviceKnownTypes; } } #endregion #region IServiceResult<T> and its concrete implementation: public interface IServiceResult<T> { IList<string> Errors { get; } T Value { get; set; } } [DataContract] public class ServiceResult<T> : IServiceResult<T> { public ServiceResult(T value) { this.Value = value; } private IList<string> errors = new List<string>(); [DataMember] public IList<string> Errors { get { return this.errors; } } [DataMember] public T Value { get; set; } } #endregion #region IFooData and its concrete implementation: public interface IFooData { int Foo { get; set; } } [DataContract] public class FooData: IFooData { [DataMember] public int Foo { get; set; } } #endregion #region IBarData and its concrete implementation: public interface IBarData { string Bar { get; set; } } [DataContract] public class BarData: IBarData { [DataMember] public string Bar { get; set; } } #endregion ``` And the error message when I invoke `GetBarData()` from the browser: ``` Type 'ServiceResult`1[IBarData]' cannot be added to list of known types since another type 'ServiceResult`1[IFooData]' with the same data contract name 'http://schemas.datacontract.org/2004/07/ServiceResultOfanyType' is already present. ``` The rest of the error message is a red herring about colliding collection types `List<Test>` and `Test[]`, which isn't the case here. Clearly, `IFooData` and `IBarData` aren't the same, nor are the classes that implement them. So why do `ServiceResult<IFooData>` and `ServiceResult<IBarData>` both resolve to `ServiceResultOfanyType`? Am I missing something, or is there no way to fix this?

Original source