Custom ODataQueryOptions for EnableQueryAttribute

Topics: ASP.NET Web API
Nov 3, 2014 at 11:10 PM
Good Afternoon,

Within the ODATA libraries, we're struggling a little with the use of EnableQueryAttribute (or more specifically the default behaviour of the refernced ODataQueryOptions), as we wish to disable the automated sorting, paging and filtering capability, while still retaining other features such as validation and 'select'.

As part of the ExecuteQuery method an instance of ODataQueryOptions is instantiated. Is there any chance that this instantiation could be refactored to a property or virtual method so that we could provide a subclassed implementation of ODataQueryOptions?

Regards,

Nick
Coordinator
Nov 3, 2014 at 11:29 PM
Have you looked at using OData Query validation to restrict the set of allowed queries?:

http://www.asp.net/web-api/overview/odata-support-in-aspnet-web-api/supporting-odata-query-options#query-validation

Daniel Roth
Microsoft
Nov 3, 2014 at 11:42 PM
Hi Daniel,

Thanks for getting back to me so quickly.

Yes, through trial and error we've been able to get the behaviour we are after - it's just resulted in a bit of extra code for each action that doesn't quite feel 'tidy'. The following snippet is what we have ended up with (along with a modified version of ODataQueryOptions) - however it seems a shame to lose the encapsulation that EnableQuery provides simply because we cannot override the instance of ODataQueryOptions.
 public IHttpActionResult GetChildren(ODataQueryOptionsSelectOnly<Customer> odataQuery, [FromODataUri]  string partyNumber)
 {

            var validationSettings = new ODataValidationSettings();
            validationSettings.AllowedQueryOptions = AllowedQueryOptions.Skip | AllowedQueryOptions.Top | AllowedQueryOptions.Select;
            odataQuery.Validate(validationSettings);

            // Read Skip, Top
            
            // Perform query, including the sorting,paging etc.

            // Apply the customised ODataQueryOptions implementation 
            // This basically just adds the support for $select
            IQueryable result = odataQuery.ApplyTo(children.AsQueryable());

            return Ok(result);
 }
An alternative to this request might be to have flags added to ODataQueryOptions to disable the automated application of features such as $skip, $top, $orderby and $filter?

Regards,

Nick
Coordinator
Nov 3, 2014 at 11:58 PM
The EnableQueryAttribute exposes an AllowedQueryOptions property directly. Does this work for your scenario?:
[EnableQuery(AllowedQueryOptions=AllowedQueryOptions.Skip | AllowedQueryOptions.Top | AllowedQueryOptions.Select)]
Daniel Roth
Microsoft
Nov 4, 2014 at 12:49 AM
Hi Daniel,

Sorry, I think I may have jumped straight to proposing a solution, without properly describing the problem we encountered. The issue we’re having is with the implementation of paging, rather than the validation of allowed query options. The validation code in the snippet above may have caused confusion – this is required as a side effect of the fact we cannot use EnableQuery directly.

Let me rewind a little, using the following resource request as an example
https://.../Customers(123)/Children?$select=Name,$size=10,$top=10

When responding to this request ODataQueryOptions is expecting an IQueryable instance that the size and top parameters can be applied to – or at the very least an in-memory list of more than 10 items (to be able to respond to page 2).

The datasource we are using is not directly IQueryable, but rather is a SOAP web service. The data involved is moderately large, and therefore the SOAP service is responsible for its own paging, sorting and filtering. The result is an in-memory list of only page 2 (10 items) being returned from the action – so when ODataQueryOptions applies the $skip parameter there are no records returned.

So for the paging example we needed to remove the following code from within ODataQueryOptions ApplyTo in order for this to work.
public virtual IQueryable ApplyTo(IQueryable query, ODataQuerySettings querySettings)
{
         ...
         if (Skip != null)
         {
                result = Skip.ApplyTo(result, querySettings);
         }

         if (Top != null)
         {
                result = Top.ApplyTo(result, querySettings);
         }
I'd imagine that that this is a fairly common scenario - unfortunitely not all data sources are able to implement IQueryable (especially legacy services etc), so my suggestion was around trying to provide some flexibility in enabling/disabling query parameters from being automatically applied, or being able to provide a modified instance of ODataQueryOptions with a reduced set of functionallity.

Regards,

Nick
Coordinator
Nov 4, 2014 at 5:04 PM
To run OData queries on backend data sources that don't support IQueryable it is a bit more work, but it is doable. Please take a look at the following sample and see if it helps:

https://aspnet.codeplex.com/SourceControl/latest#Samples/WebApi/OData/v4/NHibernateQueryableSample/Readme.txt

Daniel Roth
Microsoft
Nov 7, 2014 at 12:45 AM
Hi Daniel,

Thanks for the response - I'd previously looked at the NHibernate example and felt it was a bit more work than we had time for, but I'll take another look over the next couple of days.

One thing I did like about it compared to the solution that we've put in place was the usage of extension methods rather than creating a subclass of ODataQueryOptions - however I note that this solution still wouldn't work with the EnableQuery attribute.

Regards,

Nick