/// <inheritdoc /> public virtual ODataSerializer GetODataPayloadSerializer(HttpContext context, Type type) { if (context == null) { throw Error.ArgumentNull("context"); } if (type == null) { throw Error.ArgumentNull("type"); } IServiceProvider provider = context.RequestServices; var x = context.Response.StatusCode; // handle the special types. if (type == typeof(ODataServiceDocument)) { return(provider.GetRequiredService <ODataServiceDocumentSerializer>()); } else if (type == typeof(Uri) || type == typeof(ODataEntityReferenceLink)) { return(provider.GetRequiredService <ODataEntityReferenceLinkSerializer>()); } else if (typeof(IEnumerable <Uri>).IsAssignableFrom(type) || type == typeof(ODataEntityReferenceLinks)) { return(provider.GetRequiredService <ODataEntityReferenceLinksSerializer>()); } else if (type == typeof(ODataError) || type == typeof(SerializableError)) { return(provider.GetRequiredService <ODataErrorSerializer>()); } else if (typeof(IEdmModel).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo())) { return(provider.GetRequiredService <ODataMetadataSerializer>()); } // if it is not a special type, assume it has a corresponding EdmType. IEdmModel model = context.ODataFeature().Model; ClrTypeCache typeMappingCache = model.GetTypeMappingCache(); IEdmTypeReference edmType = typeMappingCache.GetEdmType(type, model); if (edmType != null) { if (((edmType.IsPrimitive() || edmType.IsEnum()) && ODataRawValueMediaTypeMapping.IsRawValueRequest(context)) || ODataCountMediaTypeMapping.IsCountRequest(context)) { return(provider.GetRequiredService <ODataRawValueSerializer>()); } else { return(GetEdmTypeSerializer(context, edmType)); } } else { return(null); } }
public override void OnActionExecuted(ActionExecutedContext context) { if (context == null) { throw Error.ArgumentNull("context"); } var response = context.HttpContext.Response; if (!response.IsSuccessStatusCode()) { return; } var request = context.HttpContext.Request; if (request.HasQueryOptions() || ODataCountMediaTypeMapping.IsCountRequest(request) || context.ActionDescriptor.HasQueryOption()) { var result = context.Result as ObjectResult; if (result == null) { throw Error.Argument("context", SRResources.QueryingRequiresObjectContent, context.Result.GetType().FullName); } if (result.Value != null) { result.Value = ApplyQueryOptions(result.Value, request, context.ActionDescriptor, context.HttpContext.RequestServices.GetService <AssembliesResolver>()); } } }
public virtual object ApplyQueryOptions(object value, HttpRequest request, ActionDescriptor actionDescriptor, AssembliesResolver assembliesResolver) { var elementClrType = value is IEnumerable ? TypeHelper.GetImplementedIEnumerableType(value.GetType()) : value.GetType(); var model = request.ODataProperties().Model; if (model == null) { throw Error.InvalidOperation(SRResources.QueryGetModelMustNotReturnNull); } var queryContext = new ODataQueryContext( model, elementClrType, assembliesResolver, request.ODataProperties().Path ); var queryOptions = new ODataQueryOptions(queryContext, request, assembliesResolver); var enumerable = value as IEnumerable; if (enumerable == null) { // response is single entity. return(value); } // response is a collection. var query = (value as IQueryable) ?? enumerable.AsQueryable(); query = queryOptions.ApplyTo(query, new ODataQuerySettings { // TODO: If we are using SQL, set this to false // otherwise if it is entities in code then // set it to true HandleNullPropagation = //HandleNullPropagationOption.True HandleNullPropagationOptionHelper.GetDefaultHandleNullPropagationOption(query), PageSize = actionDescriptor.PageSize(), SearchDerivedTypeWhenAutoExpand = true }, AllowedQueryOptions.None); // Determine if this result should be a single entity if (ODataCountMediaTypeMapping.IsCountRequest(request)) { long?count = request.ODataProperties().TotalCount; if (count.HasValue) { // Return the count value if it is a $count request. return(count.Value); } } return(query); }
/// <inheritdoc /> public override ODataSerializer GetODataPayloadSerializer(IEdmModel model, Type type, HttpRequestMessage request) { if (model == null) { throw Error.ArgumentNull("model"); } if (type == null) { throw Error.ArgumentNull("type"); } if (request == null) { throw Error.ArgumentNull("request"); } // handle the special types. if (type == typeof(ODataServiceDocument)) { return(_workspaceSerializer); } else if (type == typeof(Uri) || type == typeof(ODataEntityReferenceLink)) { return(_entityReferenceLinkSerializer); } else if (typeof(IEnumerable <Uri>).IsAssignableFrom(type) || type == typeof(ODataEntityReferenceLinks)) { return(_entityReferenceLinksSerializer); } else if (type == typeof(ODataError) || type == typeof(HttpError)) { return(_errorSerializer); } else if (typeof(IEdmModel).IsAssignableFrom(type)) { return(_metadataSerializer); } // if it is not a special type, assume it has a corresponding EdmType. ClrTypeCache typeMappingCache = model.GetTypeMappingCache(); IEdmTypeReference edmType = typeMappingCache.GetEdmType(type, model); if (edmType != null) { if (((edmType.IsPrimitive() || edmType.IsEnum()) && ODataRawValueMediaTypeMapping.IsRawValueRequest(request)) || ODataCountMediaTypeMapping.IsCountRequest(request)) { return(_rawValueSerializer); } else { return(GetEdmTypeSerializer(edmType)); } } else { return(null); } }
/// <summary> /// Returns true if the query should be applied /// </summary> /// <param name="responseContent">The response content</param> /// <param name="request">The incoming request</param> /// <param name="actionDescriptor">>The action descriptor for the action being queried on.</param> /// <returns></returns> protected virtual bool ShouldApplyQuery(ObjectContent responseContent, HttpRequestMessage request, HttpActionDescriptor actionDescriptor) { bool shouldApplyQuery = responseContent.Value != null && request.RequestUri != null && (!String.IsNullOrWhiteSpace(request.RequestUri.Query) || _querySettings.PageSize.HasValue || responseContent.Value is SingleResult || ODataCountMediaTypeMapping.IsCountRequest(request) || ContainsAutoExpandProperty(responseContent.Value, request, actionDescriptor)); return(shouldApplyQuery); }
private object ExecuteQuery(object response, HttpRequestMessage request, HttpActionDescriptor actionDescriptor, ODataQueryContext queryContext) { if (queryContext == null) { queryContext = GetODataQueryContext(response, request, actionDescriptor); } ODataQueryOptions queryOptions = new ODataQueryOptions(queryContext, request); ValidateQuery(request, queryOptions); // apply the query IEnumerable enumerable = response as IEnumerable; if (enumerable == null || response is string || response is byte[]) { // response is not a collection; we only support $select and $expand on single entities. ValidateSelectExpandOnly(queryOptions); SingleResult singleResult = response as SingleResult; if (singleResult == null) { // response is a single entity. return(ApplyQuery(entity: response, queryOptions: queryOptions)); } else { // response is a composable SingleResult. ApplyQuery and call SingleOrDefault. IQueryable queryable = singleResult.Queryable; queryable = ApplyQuery(queryable, queryOptions); return(SingleOrDefault(queryable, actionDescriptor)); } } else { // response is a collection. IQueryable queryable = (enumerable as IQueryable) ?? enumerable.AsQueryable(); queryable = ApplyQuery(queryable, queryOptions); if (ODataCountMediaTypeMapping.IsCountRequest(request)) { long?count = request.ODataProperties().TotalCount; if (count.HasValue) { // Return the count value if it is a $count request. return(count.Value); } } return(queryable); } }
public override void OnActionExecuted(ActionExecutedContext context) { if (context == null) { throw Error.ArgumentNull("context"); } var response = context.HttpContext.Response; if (!response.IsSuccessStatusCode()) { return; } var request = context.HttpContext.Request; var result = context.Result as ObjectResult; if (request.HasQueryOptions() || ODataCountMediaTypeMapping.IsCountRequest(request) || context.ActionDescriptor.HasQueryOption()) { if (result == null) { throw Error.Argument("context", SRResources.QueryingRequiresObjectContent, context.Result.GetType().FullName); } if (result.Value != null) { result.Value = ApplyQueryOptions(result.Value, request, context.ActionDescriptor, context.HttpContext.RequestServices.GetService <AssembliesResolver>()); } } if (result != null && ShouldBeSingleEntity(request.ODataProperties().Path.PathTemplate)) { var queryable = result.Value as IQueryable; if (queryable != null) { result.Value = SingleOrDefault(queryable, context.ActionDescriptor); } } }
protected virtual async Task <object> ApplyQueryOptionsAsync(object value, ODataQueryOptions options) { var enumerable = value as IEnumerable; if (enumerable == null || value is string) { // response is not a collection; we only support $select and $expand on single entities. //ValidateSelectExpandOnly(queryOptions); //options.Request.ODataFeature().IsEnumerated = true; var singleResult = value as SingleResult; if (singleResult == null) { // response is a single entity. return(await ApplyQueryObjectAsync(value, options, false)); } // response is a composable SingleResult. ApplyQuery and call SingleOrDefault. var singleQueryable = singleResult.Queryable; singleQueryable = await ApplyQueryAsync(singleQueryable, options, true); return(SingleOrDefault(singleQueryable)); } // response is a collection. var query = (value as IQueryable) ?? enumerable.AsQueryable(); query = await ApplyQueryAsync(query, options, true); if (ODataCountMediaTypeMapping.IsCountRequest(options.Request.HttpContext)) { long?count = options.Request.ODataFeature().TotalCount; if (count.HasValue) { // Return the count value if it is a $count request. return(count.Value); } } return(query); }
/// <summary> /// Validates the OData query. /// </summary> /// <param name="options">The OData query to validate.</param> /// <param name="validationSettings">The validation settings.</param> public virtual void Validate(ODataQueryOptions options, ODataValidationSettings validationSettings) { if (options == null) { throw Error.ArgumentNull("options"); } if (validationSettings == null) { throw Error.ArgumentNull("validationSettings"); } // Validate each query options if (options.Skip != null) { ValidateQueryOptionAllowed(AllowedQueryOptions.Skip, validationSettings.AllowedQueryOptions); options.Skip.Validate(validationSettings); } if (options.Top != null) { ValidateQueryOptionAllowed(AllowedQueryOptions.Top, validationSettings.AllowedQueryOptions); options.Top.Validate(validationSettings); } if (options.OrderBy != null) { ValidateQueryOptionAllowed(AllowedQueryOptions.OrderBy, validationSettings.AllowedQueryOptions); options.OrderBy.Validate(validationSettings); } if (options.Filter != null) { ValidateQueryOptionAllowed(AllowedQueryOptions.Filter, validationSettings.AllowedQueryOptions); options.Filter.Validate(validationSettings); } if (options.Count != null || ODataCountMediaTypeMapping.IsCountRequest(options.Request)) { ValidateQueryOptionAllowed(AllowedQueryOptions.Count, validationSettings.AllowedQueryOptions); if (options.Count != null) { options.Count.Validate(validationSettings); } } if (options.RawValues.Expand != null) { ValidateQueryOptionAllowed(AllowedQueryOptions.Expand, validationSettings.AllowedQueryOptions); } if (options.RawValues.Select != null) { ValidateQueryOptionAllowed(AllowedQueryOptions.Select, validationSettings.AllowedQueryOptions); } if (options.SelectExpand != null) { options.SelectExpand.Validate(validationSettings); } if (options.RawValues.Format != null) { ValidateQueryOptionAllowed(AllowedQueryOptions.Format, validationSettings.AllowedQueryOptions); } if (options.RawValues.SkipToken != null) { ValidateQueryOptionAllowed(AllowedQueryOptions.SkipToken, validationSettings.AllowedQueryOptions); } }
/// <summary> /// Gets a value indicating if this is a count request. /// </summary> /// <returns></returns> public bool IsCountRequest() { return(ODataCountMediaTypeMapping.IsCountRequest(this.innerRequest.ODataProperties().Path)); }
/// <summary> /// Apply the individual query to the given IQueryable in the right order. /// </summary> /// <param name="query">The original <see cref="IQueryable"/>.</param> /// <param name="querySettings">The settings to use in query composition.</param> /// <param name="ignoreQueryOptions">The query parameters that are already applied in queries.</param> /// <returns>The new <see cref="IQueryable"/> after the query has been applied to.</returns> public virtual IQueryable ApplyTo(IQueryable query, ODataQuerySettings querySettings, AllowedQueryOptions ignoreQueryOptions) { _ignoreQueryOptions = ignoreQueryOptions; if (query == null) { throw Error.ArgumentNull("query"); } // Construct the actual query and apply them in the following order: filter if (IsAvailableODataQueryOption(Filter, AllowedQueryOptions.Filter)) { query = Filter.ApplyTo(query, querySettings, _assembliesResolver); } if (IsAvailableODataQueryOption(Count, AllowedQueryOptions.Count)) { if (Request.ODataProperties().TotalCountFunc == null) { Func <long> countFunc = Count.GetEntityCountFunc(query); if (countFunc != null) { Request.ODataProperties().TotalCountFunc = countFunc; } } if (ODataCountMediaTypeMapping.IsCountRequest(Request)) { return(query); } } OrderByQueryOption orderBy = OrderBy; // $skip or $top require a stable sort for predictable results. // Result limits require a stable sort to be able to generate a next page link. // If either is present in the query and we have permission, // generate an $orderby that will produce a stable sort. if (querySettings.EnsureStableOrdering && (IsAvailableODataQueryOption(Skip, AllowedQueryOptions.Skip) || IsAvailableODataQueryOption(Top, AllowedQueryOptions.Top) || querySettings.PageSize.HasValue)) { // If there is no OrderBy present, we manufacture a default. // If an OrderBy is already present, we add any missing // properties necessary to make a stable sort. // Instead of failing early here if we cannot generate the OrderBy, // let the IQueryable backend fail (if it has to). orderBy = orderBy == null ? GenerateDefaultOrderBy(Context) : EnsureStableSortOrderBy(orderBy, Context); } if (IsAvailableODataQueryOption(orderBy, AllowedQueryOptions.OrderBy)) { query = orderBy.ApplyTo(query, querySettings); } if (IsAvailableODataQueryOption(Skip, AllowedQueryOptions.Skip)) { query = Skip.ApplyTo(query, querySettings); } if (IsAvailableODataQueryOption(Top, AllowedQueryOptions.Top)) { query = Top.ApplyTo(query, querySettings); } AddAutoExpandProperties(querySettings); if (SelectExpand != null) { var tempResult = ApplySelectExpand(query, querySettings); if (tempResult != default(IQueryable)) { query = tempResult; } } if (querySettings.PageSize.HasValue) { bool resultsLimited = true; query = LimitResults(query, querySettings.PageSize.Value, out resultsLimited); var uriString = Request.GetDisplayUrl(); if (!string.IsNullOrWhiteSpace(uriString)) { var uri = new Uri(uriString); if (resultsLimited && uri != null && uri.IsAbsoluteUri && Request.ODataProperties().NextLink == null) { Uri nextPageLink = Request.GetNextPageLink(querySettings.PageSize.Value); Request.ODataProperties().NextLink = nextPageLink; } } } return(query); }
private object ExecuteQuery(object response, HttpRequestMessage request, HttpActionDescriptor actionDescriptor) { Type elementClrType = GetElementType(response, actionDescriptor); IEdmModel model = GetModel(elementClrType, request, actionDescriptor); if (model == null) { throw Error.InvalidOperation(SRResources.QueryGetModelMustNotReturnNull); } ODataQueryContext queryContext = new ODataQueryContext( model, elementClrType, request.ODataProperties().Path); ODataQueryOptions queryOptions = new ODataQueryOptions(queryContext, request); if (queryOptions.SelectExpand != null) { queryOptions.SelectExpand.LevelsMaxLiteralExpansionDepth = _validationSettings.MaxExpansionDepth; } ValidateQuery(request, queryOptions); // apply the query IEnumerable enumerable = response as IEnumerable; if (enumerable == null) { // response is not a collection; we only support $select and $expand on single entities. ValidateSelectExpandOnly(queryOptions); SingleResult singleResult = response as SingleResult; if (singleResult == null) { // response is a single entity. return(ApplyQuery(entity: response, queryOptions: queryOptions)); } else { // response is a composable SingleResult. ApplyQuery and call SingleOrDefault. IQueryable queryable = singleResult.Queryable; queryable = ApplyQuery(queryable, queryOptions); return(SingleOrDefault(queryable, actionDescriptor)); } } else { // response is a collection. IQueryable queryable = (enumerable as IQueryable) ?? enumerable.AsQueryable(); queryable = ApplyQuery(queryable, queryOptions); if (ODataCountMediaTypeMapping.IsCountRequest(request)) { long?count = request.ODataProperties().TotalCount; if (count.HasValue) { // Return the count value if it is a $count request. return(count.Value); } } return(queryable); } }
/// <summary> /// Performs the query composition after action is executed. It first tries to retrieve the IQueryable from the /// returning response message. It then validates the query from uri based on the validation settings on /// <see cref="EnableQueryAttribute"/>. It finally applies the query appropriately, and reset it back on /// the response message. /// </summary> /// <param name="actionExecutedContext">The context related to this action, including the response message, /// request message and HttpConfiguration etc.</param> public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext) { if (actionExecutedContext == null) { throw Error.ArgumentNull("actionExecutedContext"); } HttpRequestMessage request = actionExecutedContext.Request; if (request == null) { throw Error.Argument("actionExecutedContext", SRResources.ActionExecutedContextMustHaveRequest); } HttpConfiguration configuration = request.GetConfiguration(); if (configuration == null) { throw Error.Argument("actionExecutedContext", SRResources.RequestMustContainConfiguration); } if (actionExecutedContext.ActionContext == null) { throw Error.Argument("actionExecutedContext", SRResources.ActionExecutedContextMustHaveActionContext); } HttpActionDescriptor actionDescriptor = actionExecutedContext.ActionContext.ActionDescriptor; if (actionDescriptor == null) { throw Error.Argument("actionExecutedContext", SRResources.ActionContextMustHaveDescriptor); } HttpResponseMessage response = actionExecutedContext.Response; if (response != null && response.IsSuccessStatusCode && response.Content != null) { ObjectContent responseContent = response.Content as ObjectContent; if (responseContent == null) { throw Error.Argument("actionExecutedContext", SRResources.QueryingRequiresObjectContent, response.Content.GetType().FullName); } // Apply the query if there are any query options, if there is a page size set, in the case of // SingleResult or in the case of $count request. bool shouldApplyQuery = responseContent.Value != null && request.RequestUri != null && (!String.IsNullOrWhiteSpace(request.RequestUri.Query) || _querySettings.PageSize.HasValue || responseContent.Value is SingleResult || ODataCountMediaTypeMapping.IsCountRequest(request)); if (shouldApplyQuery) { try { object queryResult = ExecuteQuery(responseContent.Value, request, actionDescriptor); if (queryResult == null && request.ODataProperties().Path == null) { // This is the case in which a regular OData service uses the EnableQuery attribute. // For OData services ODataNullValueMessageHandler should be plugged in for the service // if this behavior is desired. // For non OData services this behavior is equivalent as the one in the v3 version in order // to reduce the friction when they decide to move to use the v4 EnableQueryAttribute. actionExecutedContext.Response = request.CreateResponse(HttpStatusCode.NotFound); } else { responseContent.Value = queryResult; } } catch (NotImplementedException e) { actionExecutedContext.Response = request.CreateErrorResponse( HttpStatusCode.BadRequest, Error.Format(SRResources.UriQueryStringInvalid, e.Message), e); } catch (NotSupportedException e) { actionExecutedContext.Response = request.CreateErrorResponse( HttpStatusCode.BadRequest, Error.Format(SRResources.UriQueryStringInvalid, e.Message), e); } catch (InvalidOperationException e) { // Will also catch ODataException here because ODataException derives from InvalidOperationException. actionExecutedContext.Response = request.CreateErrorResponse( HttpStatusCode.BadRequest, Error.Format(SRResources.UriQueryStringInvalid, e.Message), e); } } } }
private void BuildQueryOptions(IDictionary <string, string> queryParameters) { foreach (KeyValuePair <string, string> kvp in queryParameters) { switch (kvp.Key.ToLowerInvariant()) { case "$filter": ThrowIfEmpty(kvp.Value, "$filter"); RawValues.Filter = kvp.Value; Filter = new FilterQueryOption(kvp.Value, Context, _queryOptionParser); break; case "$orderby": ThrowIfEmpty(kvp.Value, "$orderby"); RawValues.OrderBy = kvp.Value; OrderBy = new OrderByQueryOption(kvp.Value, Context, _queryOptionParser); break; case "$top": ThrowIfEmpty(kvp.Value, "$top"); RawValues.Top = kvp.Value; Top = new TopQueryOption(kvp.Value, Context, _queryOptionParser); break; case "$skip": ThrowIfEmpty(kvp.Value, "$skip"); RawValues.Skip = kvp.Value; Skip = new SkipQueryOption(kvp.Value, Context, _queryOptionParser); break; case "$select": RawValues.Select = kvp.Value; break; case "$count": ThrowIfEmpty(kvp.Value, "$count"); RawValues.Count = kvp.Value; Count = new CountQueryOption(kvp.Value, Context, _queryOptionParser); break; case "$expand": RawValues.Expand = kvp.Value; break; case "$format": RawValues.Format = kvp.Value; break; case "$skiptoken": RawValues.SkipToken = kvp.Value; break; case "$deltatoken": RawValues.DeltaToken = kvp.Value; break; case "$apply": ThrowIfEmpty(kvp.Value, "$apply"); RawValues.Apply = kvp.Value; Apply = new ApplyQueryOption(kvp.Value, Context, _queryOptionParser); break; default: // we don't throw if we can't recognize the query break; } } if (RawValues.Select != null || RawValues.Expand != null) { SelectExpand = new SelectExpandQueryOption(RawValues.Select, RawValues.Expand, Context, _queryOptionParser); } if (ODataCountMediaTypeMapping.IsCountRequest(Request)) { Count = new CountQueryOption( "true", Context, new ODataQueryOptionParser( Context.Model, Context.ElementType, Context.NavigationSource, new Dictionary <string, string> { { "$count", "true" } })); } }
public virtual IQueryable ApplyTo(IQueryable query, ODataQuerySettings querySettings) { if (query == null) { throw Error.ArgumentNull("query"); } if (querySettings == null) { throw Error.ArgumentNull("querySettings"); } IQueryable result = query; // First apply $apply // Section 3.15 of the spec http://docs.oasis-open.org/odata/odata-data-aggregation-ext/v4.0/cs01/odata-data-aggregation-ext-v4.0-cs01.html#_Toc378326311 if (IsAvailableODataQueryOption(Apply, AllowedQueryOptions.Apply)) { result = Apply.ApplyTo(result, querySettings, _assembliesResolver); Request.ODataProperties().ApplyClause = Apply.ApplyClause; this.Context.ElementClrType = Apply.ResultClrType; } // Construct the actual query and apply them in the following order: filter, orderby, skip, top if (IsAvailableODataQueryOption(Filter, AllowedQueryOptions.Filter)) { result = Filter.ApplyTo(result, querySettings, _assembliesResolver); } if (IsAvailableODataQueryOption(Count, AllowedQueryOptions.Count)) { if (Request.ODataProperties().TotalCountFunc == null) { Func <long> countFunc = Count.GetEntityCountFunc(result); if (countFunc != null) { Request.ODataProperties().TotalCountFunc = countFunc; } } if (ODataCountMediaTypeMapping.IsCountRequest(Request)) { return(result); } } OrderByQueryOption orderBy = OrderBy; // $skip or $top require a stable sort for predictable results. // Result limits require a stable sort to be able to generate a next page link. // If either is present in the query and we have permission, // generate an $orderby that will produce a stable sort. if (querySettings.EnsureStableOrdering && (IsAvailableODataQueryOption(Skip, AllowedQueryOptions.Skip) || IsAvailableODataQueryOption(Top, AllowedQueryOptions.Top) || querySettings.PageSize.HasValue)) { // If there is no OrderBy present, we manufacture a default. // If an OrderBy is already present, we add any missing // properties necessary to make a stable sort. // Instead of failing early here if we cannot generate the OrderBy, // let the IQueryable backend fail (if it has to). orderBy = orderBy == null ? GenerateDefaultOrderBy(Context) : EnsureStableSortOrderBy(orderBy, Context); } if (IsAvailableODataQueryOption(orderBy, AllowedQueryOptions.OrderBy)) { result = orderBy.ApplyTo(result, querySettings); } if (IsAvailableODataQueryOption(Skip, AllowedQueryOptions.Skip)) { result = Skip.ApplyTo(result, querySettings); } if (IsAvailableODataQueryOption(Top, AllowedQueryOptions.Top)) { result = Top.ApplyTo(result, querySettings); } AddAutoExpandProperties(querySettings); if (SelectExpand != null) { var tempResult = ApplySelectExpand(result, querySettings); if (tempResult != default(IQueryable)) { result = tempResult; } } if (querySettings.PageSize.HasValue) { bool resultsLimited; result = LimitResults(result, querySettings.PageSize.Value, out resultsLimited); if (resultsLimited && Request.RequestUri != null && Request.RequestUri.IsAbsoluteUri && Request.ODataProperties().NextLink == null) { Uri nextPageLink = Request.GetNextPageLink(querySettings.PageSize.Value); Request.ODataProperties().NextLink = nextPageLink; } } return(result); }
public override void OnActionExecuted(ActionExecutedContext context) { if (context == null) { throw Error.ArgumentNull("context"); } var response = context.Result as StatusCodeResult; if (response != null)// && !response.IsSuccessStatusCode()) { return; } var request = context.HttpContext.Request; var model = request.ODataFeature().Model; if (model == null) { throw Error.InvalidOperation(SRResources.QueryGetModelMustNotReturnNull); } var result = context.Result as ObjectResult; if (result == null) { if (context.Exception != null) { throw context.Exception; } throw Error.Argument("context", SRResources.QueryingRequiresObjectContent, context.Result.GetType().FullName); } if (result?.Value is ODataError) { return; } var value = result.Value; if (request.GetDisplayUrl() == null || value == null || value.GetType().GetTypeInfo().IsValueType || value is string) { return; } var elementClrType = result.GetElementType(); var queryContext = new ODataQueryContext( model, elementClrType, request.ODataFeature().Path); var shouldApplyQuery = request.HasQueryOptions() || ResolvePageSize(_querySettings, context.ActionDescriptor).HasValue || new InterceptorContainer(elementClrType, context.HttpContext.RequestServices).Any || value is SingleResult || ODataCountMediaTypeMapping.IsCountRequest(context.HttpContext) || ContainsAutoExpandProperty(queryContext); if (!shouldApplyQuery) { return; } var queryOptions = new ODataQueryOptions(queryContext, request, context.HttpContext.RequestServices); long?count = null; var processedResult = ApplyQueryOptions(result.Value, queryOptions, context.ActionDescriptor); var enumberable = processedResult as IEnumerable <object>; if (enumberable != null) { // Apply count to result, if necessary, and return as a page result if (queryOptions.Count) { count = request.ODataFeature().TotalCount; count = Count(result.Value, queryOptions, context.ActionDescriptor); } // We might be getting a single result, so no paging involved var nextPageLink = request.ODataFeature().NextLink; var pageResult = new PageResult <object>(enumberable, nextPageLink, count); result.Value = pageResult; } else { // Return just the single entity result.Value = processedResult; } }
private void BuildQueryOptions(IDictionary <string, string> queryParameters) { foreach (var kvp in queryParameters) { switch (kvp.Key.ToLowerInvariant()) { case "$filter": ThrowIfEmpty(kvp.Value, "$filter"); RawValues.Filter = kvp.Value; Filter = new FilterQueryOption(kvp.Value, Context, _queryOptionParser, _serviceProvider); break; case "$orderby": ThrowIfEmpty(kvp.Value, "$orderby"); RawValues.OrderBy = kvp.Value; OrderBy = new OrderByQueryOption(kvp.Value, Context, _queryOptionParser, _serviceProvider); break; case "$top": ThrowIfEmpty(kvp.Value, "$top"); RawValues.Top = kvp.Value; Top = TryParseNonNegativeInteger("$top", kvp.Value); break; case "$skip": ThrowIfEmpty(kvp.Value, "$skip"); RawValues.Skip = kvp.Value; Skip = TryParseNonNegativeInteger("$skip", kvp.Value); SkipQueryOption = new SkipQueryOption(kvp.Value, Context, _queryOptionParser); break; case "$select": RawValues.Select = kvp.Value; break; case "$apply": ThrowIfEmpty(kvp.Value, "$apply"); RawValues.Apply = kvp.Value; Apply = new ApplyQueryOption(kvp.Value, Context, _queryOptionParser, _serviceProvider); break; case "$count": // According to the OData 4 protocol, the value of this query option is optional: // http://docs.oasis-open.org/odata/odata/v4.0/errata02/os/complete/part1-protocol/odata-v4.0-errata02-os-part1-protocol-complete.html#_Toc406398308 // "A $count query option with a value of false (or not specified) hints that the service SHOULD NOT return a count." RawValues.Count = kvp.Value; if (string.IsNullOrWhiteSpace(kvp.Value) == false) { bool count; if (bool.TryParse(kvp.Value, out count)) { Count = count; } else { throw new ODataException($"If a value for the query '$count' is specified, it must have a value of '{bool.TrueString}' or '{bool.FalseString}'"); } } break; case "$expand": RawValues.Expand = kvp.Value; // TODO Parse the select statement if any Request.ODataFeature().SelectExpandClause = _queryOptionParser.ParseSelectAndExpand(); SelectExpand = new SelectExpandQueryOption(string.Empty, kvp.Value, Context, _queryOptionParser, Request, _serviceProvider); break; case "$format": RawValues.Format = kvp.Value; break; case "$skiptoken": RawValues.SkipToken = kvp.Value; break; } } if (ODataCountMediaTypeMapping.IsCountRequest(Request.HttpContext)) { CountQueryOption = new CountQueryOption( "true", Context, new ODataQueryOptionParser( Context.Model, Context.ElementType, Context.NavigationSource, new Dictionary <string, string> { { "$count", "true" } })); } }
/// <summary> /// Apply the individual query to the given IQueryable in the right order. /// </summary> /// <param name="query">The original <see cref="IQueryable"/>.</param> /// <param name="querySettings">The settings to use in query composition.</param> /// <param name="pageSize">The page size for this query</param> /// <param name="queryOptions"></param> /// <returns>The new <see cref="IQueryable"/> after the query has been applied to.</returns> public virtual IQueryable ApplyTo(IQueryable query, ODataQuerySettings querySettings, int?pageSize, ODataQueryOptions queryOptions) { if (query == null) { throw Error.ArgumentNull("query"); } // Construct the actual query and apply them in the following order: filter if (Filter != null) { query = Filter.ApplyTo(query, querySettings, _assemblyProvider); } var orderBy = OrderBy; // $skip or $top require a stable sort for predictable results. // Result limits require a stable sort to be able to generate a next page link. // If either is present in the query and we have permission, // generate an $orderby that will produce a stable sort. if (querySettings.EnsureStableOrdering && ((Skip != null && queryOptions.IgnoreSkip == false) || (Top != null && queryOptions.IgnoreTop == false) || pageSize.HasValue)) { // If there is no OrderBy present, we manufacture a default. // If an OrderBy is already present, we add any missing // properties necessary to make a stable sort. // Instead of failing early here if we cannot generate the OrderBy, // let the IQueryable backend fail (if it has to). orderBy = orderBy == null ? OrderByHelper.GenerateDefaultOrderBy(Context, _serviceProvider) : OrderByHelper.EnsureStableSortOrderBy(orderBy, Context, _serviceProvider); } // First apply $apply // Section 3.15 of the spec http://docs.oasis-open.org/odata/odata-data-aggregation-ext/v4.0/cs01/odata-data-aggregation-ext-v4.0-cs01.html#_Toc378326311 if (Apply != null) { query = Apply.ApplyTo(query, querySettings, _assemblyProvider); Request.ODataFeature().ApplyClause = Apply.ApplyClause; Context.ElementClrType = Apply.ResultClrType; } if (orderBy != null && Apply == null) { query = orderBy.ApplyTo(query, querySettings); } if (Skip.HasValue && queryOptions.IgnoreSkip == false) { query = ExpressionHelpers.Skip(query, Skip.Value, Context.ElementClrType, false); } int?take = null; if (querySettings.PageSize.HasValue) { take = Math.Min(querySettings.PageSize.Value, int.MaxValue); } if (Top.HasValue && queryOptions.IgnoreTop == false) { take = Math.Min(Top.Value, take ?? int.MaxValue); } if (take.HasValue) { query = ExpressionHelpers.Take(query, take.Value, Context.ElementClrType, false); } if (SelectExpand != null) { query = SelectExpand.ApplyTo(query, querySettings, _assemblyProvider); } if (CountQueryOption != null) { if (Request.ODataFeature().TotalCountFunc == null) { Func <long> countFunc = CountQueryOption.GetEntityCountFunc(query); if (countFunc != null) { Request.ODataFeature().TotalCountFunc = countFunc; } } if (ODataCountMediaTypeMapping.IsCountRequest(Request.HttpContext)) { return(query); } } if (pageSize.HasValue && Apply == null) { bool resultsLimited; query = LimitResults(query, pageSize.Value, out resultsLimited); if (resultsLimited && Request.GetDisplayUrl() != null && new Uri(Request.GetDisplayUrl()).IsAbsoluteUri&& Request.ODataFeature().NextLink == null) { Uri nextPageLink = Request.GetNextPageLink(pageSize.Value); Request.ODataFeature().NextLink = nextPageLink; } } return(query); }
/// <summary> /// Apply the individual query to the given IQueryable in the right order. /// </summary> /// <param name="query">The original <see cref="IQueryable"/>.</param> /// <param name="querySettings">The settings to use in query composition.</param> /// <returns>The new <see cref="IQueryable"/> after the query has been applied to.</returns> public virtual IQueryable ApplyTo(IQueryable query, ODataQuerySettings querySettings) { if (query == null) { throw Error.ArgumentNull("query"); } if (querySettings == null) { throw Error.ArgumentNull("querySettings"); } IQueryable result = query; // Construct the actual query and apply them in the following order: filter, orderby, skip, top if (Filter != null) { result = Filter.ApplyTo(result, querySettings, _assembliesResolver); } if (Count != null) { if (Request.ODataProperties().TotalCount == null) { long?count = Count.GetEntityCount(result); if (count.HasValue) { Request.ODataProperties().TotalCount = count.Value; } } if (ODataCountMediaTypeMapping.IsCountRequest(Request)) { return(result); } } OrderByQueryOption orderBy = OrderBy; // $skip or $top require a stable sort for predictable results. // Result limits require a stable sort to be able to generate a next page link. // If either is present in the query and we have permission, // generate an $orderby that will produce a stable sort. if (querySettings.EnsureStableOrdering && (Skip != null || Top != null || querySettings.PageSize.HasValue)) { // If there is no OrderBy present, we manufacture a default. // If an OrderBy is already present, we add any missing // properties necessary to make a stable sort. // Instead of failing early here if we cannot generate the OrderBy, // let the IQueryable backend fail (if it has to). orderBy = orderBy == null ? GenerateDefaultOrderBy(Context) : EnsureStableSortOrderBy(orderBy, Context); } if (orderBy != null) { result = orderBy.ApplyTo(result, querySettings); } if (Skip != null) { result = Skip.ApplyTo(result, querySettings); } if (Top != null) { result = Top.ApplyTo(result, querySettings); } if (SelectExpand != null) { SelectExpandClause processedClause = SelectExpand.ProcessLevels(); SelectExpandQueryOption newSelectExpand = new SelectExpandQueryOption( SelectExpand.RawSelect, SelectExpand.RawExpand, SelectExpand.Context, processedClause); Request.ODataProperties().SelectExpandClause = processedClause; result = newSelectExpand.ApplyTo(result, querySettings); } if (querySettings.PageSize.HasValue) { bool resultsLimited; result = LimitResults(result, querySettings.PageSize.Value, out resultsLimited); if (resultsLimited && Request.RequestUri != null && Request.RequestUri.IsAbsoluteUri && Request.ODataProperties().NextLink == null) { Uri nextPageLink = GetNextPageLink(Request, querySettings.PageSize.Value); Request.ODataProperties().NextLink = nextPageLink; } } return(result); }
public virtual async Task <object> ProcessQueryAsync( HttpRequest request, object value, Type elementClrType, bool ignoreSkip = false, bool ignoreTop = false) { var model = request.ODataFeature().Model; if (request.GetDisplayUrl() == null || value == null || value.GetType().GetTypeInfo().IsValueType || value is string) { return(value); } var queryContext = new ODataQueryContext( model, elementClrType, request.ODataFeature().Path); var shouldApplyQuery = request.HasQueryOptions() || PageSize.HasValue || new InterceptorContainer(elementClrType, request.HttpContext.RequestServices).Any || value is SingleResult || ODataCountMediaTypeMapping.IsCountRequest(request.HttpContext) || ContainsAutoExpandProperty(queryContext, QuerySettings); if (!shouldApplyQuery) { return(value); } var queryOptions = new ODataQueryOptions(queryContext, request, request.HttpContext.RequestServices); if (ignoreSkip) { queryOptions.IgnoreSkip = true; } if (ignoreSkip) { queryOptions.IgnoreTop = true; } var processedResult = await ApplyQueryOptionsAsync( value, queryOptions); var enumberable = processedResult as IEnumerable <object>; if (enumberable != null) { long?count = null; // Apply count to result, if necessary, and return as a page result if (queryOptions.Count) { //count = request.ODataFeature().TotalCount; count = await Count(value, queryOptions); } // We might be getting a single result, so no paging involved var nextPageLink = request.ODataFeature().NextLink; var pageResult = new PageResult <object>(enumberable, nextPageLink, count); value = pageResult; } else { // Return just the single entity value = processedResult; } return(value); }