/// <summary> /// Throws if the type is not related to the type of the given set. /// </summary> /// <param name="type">Type to check.</param> /// <param name="secondType">Second type, which should be related to the first type.</param> /// <param name="segmentName">The segment that is checking this.</param> internal static void ThrowIfTypesUnrelated(IEdmType type, IEdmType secondType, string segmentName) { if (!UriEdmHelpers.IsRelatedTo(type.AsElementType(), secondType.AsElementType())) { throw new ODataException(Strings.PathParser_TypeMustBeRelatedToSet(type, secondType, segmentName)); } }
/// <summary> /// Throws if the type is not related to the type of the given set. /// </summary> /// <param name="type">Type to check.</param> /// <param name="secondType">Second type, which should be related to the first type.</param> /// <param name="segmentName">The segment that is checking this.</param> internal static void ThrowIfTypesUnrelated(IEdmType type, IEdmType secondType, string segmentName) { if (!UriEdmHelpers.IsRelatedTo(type.AsElementType(), secondType)) { throw new ODataException(Strings.PathParser_TypeMustBeRelatedToSet(type, secondType, segmentName)); } }
/// <summary> /// Performs query validations before action is executed. /// </summary> /// <param name="context">Action context.</param> public override void OnActionExecuting(ActionExecutingContext context) { if (context == null) { throw Error.ArgumentNull(nameof(context)); } base.OnActionExecuting(context); RequestQueryData requestQueryData = new RequestQueryData() { QueryValidationRunBeforeActionExecution = false, }; context.HttpContext.Items.Add(nameof(RequestQueryData), requestQueryData); HttpRequest request = context.HttpContext.Request; ODataPath path = request.ODataFeature().Path; ODataQueryContext queryContext = null; // For OData based controllers. if (path != null) { IEdmType edmType = path.EdmType; // When $count is at the end, the return type is always int. Trying to instead fetch the return type of the actual type being counted on. if (request.IsCountRequest()) { edmType = path.Segments[path.Segments.Count - 2].EdmType; } IEdmType elementType = edmType.AsElementType(); IEdmModel edmModel = request.GetModel(); // For Swagger metadata request. elementType is null. if (elementType == null || edmModel == null) { return; } Type clrType = edmModel.GetTypeMappingCache().GetClrType( elementType.ToEdmTypeReference(isNullable: false), edmModel); // CLRType can be missing if untyped registrations were made. if (clrType != null) { queryContext = new ODataQueryContext(edmModel, clrType, path); } else { // In case where CLRType is missing, $count, $expand verifications cannot be done. // More importantly $expand required ODataQueryContext with clrType which cannot be done // If the model is untyped. Hence for such cases, letting the validation run post action. return; } } else { // For non-OData Json based controllers. // For these cases few options are supported like IEnumerable<T>, Task<IEnumerable<T>>, T, Task<T> // Other cases where we cannot determine the return type upfront, are not supported // Like IActionResult, SingleResult. For such cases, the validation is run in OnActionExecuted // When we have the result. ControllerActionDescriptor controllerActionDescriptor = context.ActionDescriptor as ControllerActionDescriptor; if (controllerActionDescriptor == null) { return; } Type returnType = controllerActionDescriptor.MethodInfo.ReturnType; Type elementType; // For Task<> get the base object. if (returnType.IsGenericType && returnType.GetGenericTypeDefinition() == typeof(Task <>)) { returnType = returnType.GetGenericArguments().First(); } // For NetCore2.2+ new type ActionResult<> was created which encapculates IActionResult and T result. // However we don't exactly have a version specific to NetCore2.2 (also at the time of writing this code // 2.2 and 3.0 are both out of support), hence the code is made to work on NetCore3.1+ only. #if NETCOREAPP3_1 || NET5_0 if (returnType.IsGenericType && returnType.GetGenericTypeDefinition() == typeof(ActionResult <>)) { returnType = returnType.GetGenericArguments().First(); } #endif if (TypeHelper.IsCollection(returnType)) { elementType = TypeHelper.GetImplementedIEnumerableType(returnType); } else if (TypeHelper.IsGenericType(returnType) && returnType.GetGenericTypeDefinition() == typeof(Task <>)) { elementType = returnType.GetGenericArguments().First(); } else { return; } IEdmModel edmModel = this.GetModel( elementType, request, controllerActionDescriptor); queryContext = new ODataQueryContext( edmModel, elementType); } // Create and validate the query options. requestQueryData.QueryValidationRunBeforeActionExecution = true; requestQueryData.ProcessedQueryOptions = new ODataQueryOptions(queryContext, request); try { ValidateQuery(request, requestQueryData.ProcessedQueryOptions); } catch (ArgumentOutOfRangeException e) { context.Result = CreateBadRequestResult( Error.Format(SRResources.QueryParameterNotSupported, e.Message), e); } catch (NotImplementedException e) { context.Result = CreateBadRequestResult( Error.Format(SRResources.UriQueryStringInvalid, e.Message), e); } catch (NotSupportedException e) { context.Result = CreateBadRequestResult( Error.Format(SRResources.UriQueryStringInvalid, e.Message), e); } catch (InvalidOperationException e) { // Will also catch ODataException here because ODataException derives from InvalidOperationException. context.Result = CreateBadRequestResult( Error.Format(SRResources.UriQueryStringInvalid, e.Message), e); } }
/// <summary> /// Performs the query composition before action is executing. /// </summary> /// <param name="actionExecutingContext">The action executing context.</param> public override void OnActionExecuting(ActionExecutingContext actionExecutingContext) { if (actionExecutingContext == null) { throw new ArgumentNullException(nameof(actionExecutingContext)); } base.OnActionExecuting(actionExecutingContext); RequestQueryData requestQueryData = new RequestQueryData() { QueryValidationRunBeforeActionExecution = false, }; actionExecutingContext.HttpContext.Items.Add(nameof(RequestQueryData), requestQueryData); HttpRequest request = actionExecutingContext.HttpContext.Request; ODataPath path = request.ODataFeature().Path; ODataQueryContext queryContext; // For OData based controllers. if (path != null) { IEdmType edmType = path.GetEdmType(); // When $count is at the end, the return type is always int. Trying to instead fetch the return type of the actual type being counted on. if (request.IsCountRequest()) { ODataPathSegment[] pathSegments = path.ToArray(); edmType = pathSegments[pathSegments.Length - 2].EdmType; } IEdmType elementType = edmType.AsElementType(); IEdmModel edmModel = request.GetModel(); // For Swagger metadata request. elementType is null. if (elementType == null || edmModel == null) { return; } Type clrType = edmModel.GetTypeMappingCache().GetClrType( elementType.ToEdmTypeReference(isNullable: false), edmModel); // CLRType can be missing if untyped registrations were made. if (clrType != null) { queryContext = new ODataQueryContext(edmModel, clrType, path); } else { // In case where CLRType is missing, $count, $expand verifications cannot be done. // More importantly $expand required ODataQueryContext with clrType which cannot be done // If the model is untyped. Hence for such cases, letting the validation run post action. return; } } else { // For non-OData Json based controllers. // For these cases few options are supported like IEnumerable<T>, Task<IEnumerable<T>>, T, Task<T> // Other cases where we cannot determine the return type upfront, are not supported // Like IActionResult, SingleResult. For such cases, the validation is run in OnActionExecuted // When we have the result. ControllerActionDescriptor controllerActionDescriptor = actionExecutingContext.ActionDescriptor as ControllerActionDescriptor; if (controllerActionDescriptor == null) { return; } Type returnType = controllerActionDescriptor.MethodInfo.ReturnType; Type elementType; // For Task<> get the base object. if (returnType.IsGenericType && returnType.GetGenericTypeDefinition() == typeof(Task <>)) { returnType = returnType.GetGenericArguments().First(); } // For NetCore2.2+ new type ActionResult<> was created which encapculates IActionResult and T result. if (returnType.IsGenericType && returnType.GetGenericTypeDefinition() == typeof(ActionResult <>)) { returnType = returnType.GetGenericArguments().First(); } if (TypeHelper.IsCollection(returnType)) { elementType = TypeHelper.GetImplementedIEnumerableType(returnType); } else if (TypeHelper.IsGenericType(returnType) && returnType.GetGenericTypeDefinition() == typeof(Task <>)) { elementType = returnType.GetGenericArguments().First(); } else { return; } IEdmModel edmModel = GetModel( elementType, request, controllerActionDescriptor); queryContext = new ODataQueryContext( edmModel, elementType); IODataFeature odataFeature = request.ODataFeature(); odataFeature.PrefixName = odataFeature.PrefixName ?? string.Empty; IOptions <ODataOptions> odataOptionsOptions = request.HttpContext.RequestServices.GetRequiredService <IOptions <ODataOptions> >(); var options = odataOptionsOptions.Value; if (!options.Models.ContainsKey(odataFeature.PrefixName)) { options.AddModel(odataFeature.PrefixName, edmModel); } } // Create and validate the query options. requestQueryData.QueryValidationRunBeforeActionExecution = true; requestQueryData.ProcessedQueryOptions = new ODataQueryOptions(queryContext, request); try { ValidateQuery(request, requestQueryData.ProcessedQueryOptions); } catch (ArgumentOutOfRangeException e) { actionExecutingContext.Result = CreateBadRequestResult( Error.Format(SRResources.QueryParameterNotSupported, e.Message), e); } catch (NotImplementedException e) { actionExecutingContext.Result = CreateBadRequestResult( Error.Format(SRResources.UriQueryStringInvalid, e.Message), e); } catch (NotSupportedException e) { actionExecutingContext.Result = CreateBadRequestResult( Error.Format(SRResources.UriQueryStringInvalid, e.Message), e); } catch (InvalidOperationException e) { // Will also catch ODataException here because ODataException derives from InvalidOperationException. actionExecutingContext.Result = CreateBadRequestResult( Error.Format(SRResources.UriQueryStringInvalid, e.Message), e); } }