Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generic return types with interface type params in WCF

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?

like image 256
JMD Avatar asked Mar 28 '13 00:03

JMD


1 Answers

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

like image 52
JMD Avatar answered Oct 30 '22 14:10

JMD