ApiQueryOptions 1.0.0-beta5

This is a prerelease version of ApiQueryOptions.
dotnet add package ApiQueryOptions --version 1.0.0-beta5
                    
NuGet\Install-Package ApiQueryOptions -Version 1.0.0-beta5
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="ApiQueryOptions" Version="1.0.0-beta5" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="ApiQueryOptions" Version="1.0.0-beta5" />
                    
Directory.Packages.props
<PackageReference Include="ApiQueryOptions" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add ApiQueryOptions --version 1.0.0-beta5
                    
#r "nuget: ApiQueryOptions, 1.0.0-beta5"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package ApiQueryOptions@1.0.0-beta5
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=ApiQueryOptions&version=1.0.0-beta5&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=ApiQueryOptions&version=1.0.0-beta5&prerelease
                    
Install as a Cake Tool

ApiQueryOptions

NuGet CI

Lightweight OData-style query parameter parsing for ASP.NET Core REST APIs — without the overhead of a full OData stack.

Parses $filter, $expand, $orderby, $top, $skip, and $skiptoken from HTTP query strings and applies them to any IQueryable<T> as deferred LINQ expression trees. Nothing is enumerated until you call .ToList() or .ToListAsync().

For EF Core support ($expand → .Include()), see ApiQueryOptions.EntityFrameworkCore.


Installation

dotnet add package ApiQueryOptions

Quick start

1. Register in Program.cs

builder.Services.AddControllers();
builder.Services.AddApiQueryOptions();

Restrict options globally:

builder.Services.AddApiQueryOptions(new ApiQueryOptionsSettings
{
    ExpandEnabled    = false,
    SkipTokenEnabled = false,
    MaxPageSize      = 100,
    DefaultPageSize  = 25,
});

2. Accept options in a controller action

[HttpGet]
public IActionResult Get(ApiQueryOptions<Product> options)
{
    List<Product> items = _db.Products.Apply(options).ToList();
    return Ok(new { value = items, nextLink = options.NextLink(items.Count) });
}

3. Override settings per action or controller

[ApiController]
[Route("api/[controller]")]
[ApiQueryOptions(DefaultPageSize = 20, MaxPageSize = 100)]
public class ProductsController : ControllerBase
{
    [HttpGet]
    public IActionResult Get(ApiQueryOptions<Product> options) { ... }

    // Method-level attribute wins over controller-level
    [HttpGet("archive")]
    [ApiQueryOptions(MaxPageSize = 500, FilterEnabled = QueryOptionState.Disabled)]
    public IActionResult GetArchive(ApiQueryOptions<Product> options) { ... }
}

Unset properties (0 for integers, QueryOptionState.Default for toggles) inherit from the registered settings unchanged.


Supported query parameters

Both the $-prefixed OData form ($filter=...) and the bare form (filter=...) are accepted.

Parameter Type Description
$filter string Boolean filter expression
$expand string Comma-delimited navigation property paths
$orderby string Sort list: Name asc, CreatedAt desc
$top integer Maximum results to return
$skip integer Results to skip (offset-based paging)
$skiptoken string Opaque cursor token (cursor-based paging)

Filter syntax

Comparison operators

Operator Meaning Example
eq Equals Status eq 'Active'
ne Not equals Status ne 'Deleted'
lt Less than Age lt 30
gt Greater than Price gt 9.99
le Less than or equal Score le 100
ge Greater than or equal Score ge 0

String eq/ne comparisons respect the StringComparison in ApiQueryOptionsSettings (default: OrdinalIgnoreCase).

Logical operators

$filter=Age gt 18 and Age lt 65
$filter=Status eq 'Active' or Status eq 'Pending'
$filter=(Region eq 'US' or Region eq 'CA') and IsActive eq true

String functions

Function Example
startswith(prop, 'value') startswith(Name, 'Al')
endswith(prop, 'value') endswith(Email, '.com')
contains(prop, 'value') contains(Description, 'sale')

Literal types

Type Example
String 'O''Brien' (escape ' as '')
Integer 42, -5
Decimal 9.99
Boolean true, false
Null null

Dotted property paths

$filter=Address.City eq 'Seattle'

Pagination

Offset-based ($top + $skip)

GET /api/products?$top=25&$skip=0
GET /api/products?$top=25&$skip=25

NextLink encodes the current query state into a Base64URL $skiptoken and returns it as the next-page cursor. Returns null on the last page.

[HttpGet]
public IActionResult Get(ApiQueryOptions<Product> options)
{
    List<Product> items = _db.Products.Apply(options).ToList();
    int total = _db.Products.Count();

    return Ok(new
    {
        value    = items,
        nextLink = options.NextLink(items.Count, total),  // null when no more pages
    });
}

The client passes the returned cursor as $skiptoken on the next request:

GET /api/products?$skiptoken=<token>

IQueryable extensions

using ApiQueryOptions.Extensions;

IQueryable<T> result = query.Apply(options);            // filter → orderby → skip → top

query = query.ApplyFilter(options.Filter!, settings);
query = query.ApplyOrderBy(options.OrderBy!, settings);
query = query.ApplySkip(options.Skip!, settings);
query = query.ApplyTop(options.Top!, settings);

Configuration (ApiQueryOptionsSettings)

All properties use init-only setters and default to the most permissive values.

Property Type Default Description
FilterEnabled bool true Accept $filter
ExpandEnabled bool true Accept $expand
OrderByEnabled bool true Accept $orderby
TopEnabled bool true Accept $top
SkipEnabled bool true Accept $skip
SkipTokenEnabled bool true Accept $skiptoken
MaxPageSize int? null Upper limit for $top; silently clamps
DefaultPageSize int? null Default $top when none provided
StringComparison StringComparison OrdinalIgnoreCase Used for string eq/ne and functions
FilterParameterNames IReadOnlyList<string> ["$filter", "filter"] Query keys recognised as $filter
ExpandParameterNames IReadOnlyList<string> ["$expand", "expand"] Query keys recognised as $expand
OrderByParameterNames IReadOnlyList<string> ["$orderby", "orderby"] Query keys recognised as $orderby
TopParameterNames IReadOnlyList<string> ["$top", "top"] Query keys recognised as $top
SkipParameterNames IReadOnlyList<string> ["$skip", "skip"] Query keys recognised as $skip
SkipTokenParameterNames IReadOnlyList<string> ["$skiptoken", "skiptoken"] Query keys recognised as $skiptoken

When an option is disabled, any matching query key is silently ignored and the corresponding property on ApiQueryOptions<T> will be null.

*ParameterNames lists are tried left-to-right; the first match wins. Custom names are automatically excluded from NextLink query-string passthrough.


Error handling

Exception When
FilterParseException $filter contains a syntax error — exposes RawFilter and Position
QueryOptionDisabledException An Apply* method is called directly against a disabled option
FormatException SkipTokenEncoder.Decode receives a malformed or tampered token
InvalidOperationException A filter or orderby expression references a property that does not exist on T

Manual construction

// From an HttpRequest (minimal API or middleware):
ApiQueryOptions<Product> options = ApiQueryOptions.FromRequest<Product>(request, settings);

// From an HttpContext — safe when context is null (returns empty no-op instance):
ApiQueryOptions<Product> options = ApiQueryOptions.FromRequest<Product>(httpContext, settings);

// From an IHttpContextAccessor (in a service):
ApiQueryOptions<Product> options = ApiQueryOptions.FromRequest<Product>(httpContextAccessor, settings);

// From any IQueryCollection:
ApiQueryOptions<Product> options = new(queryCollection, settings);

Source & examples

Full documentation, runnable examples, and source: github.com/MassiveScale/ApiQueryOptions

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net10.0

  • net8.0

NuGet packages (1)

Showing the top 1 NuGet packages that depend on ApiQueryOptions:

Package Downloads
ApiQueryOptions.EntityFrameworkCore

EF Core companion for ApiQueryOptions — applies expand (Include), filter, orderby, skip, and top as a single deferred SQL query.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.0-beta5 1,006 6/14/2026
1.0.0-beta4 81 6/13/2026
1.0.0-beta3 76 6/12/2026
1.0.0-beta2 87 6/10/2026
1.0.0-beta1 69 6/10/2026