private IEnumerable <T> Query(ResourceQuery query, CancellationToken cancellationToken) { IEnumerable <T> results = _service.Get(query, cancellationToken); // // "Developers who want to know the full number of records across all pages, MAY include the query parameter // $count=true to tell the server to include the count of items in the response. // if (query.TotalRows.HasValue) { HttpContext.Items.TryAdd(Constants.CountResultContextKey, query.TotalRows.Value); } HttpContext.Items.TryAdd(Constants.QueryContextKey, query); return(results); }
private static bool BuildResourceQuery(IResourceDataService service, HttpContext context, IOptions <ApiOptions> options, out ResourceQuery query) { query = new ResourceQuery(); if (service.SupportsSorting && context.Items.TryGetValue(Constants.SortContextKey, out var sortMap) && sortMap != null) { query.Sorting = (List <(AccessorMember, SortDirection)>)sortMap; context.Items.Remove(Constants.SortContextKey); } if (service.SupportsFiltering && context.Items.TryGetValue(Constants.FilterOperationContextKey, out var filterValue) && filterValue is List <(string, FilterOperator, string?)> filters) { query.Filters = filters; } if (service.SupportsCount) { // We don't check whether the request asked for counting, because we need counting to determine next page results either way, // so we'll attempt to always count the total records if it's supported. query.CountTotalRows = true; context.Items.Remove(Constants.CountContextKey); } if (service.SupportsSkip && context.Items.TryGetValue(Constants.SkipOperationContextKey, out var skipValue) && skipValue is int skip) { query.PageOffset = skip; context.Items.Remove(Constants.SkipOperationContextKey); } if (service.SupportsTop & context.Items.TryGetValue(Constants.TopOperationContextKey, out var topValue) && topValue is int top) { // // "Note that client-driven paging does not preclude server-driven paging. // If the page size requested by the client is larger than the default page size // supported by the server, the expected response would be the number of results specified by the client, // paginated as specified by the server paging settings." // query.PageSize = top; context.Items.Remove(Constants.TopOperationContextKey); } if (service.SupportsMaxPageSize && context.Items.TryGetValue(Constants.MaxPageSizeContextKey, out var maxPageSizeValue) && maxPageSizeValue is int maxPageSize) { query.MaxPageSize = maxPageSize; context.Items.Remove(Constants.MaxPageSizeContextKey); } if (service.SupportsShaping && context.Items.TryGetValue(Constants.ShapingOperationContextKey, out var shapingValue) && shapingValue is List <string> include) { query.Fields = include; context.Items.Remove(Constants.ShapingOperationContextKey); } // If no $skip is provided, assume the query is for the first page query.PageOffset ??= 0; // // "Clients MAY request server-driven paging with a specific page size by specifying a $maxpagesize preference. // The server SHOULD honor this preference if the specified page size is smaller than the server's default page size." // // Interpretation: // - If the client specifies $top, use $top as the page size. // - If the client omits $top but specifies $maxpagesize, use the smaller of $maxpagesize and the server's default page size. if (!query.PageSize.HasValue) { if (query.MaxPageSize.HasValue && query.MaxPageSize.Value < options.Value.Paging.MaxPageSize.DefaultPageSize) { query.PageSize = query.MaxPageSize.Value; } else { query.PageSize = options.Value.Paging.MaxPageSize.DefaultPageSize; } } // If we still don't have a page size, assume the query is for the default page size if (!query.PageSize.HasValue || query.PageSize.Value == 0) { query.PageSize = options.Value.Paging.MaxPageSize.DefaultPageSize; } // // "If the server can't honor $top and/or $skip, // the server MUST return an error to the client informing about it instead of just ignoring the query options. // This will avoid the risk of the client making assumptions about the data returned." // if (query.PageSize.Value > options.Value.Paging.MaxPageSize.MaxPageSize) { return(false); } if (service.SupportsSearch && context.Items.TryGetValue(Constants.SearchOperationContextKey, out var searchValue) && searchValue is string searchQuery) { query.SearchQuery = searchQuery; context.Items.Remove(Constants.SearchOperationContextKey); } return(true); }