4
votes

I'm struggling to add OData v4 query support to a method on a controller that inherits from ApiController rather than ODataController. While I have a working OData model in the solution there are some endpoints that don't really belong in the model but the power of the query would be useful.

I've seen some articles suggesting I can just return an IQueryable and use EnableQuery.

Here is my sample code:

public class TestController : ApiController
{
    [HttpGet]    
    [EnableQuery]
    public IQueryable<TestObject> Events()
    {
        var result = new[] { new TestObject { Id = "1" } }.AsQueryable();
        return result;
    }
}

public class TestObject
{
    public string Id { get; set; }
}

All I get back when a call /Test/Events is a 406 Not Acceptable, something that I've run into a lot dealing with OData that usually means I've return something the OData framework doesn't like. I've never been able to get anymore info out of the framework (a major failing I think) as to why it doesn't like it and only solved them by trial and error in the past.

Has anyone got this setup working and if so how?

Or any suggestions on debugging the 406 response?

EDIT:

OK so it turned out the culprit was so code in the start up that was registering a custom ODataMediaTypeFormatter and in the process was clearing all other formatters.

After removing the offending code it worked.

Really really wish WebApi would record somewhere what caused the 406 error when it spits one out.

1
How are you declared the route ?nlips
I haven't, I have just used the conventions of Controller/Action. It works fine when I remove EnableQuery.Mant101
Does changing action name Events() to Get() work?eoghank
Nope, with EnableQuery its my old friend the 406, without it works when I rename with a GET to /TestMant101

1 Answers

0
votes

You can apply the "query" magic manually, without [EnableQuery].

  1. Create a ODataQueryContext, passing in your model (you'll need to save it in global and make it available), and the entity type your are querying against.
  2. Create an HttpRequestMessage using Get and the current url (Request.RequestUri.AbsoluteUri).
  3. Create a new ODataQueryOptions<yourEntity>, passing in the context and request message you created.
  4. Call that object's ApplyTo using your entity.AsQueryable(), casting the result as an IQueryable<yourEntity>.

Further manipulate needed and return. Keep in mind that return type does not need to be IQueryable<yourEntity>, or even based upon "yourEntity".

Here is a similar implementation that does this for an ODataController method ( as a base ODataQueryOptions comes for free):

        public IEnumerable<Aggregate> GetOrders(ODataQueryOptions<ReportingOrder> opts, [FromUri(Name="$groupby")]string groupby, [FromUri(Name="$aggregates")]string aggregates = null)
        {
            var url = opts.Request.RequestUri.AbsoluteUri;


            int? top = null;

            if (opts.Top != null)
            {
                top = int.Parse(opts.Top.RawValue);

                var topStr = string.Format("$top={0}", top.Value);
                url = url.Replace(topStr, "");
                var req = new HttpRequestMessage(HttpMethod.Get, url);

                opts = new ODataQueryOptions<ReportingOrder>(opts.Context, req);

            }

            var query = opts.ApplyTo(db.ReportingOrders.AsQueryable()) as IQueryable<ReportingOrder>;

            var results = query.GroupBy(groupby, aggregates, top);

            return results;
        }