4

I want to use the following pattern in my controllers:

api/{controller}/{id}/{collection}

Example: api/customers/123/addresses

But I want to return IQueryable Of T from the corresponding Get action. I want something like this (simplified):

public IQueryable<????> GetCollection(int id, string collection)  
{  
    switch(collection)  
    {  
        case "addresses":  
            return _addresses.AsQueryable();  
            break;  
        case "orders":  
            return _orders.AsQueryable();  
            break;  
        default:  
            throw new Exception(NotSupported);  
    }  
}   

Can this be done?
What would be the recommended approach?

Darrel Miller
  • 139,164
  • 32
  • 194
  • 243

3 Answers3

6

@SLacks is correct that you should return IQueryable<object> or IQueryable<someBaseType> if you can.

The error your getting is a function of the DataContract Serializer. So you have a few options.

  1. Switch to an alternate xml serlializer that supports what you want.
  2. Swtitch to a form of output that bypasses the serializer at issue (say JSON using JSON.net)
  3. Teach the data contract serializer how to serialize your object using the

For the "teach" option, you can teach in two ways.

(A) leverage the [KnownType(typeof(...))] attribute. Here's a post on the KnownType attribute. It's for WCF but should get you started.

(B) use a data contract resolver. This post should get you started.

Community
  • 1
  • 1
EBarr
  • 11,826
  • 7
  • 63
  • 85
3

Expanding on what @Ebarr said, the easiest way to accomplish this is to update the various classes which you want to be able to return this way, and have them inherit from a common base class or interface.

Example:

[KnownType(typeof(Address))]
[KnownType(typeof(Order))]
public abstract class _BaseObject { }

public partial class Address : _BaseObject { }
public partial class Order : _BaseObject { }

Now you can have your controller method look like:

public IQueryable<_BaseObject> GetCollection(int id, string collection) {  
    switch(collection) {  
        case "addresses":  
            return _addresses.AsQueryable();
        case "orders":  
            return _orders.AsQueryable();
        default:  
            throw new NotSupportedException();  
    }  
}

Note that the [KnownType] attribute is in System.Runtime.Serialization. You should also be aware that this method will result in exactly what you would expect with regards to JSON serialization - but XML serialization will generally result in tags which show your objects as the base class (because that's what you returned) rather than the sub-classes.

Troy Alford
  • 26,660
  • 10
  • 64
  • 82
0

Just return the non-generic IQueryable.
Or IQueryable<object> via covariance.

SLaks
  • 868,454
  • 176
  • 1,908
  • 1,964
  • Both of these suggestions fail, I'm afraid. I get an exception in both cases. – Jan Ove Halvorsen Apr 12 '12 at 08:29
  • 1) IQueryable gives: System.Runtime.Serialization.SerializationException: Type 'System.Linq.EnumerableQuery`1[[MvcApplication1.Controllers.Address, MvcApplication1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]' with data contract name 'ArrayOfAddress:http://schemas.datacontract.org/2004/07/MvcApplication1.Controllers' is not expected. Consider using a DataContractResolver or add any types not known statically to the list of known types - for example, by using the KnownTypeAttribute attribute or by adding them to the list of known types passed to DataContractSerializer. – Jan Ove Halvorsen Apr 12 '12 at 12:18
  • 2) IQueryable gives: System.Xml.XmlException: You must write an attribute 'type'='object' after writing the attribute with local name '__type'. – Jan Ove Halvorsen Apr 12 '12 at 12:19