-
Notifications
You must be signed in to change notification settings - Fork 712
OData Query Options
OData query option conventions allow you to specify information for your OData services without having to rely solely on .NET attributes. There are a number of reasons why you might uses these conventions. The most common reasons are:
- Centralized management and application of all OData query options
- Define OData query options that cannot be expressed with any OData query attributes
- Apply OData query options to services defined by controllers in external .NET assemblies
The parameter names generated are based on the name of the OData query option and the configuration of the ODataUriResolver. OData supports query options without the system $
prefix. This is enabled or disabled by the ODataUriResolver.EnableNoDollarQueryOptions
property.
The attribute model relies on Model Bound attributes and the EnableQueryAttribute. The EnableQueryAttribute indicates options that cannot otherwise be set such as MaxTop
, MaxSkip
, allowed functions, and so on. Consider the following model and controller definitions.
using System;
using Microsoft.AspNet.OData.Query;
using static Microsoft.AspNet.OData.Query.SelectExpandType;
[Select]
[Select( "effectiveDate", SelectType = Disabled )]
public class Order
{
public int Id { get; set; }
public DateTime CreatedDate { get; set; } = DateTime.Now;
public DateTime EffectiveDate { get; set; } = DateTime.Now;
public string Customer { get; set; }
public string Description { get; set; }
}
using Microsoft.AspNet.OData;
using Microsoft.AspNet.OData.Routing;
using Microsoft.Web.Http;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Http;
using System.Web.Http.Description;
using static Microsoft.AspNet.OData.Query.AllowedQueryOptions;
using static System.Net.HttpStatusCode;
using static System.DateTime;
[ApiVersion( "1.0" )]
[ODataRoutePrefix( "Orders" )]
public class OrdersController : ODataController
{
[ODataRoute]
[Produces( "application/json" )]
[ProducesResponseType( typeof( ODataValue<IEnumerable<Order>> ), Status200OK )]
[EnableQuery( MaxTop = 100, AllowedQueryOptions = Select | Top | Skip | Count )]
public IQueryable<Order> Get()
{
var orders = new[]
{
new Order(){ Id = 1, Customer = "John Doe" },
new Order(){ Id = 2, Customer = "John Doe" },
new Order(){ Id = 3, Customer = "Jane Doe", EffectiveDate = UtcNow.AddDays( 7d ) }
};
return orders.AsQueryable();
}
[ODataRoute( "({key})" )]
[Produces( "application/json" )]
[ProducesResponseType( typeof( Order ), Status200OK )]
[ProducesResponseType( Status404NotFound )]
[EnableQuery( AllowedQueryOptions = Select )]
public SingleResult<Order> Get( int key )
{
var orders = new[] { new Order(){ Id = key, Customer = "John Doe" } };
return SingleResult.Create( orders.AsQueryable() );
}
}
using Microsoft.AspNet.OData;
using Microsoft.AspNet.OData.Routing;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using static Microsoft.AspNet.OData.Query.AllowedQueryOptions;
using static Microsoft.AspNetCore.Http.StatusCodes;
using static System.DateTime;
[ApiVersion( "1.0" )]
[ODataRoutePrefix( "Orders" )]
public class OrdersController : ODataController
{
[ODataRoute]
[Produces( "application/json" )]
[ProducesResponseType( typeof( ODataValue<IEnumerable<Order>> ), Status200OK )]
[EnableQuery( MaxTop = 100, AllowedQueryOptions = Select | Top | Skip | Count )]
public IQueryable<Order> Get()
{
var orders = new[]
{
new Order(){ Id = 1, Customer = "John Doe" },
new Order(){ Id = 2, Customer = "John Doe" },
new Order(){ Id = 3, Customer = "Jane Doe", EffectiveDate = UtcNow.AddDays(7d) }
};
return orders.AsQueryable();
}
[ODataRoute( "({key})" )]
[Produces( "application/json" )]
[ProducesResponseType( typeof( Order ), Status200OK )]
[ProducesResponseType( Status404NotFound )]
[EnableQuery( AllowedQueryOptions = Select )]
public SingleResult<Order> Get( int key )
{
var orders = new[] { new Order(){ Id = key, Customer = "John Doe" } };
return SingleResult.Create( orders.AsQueryable() );
}
}
The OData API Explorer will discover and add add the following parameters for an entity set query:
Name | Description | Parameter Type | Data Type |
---|---|---|---|
$select | Limits the properties returned in the result. The allowed properties are: id, createdDate, customer, description. | query | string |
$top | Limits the number of items returned from a collection. The maximum value is 100. | query | integer |
$skip | Excludes the specified number of items of the queried collection from the result. | query | integer |
The convention model relies on Model Bound attributes and the new Query Option conventions. The query option conventions configure options that cannot otherwise be set such as MaxTop
, MaxSkip
, allowed functions, and so on. Consider the following model and controller definitions.
public class Person
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public string Phone { get; set; }
}
public class PersonModelConfiguration : IModelConfiguration
{
public void Apply( ODataModelBuilder builder, ApiVersion apiVersion )
{
var person = builder.EntitySet<Person>( "People" ).EntityType;
person.HasKey( p => p.Id );
// configure model bound conventions
person.Select().OrderBy( "firstName", "lastName" );
if ( apiVersion < ApiVersions.V3 )
{
person.Ignore( p => p.Phone );
}
if ( apiVersion <= ApiVersions.V1 )
{
person.Ignore( p => p.Email );
}
if ( apiVersion > ApiVersions.V1 )
{
var function = person.Collection.Function( "NewHires" );
function.Parameter<DateTime>( "Since" );
function.ReturnsFromEntitySet<Person>( "People" );
}
if ( apiVersion > ApiVersions.V2 )
{
person.Action( "Promote" ).Parameter<string>( "title" );
}
}
}
using Microsoft.AspNet.OData;
using Microsoft.AspNet.OData.Routing;
using Microsoft.Web.Http;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Http;
using System.Web.Http.Description;
using static Microsoft.AspNet.OData.Query.AllowedQueryOptions;
using static System.Net.HttpStatusCode;
using static System.DateTime;
public class PeopleController : ODataController
{
[HttpGet]
[ResponseType( typeof( ODataValue<IEnumerable<Person>> ) )]
public IHttpActionResult Get( ODataQueryOptions<Person> options )
{
var validationSettings = new ODataValidationSettings()
{
AllowedQueryOptions = Select | OrderBy | Top | Skip | Count,
AllowedOrderByProperties = { "firstName", "lastName" },
AllowedArithmeticOperators = AllowedArithmeticOperators.None,
AllowedFunctions = AllowedFunctions.None,
AllowedLogicalOperators = AllowedLogicalOperators.None,
MaxOrderByNodeCount = 2,
MaxTop = 100,
};
try
{
options.Validate( validationSettings );
}
catch ( ODataException )
{
return BadRequest();
}
var people = new[]
{
new Person()
{
Id = 1,
FirstName = "John",
LastName = "Doe",
Email = "john.doe@somewhere.com",
Phone = "555-987-1234",
},
new Person()
{
Id = 2,
FirstName = "Bob",
LastName = "Smith",
Email = "bob.smith@somewhere.com",
Phone = "555-654-4321",
},
new Person()
{
Id = 3,
FirstName = "Jane",
LastName = "Doe",
Email = "jane.doe@somewhere.com",
Phone = "555-789-3456",
}
};
return this.Success( options.ApplyTo( people.AsQueryable() ) );
}
[HttpGet]
[ResponseType( typeof( Person ) )]
public IHttpActionResult Get( int key, ODataQueryOptions<Person> options )
{
var people = new[]
{
new Person()
{
Id = key,
FirstName = "John",
LastName = "Doe",
Email = "john.doe@somewhere.com",
Phone = "555-987-1234",
}
};
var query = options.ApplyTo( people.AsQueryable();
return this.SuccessOrNotFound( query ).SingleOrDefault() );
}
}
using Microsoft.AspNet.OData;
using Microsoft.AspNet.OData.Routing;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using static Microsoft.AspNet.OData.Query.AllowedQueryOptions;
using static Microsoft.AspNetCore.Http.StatusCodes;
using static System.DateTime;
public class PeopleController : ODataController
{
[Produces( "application/json" )]
[ProducesResponseType( typeof( ODataValue<IEnumerable<Person>> ), Status200OK )]
public IActionResult Get( ODataQueryOptions<Person> options )
{
var validationSettings = new ODataValidationSettings()
{
AllowedQueryOptions = Select | OrderBy | Top | Skip | Count,
AllowedOrderByProperties = { "firstName", "lastName" },
AllowedArithmeticOperators = AllowedArithmeticOperators.None,
AllowedFunctions = AllowedFunctions.None,
AllowedLogicalOperators = AllowedLogicalOperators.None,
MaxOrderByNodeCount = 2,
MaxTop = 100,
};
try
{
options.Validate( validationSettings );
}
catch ( ODataException )
{
return BadRequest();
}
var people = new[]
{
new Person()
{
Id = 1,
FirstName = "John",
LastName = "Doe",
Email = "john.doe@somewhere.com",
Phone = "555-987-1234",
},
new Person()
{
Id = 2,
FirstName = "Bob",
LastName = "Smith",
Email = "bob.smith@somewhere.com",
Phone = "555-654-4321",
},
new Person()
{
Id = 3,
FirstName = "Jane",
LastName = "Doe",
Email = "jane.doe@somewhere.com",
Phone = "555-789-3456",
}
};
return Ok( options.ApplyTo( people.AsQueryable() ) );
}
[Produces( "application/json" )]
[ProducesResponseType( typeof( Person ), Status200OK )]
[ProducesResponseType( Status404NotFound )]
public IActionResult Get( int key, ODataQueryOptions<Person> options )
{
var people = new[]
{
new Person()
{
Id = key,
FirstName = "John",
LastName = "Doe",
Email = "john.doe@somewhere.com",
Phone = "555-987-1234",
}
};
var person = options.ApplyTo( people.AsQueryable() ).SingleOrDefault();
if ( person == null )
{
return NotFound();
}
return Ok( person );
}
}
OData does not provide a mechanism to express some OData query options by convention; for example MaxTop
. These conventions, however, can be expressed in the OData API Explorer options:
using static Microsoft.AspNet.OData.Query.AllowedQueryOptions;
.AddODataApiExplorer( options =>
{
var queryOptions = options.QueryOptions;
// configure query options (which cannot otherwise be configured by OData conventions)
queryOptions.Controller<V2.PeopleController>()
.Action( c => c.Get( default( ODataQueryOptions<Person> ) ) )
.Allow( Skip | Count ).AllowTop( 100 );
queryOptions.Controller<V3.PeopleController>()
.Action( c => c.Get( default( ODataQueryOptions<Person> ) ) )
.Allow( Skip | Count ).AllowTop( 100 );
} );
The OData API Explorer will discover and add add the following parameters for an entity set query:
Name | Description | Parameter Type | Data Type |
---|---|---|---|
$select | Limits the properties returned in the result. | query | string |
$orderby | Specifies the order in which results are returned. The allowed properties are: firstName, lastName. | query | string |
$top | Limits the number of items returned from a collection. The maximum value is 100. | query | integer |
$skip | Excludes the specified number of items of the queried collection from the result. | query | integer |
While each OData query option has a default provided description, the description can be changed by providing a custom description. Descriptions are generated by the IODataQueryOptionDescriptionProvider:
public interface IODataQueryOptionDescriptionProvider
{
string Describe(
AllowedQueryOptions queryOption,
ODataQueryOptionDescriptionContext context );
}
Note: Although AllowedQueryOptions is a bitwise enumeration, only a single query option value is ever passed
You can change the default description by implementing your own IODataQueryOptionDescriptionProvider or extending the built-in DefaultODataQueryOptionDescriptionProvider. The implementation is updated in the OData API Explorer options using:
.AddODataApiExplorer( options =>
{
var queryOptions = options.QueryOptions;
queryOptions.DescriptionProvider = new MyODataQueryOptionDescriptionProvider();
} );
You can also define custom conventions via the IODataQueryOptionsConvention interface and add them to the builder:
public interface IODataQueryOptionsConvention
void ApplyTo( ApiDescription apiDescription );
}
.AddODataApiExplorer( options =>
{
options.QueryOptions.Add( new MyODataQueryOptionsConvention() );
} );
- Home
- Quick Starts
- Version Format
- Version Discovery
- Version Policies
- How to Version Your Service
- API Versioning with OData
- Configuring Your Application
- Error Responses
- API Documentation
- Extensions and Customizations
- Known Limitations
- FAQ
- Examples