ASP .NET MVC 4 WebApi: Manually handle OData queries

23,386

Solution 1

I just stumbled across this old post and I'm adding this answer as it's now very easy to handle the OData queries yourself. Here's an example:

[HttpGet]
[ActionName("Example")]
public IEnumerable<Poco> GetExample(ODataQueryOptions<Poco> queryOptions)
{
    var data = new Poco[] { 
        new Poco() { id = 1, name = "one", type = "a" },
        new Poco() { id = 2, name = "two", type = "b" },
        new Poco() { id = 3, name = "three", type = "c" }
    };

    var t = new ODataValidationSettings() { MaxTop = 2 };
    queryOptions.Validate(t);

    //this is the method to filter using the OData framework
    //var s = new ODataQuerySettings() { PageSize = 1 };
    //var results = queryOptions.ApplyTo(data.AsQueryable(), s) as IEnumerable<Poco>;

    //or DIY
    var results = data;
    if (queryOptions.Skip != null) 
        results = results.Skip(queryOptions.Skip.Value);
    if (queryOptions.Top != null)
        results = results.Take(queryOptions.Top.Value);

    return results;
}

public class Poco
{
    public int id { get; set; }
    public string name { get; set; }
    public string type { get; set; }
}

Solution 2

The query from the URL gets translated into a LINQ expression tree which is then executed against the IQueryable your operation returns. You can analyze the expression and provide the results in any way you want. The downside is that you need to implement IQueryable which is not super easy. Take a look at this blog post series if you're interested: http://blogs.msdn.com/b/vitek/archive/2010/02/25/data-services-expressions-part-1-intro.aspx. It talks about WCF Data Services, but the filter expressions used by the Web API will be very similar.

Solution 3

One way With Web-api would be with customer message handler http://www.asp.net/web-api/overview/working-with-http/http-message-handlers

Write a custom handler like below:

public class CustomHandler : DelegatingHandler
    {
        protected override Task<HttpResponseMessage> SendAsync(
            HttpRequestMessage request, CancellationToken cancellationToken)
        {
            return base.SendAsync(request, cancellationToken).ContinueWith(
                (task) =>
                {
                    HttpResponseMessage response = task.Result;
                    var persons = response.Content.ReadAsAsync<IQueryable<Person>>().Result;
                    var persons2 = new List<Person>(); //This can be the modified model completely different
                    foreach (var item in persons)
                    {
                        item.Name = "changed"; // here you can change the data
                        //persons2.Add(....); //Depending on the results modify this custom model
                    }
                    //overwrite the response
                    response = new HttpResponseMessage<IEnumerable<Person>>(persons2); 
                    return response;
                }
            );
        }
    }

Register in global.asax.cs

Method in application class:

static void Configure(HttpConfiguration config)
 {
     config.MessageHandlers.Add(new CustomHandler()); 
 }

protected void Application_Start()
{
     ....
     .....
     //call the configure method
     Configure(GlobalConfiguration.Configuration);
 }
Share:
23,386
frapontillo
Author by

frapontillo

Updated on July 09, 2022

Comments

  • frapontillo
    frapontillo almost 2 years

    I have a Web Service made using the WebAPI provided by ASP .NET MVC 4. I know that the layer on top of which WebAPI works automatically handles OData Queries (such as $filter, $top, $skip), but what if I want to handle the filtering by myself?

    I don't simply return data from my database, but I have another layer which adds some properties, makes some conversions etc. So querying ALL of my data, converting them and returning them to the WebAPI class for OData filtering isn't just good enough. It's of course terribly slow, and generally a crappy idea.

    So is there a way to propagate the OData query parameters from my WebAPI entry point to the functions I call to get and convert the data?

    For example, a GET to /api/people?$skip=10&$top=10 would call on the server:

    public IQueryable<Person> get() {
        return PersonService.get(SomethingAboutCurrentRequest.CurrentOData);
    }
    

    And in PersonService:

    public IQueryable<Person> getPeople(var ODataQueries) {
        IQueryable<ServerSidePerson> serverPeople = from p in dbContext.ServerSidePerson select p;
        // Make the OData queries
        // Skip
        serverPeople = serverPeople.Skip(ODataQueries.Skip);
        // Take
        serverPeople = serverPeople.Take(ODataQueries.Take);
        // And so on
        // ...
    
        // Then, convert them
        IQueryable<Person> people = Converter.convertPersonList(serverPeople);
        return people;
    }