/// <summary> /// Create the <see cref="ODataDeltaResourceSet"/> to be written for the given feed instance. /// </summary> /// <param name="feedInstance">The instance representing the feed being written.</param> /// <param name="feedType">The EDM type of the feed being written.</param> /// <param name="writeContext">The serializer context.</param> /// <returns>The created <see cref="ODataDeltaResourceSet"/> object.</returns> public virtual ODataDeltaResourceSet CreateODataDeltaFeed(IEnumerable feedInstance, IEdmCollectionTypeReference feedType, ODataSerializerContext writeContext) { if (writeContext == null) { throw Error.ArgumentNull(nameof(writeContext)); } ODataDeltaResourceSet feed = new ODataDeltaResourceSet(); if (writeContext.ExpandedResource == null) { // If we have more OData format specific information apply it now, only if we are the root feed. PageResult odataFeedAnnotations = feedInstance as PageResult; if (odataFeedAnnotations != null) { feed.Count = odataFeedAnnotations.Count; feed.NextPageLink = odataFeedAnnotations.NextPageLink; } else if (writeContext.Request != null) { IODataFeature odataFeature = writeContext.Request.ODataFeature(); feed.NextPageLink = odataFeature.NextLink; feed.DeltaLink = odataFeature.DeltaLink; long?countValue = odataFeature.TotalCount; if (countValue.HasValue) { feed.Count = countValue.Value; } } } return(feed); }
/// <summary> /// Initializes a new instance of the WebApiRequestMessage class. /// </summary> /// <param name="request">The inner request.</param> public WebApiRequestMessage(HttpRequest request) { if (request == null) { throw Error.ArgumentNull("request"); } this.innerRequest = request; this.Headers = new WebApiRequestHeaders(request.Headers); IODataFeature feature = request.ODataFeature(); if (feature != null) { this.Context = new WebApiContext(feature); } // Get the ODataOptions from the global service provider. ODataOptions options = request.HttpContext.RequestServices.GetRequiredService <ODataOptions>(); if (options != null) { this.Options = new WebApiOptions(options); } }
/// <summary> /// Gets the deserializer and the expected payload type. /// </summary> /// <param name="request">The HttpRequest.</param> /// <param name="type">The input type.</param> /// <param name="expectedPayloadType">Output the expected payload type.</param> /// <returns>null or the OData deserializer</returns> private static ODataDeserializer GetDeserializer(HttpRequest request, Type type, out IEdmTypeReference expectedPayloadType) { Contract.Assert(request != null); IODataFeature odataFeature = request.ODataFeature(); ODataPath path = odataFeature.Path; IEdmModel model = odataFeature.Model; expectedPayloadType = null; ODataDeserializerProvider deserializerProvider = request.GetSubServiceProvider().GetRequiredService <ODataDeserializerProvider>(); // Get the deserializer using the CLR type first from the deserializer provider. ODataDeserializer deserializer = deserializerProvider.GetODataDeserializer(type, request); if (deserializer == null) { expectedPayloadType = EdmLibHelper.GetExpectedPayloadType(type, path, model); if (expectedPayloadType != null) { // we are in typeless mode, get the deserializer using the edm type from the path. deserializer = deserializerProvider.GetEdmTypeDeserializer(expectedPayloadType); } } return(deserializer); }
public Task ApplyAsync(HttpContext httpContext, CandidateSet candidates) { if (httpContext == null) { throw new ArgumentNullException(nameof(httpContext)); } // The goal of this method is to perform the final matching: // Map between route values matched by the template and the ones we want to expose to the action for binding. // (tweaking the route values is fine here) // Invalidating the candidate if the key/function values are not valid/missing. // Perform overload resolution for functions by looking at the candidates and their metadata. for (var i = 0; i < candidates.Count; i++) { ref CandidateState candidate = ref candidates[i]; if (!candidates.IsValidCandidate(i)) { continue; } IODataRoutingMetadata metadata = candidate.Endpoint.Metadata.OfType <IODataRoutingMetadata>().FirstOrDefault(); if (metadata == null) { continue; } IHttpMethodMetadata httpMetadata = candidate.Endpoint.Metadata.GetMetadata <IHttpMethodMetadata>(); if (httpMetadata == null) { // Check the http method if (metadata.HttpMethods != null && !metadata.HttpMethods.Contains(httpContext.Request.Method)) { candidates.SetValidity(i, false); continue; } } ODataTemplateTranslateContext translatorContext = new ODataTemplateTranslateContext(httpContext, candidate.Values, metadata.Model); try { ODataPath odataPath = _translator.Translate(metadata.Template, translatorContext); if (odataPath != null) { IODataFeature odataFeature = httpContext.ODataFeature(); odataFeature.PrefixName = metadata.Prefix; odataFeature.Model = metadata.Model; odataFeature.Path = odataPath; } else { candidates.SetValidity(i, false); } } catch (Exception) { candidates.SetValidity(i, false); } }
/// <summary> /// /// </summary> /// <param name="setupAction"></param> /// <returns></returns> public static HttpRequest Create(Action <IODataFeature> setupAction) { HttpContext context = new DefaultHttpContext(); IODataFeature odataFeature = context.ODataFeature(); setupAction?.Invoke(odataFeature); return(context.Request); }
public static HttpRequest Create(string method, string uri, IEdmModel model) { HttpRequest request = Create(method, uri, opt => opt.AddModel("odata", model)); IODataFeature feature = request.ODataFeature(); feature.PrefixName = "odata"; feature.Model = model; return(request); }
/// <summary> /// Create the HttpRequest with IEdmModel. /// </summary> /// <param name="model">The given Edm model.</param> /// <param name="setupAction">The OData configuration.</param> /// <returns>The created HttpRequest.</returns> public static HttpRequest Create(IEdmModel model, Action <ODataOptions> setupAction) { HttpRequest request = Create("Get", "http://localhost/", setupAction); IODataFeature feature = request.ODataFeature(); feature.RoutePrefix = ""; feature.Model = model; return(request); }
public static HttpRequest Create(string method, string uri, IEdmModel model) { // HttpRequest request = Create(method, uri, opt => opt.AddModel("odata", model)); HttpRequest request = Create(method, uri, setupAction: null); IODataFeature feature = request.ODataFeature(); feature.RoutePrefix = ""; feature.Model = model; return(request); }
/// <summary> /// Creates an ETag from concurrency property names and values. /// </summary> /// <param name="properties">The input property names and values.</param> /// <returns>The generated ETag string.</returns> public string CreateETag(IDictionary <string, object> properties) { IODataFeature feature = this.innerRequest.ODataFeature(); if (feature == null) { throw Error.InvalidOperation(SRResources.RequestMustContainConfiguration); } return(this.innerRequest.GetETagHandler().CreateETag(properties)?.ToString()); }
/// <summary> /// Generates an OData link using the given OData route name, path handler, and segments. /// </summary> /// <param name="request">The Http request.</param> /// <param name="segments">The OData path segments.</param> /// <returns>The generated OData link.</returns> public static string CreateODataLink(this HttpRequest request, IList <ODataPathSegment> segments) { if (request == null) { throw Error.ArgumentNull(nameof(request)); } IODataFeature oDataFeature = request.ODataFeature(); string odataPath = segments.GetPathString(); // retrieve the cached base address string baseAddress = oDataFeature.BaseAddress; if (baseAddress != null) { return(CombinePath(baseAddress, odataPath)); } // if no, calculate the base address string uriString = UriHelper.BuildAbsolute(request.Scheme, request.Host, request.PathBase); string prefix = oDataFeature.RoutePrefix; if (string.IsNullOrEmpty(prefix)) { baseAddress = uriString; } else { // Construct the prefix template if it's a template RoutePattern routePattern = RoutePatternFactory.Parse(prefix); if (!routePattern.Parameters.Any()) { baseAddress = CombinePath(uriString, prefix); } else { if (TryProcessPrefixTemplate(request, routePattern, out var path)) { baseAddress = CombinePath(uriString, path); } else { throw new ODataException(Error.Format(SRResources.CannotProcessPrefixTemplate, prefix)); } } } // cache the base address oDataFeature.BaseAddress = baseAddress; return(CombinePath(baseAddress, odataPath)); }
/// <summary> /// Confgiures the http request with OData values. /// </summary> /// <param name="request">The http request.</param> /// <param name="prefix">The prefix.</param> /// <param name="model">The Edm model.</param> /// <param name="path">The OData path.</param> /// <returns></returns> public static HttpRequest Configure(this HttpRequest request, string prefix, IEdmModel model, ODataPath path) { if (request == null) { throw new ArgumentNullException(nameof(request)); } IODataFeature feature = request.ODataFeature(); feature.PrefixName = prefix; feature.Model = model; feature.Path = path; return(request); }
/// <summary> /// Determines whether the URL parameter contains a valid value for this constraint. /// </summary> /// <param name="httpContext">The Http context.</param> /// <param name="route">The route to compare.</param> /// <param name="routeKey">The name of the parameter.</param> /// <param name="values">A list of parameter values.</param> /// <param name="routeDirection">The route direction.</param> /// <returns> /// True if this instance equals a specified route; otherwise, false. /// </returns> /// <remarks>This signature uses types that are AspNetCore-specific.</remarks> public bool Match(HttpContext httpContext, IRouter route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection) { if (httpContext == null) { throw Error.ArgumentNull("httpContext"); } if (values == null) { throw Error.ArgumentNull("values"); } if (routeDirection == RouteDirection.IncomingRequest) { ODataPath path = null; HttpRequest request = httpContext.Request; object oDataPathValue; if (values.TryGetValue(ODataRouteConstants.ODataPath, out oDataPathValue)) { // We need to call Uri.GetLeftPart(), which returns an encoded Url. // The ODL parser does not like raw values. Uri requestUri = new Uri(request.GetEncodedUrl()); string requestLeftPart = requestUri.GetLeftPart(UriPartial.Path); string queryString = request.QueryString.HasValue ? request.QueryString.ToString() : null; path = GetODataPath(oDataPathValue as string, requestLeftPart, queryString, () => request.CreateRequestContainer(RouteName)); } if (path != null) { // Set all the properties we need for routing, querying, formatting IODataFeature odataFeature = httpContext.ODataFeature(); odataFeature.Path = path; odataFeature.RouteName = RouteName; return(true); } // The request doesn't match this route so dispose the request container. request.DeleteRequestContainer(true); return(false); } else { // This constraint only applies to incoming request. return(true); } }
public ODataJsonSerializer(OutputFormatterWriteContext context) { _context = context; _odataProperties = context.HttpContext.ODataFeature(); _version = ODataFeature.DefaultODataVersion; MessageWriterSettings = new ODataMessageWriterSettings { //Indent = true, //DisableMessageStreamDisposal = true, MessageQuotas = new ODataMessageQuotas { MaxReceivedMessageSize = Int64.MaxValue }, //AutoComputePayloadMetadataInJson = true, }; }
/// <summary> /// Extension method to return the <see cref="IODataFeature"/> from the <see cref="HttpContext"/>. /// </summary> /// <param name="httpContext">The Http context.</param> /// <returns>The <see cref="IODataFeature"/>.</returns> public static IODataFeature ODataFeature(this HttpContext httpContext) { if (httpContext == null) { throw new ArgumentNullException(nameof(httpContext)); } IODataFeature odataFeature = httpContext.Features.Get <IODataFeature>(); if (odataFeature == null) { odataFeature = new ODataFeature(); httpContext.Features.Set <IODataFeature>(odataFeature); } return(odataFeature); }
/// <summary> /// Extension method to return the <see cref="IODataFeature"/> from the <see cref="HttpContext"/>. /// </summary> /// <param name="httpContext">The Http context.</param> /// <returns>The <see cref="IODataFeature"/>.</returns> public static IODataFeature ODataFeature(this HttpContext httpContext) { if (httpContext == null) { throw Error.ArgumentNull("httpContext"); } IODataFeature odataFeature = httpContext.Features.Get <IODataFeature>(); if (odataFeature == null) { odataFeature = new ODataFeature(httpContext); httpContext.Features.Set <IODataFeature>(odataFeature); } return(odataFeature); }
/// <summary> /// Extension method to return the <see cref="IUrlHelper"/> from the <see cref="HttpRequest"/>. /// </summary> /// <param name="request">The Http request.</param> /// <returns>The <see cref="IUrlHelper"/>.</returns> public static IUrlHelper GetUrlHelper(this HttpRequest request) { if (request == null) { throw Error.ArgumentNull("request"); } IODataFeature feature = request.ODataFeature(); if (feature.UrlHelper == null) { // if not set, get it from global. feature.UrlHelper = request.HttpContext.GetUrlHelper(); } return(feature.UrlHelper); }
public void AddRouteComponents_WithDependencyInjection_SetModelAndServices() { // Arrange ODataOptions options = new ODataOptions(); IEdmModel edmModel = EdmCoreModel.Instance; // Act options.AddRouteComponents("odata", edmModel, services => services.AddSingleton <IODataFeature, ODataFeature>()); // Assert KeyValuePair <string, (IEdmModel, IServiceProvider)> model = Assert.Single(options.RouteComponents); Assert.Equal("odata", model.Key); Assert.Same(edmModel, model.Value.Item1); Assert.NotNull(model.Value.Item2); IODataFeature actual = model.Value.Item2.GetService <IODataFeature>(); Assert.IsType <ODataFeature>(actual); }
public void AddModel_WithDependencyInjection_SetModelAndServices() { // Arrange ODataOptions options = new ODataOptions(); IEdmModel edmModel = EdmCoreModel.Instance; // Act options.AddModel("odata", edmModel, builder => builder.AddService <IODataFeature, ODataFeature>(Microsoft.OData.ServiceLifetime.Singleton)); // Assert KeyValuePair <string, (IEdmModel, IServiceProvider)> model = Assert.Single(options.Models); Assert.Equal("odata", model.Key); Assert.Same(edmModel, model.Value.Item1); Assert.NotNull(model.Value.Item2); IODataFeature actual = model.Value.Item2.GetService <IODataFeature>(); Assert.IsType <ODataFeature>(actual); }
public Task ApplyAsync(HttpContext httpContext, CandidateSet candidates) { if (httpContext == null) { throw Error.ArgumentNull(nameof(httpContext)); } if (candidates == null) { throw Error.ArgumentNull(nameof(candidates)); } IODataFeature odataFeature = httpContext.ODataFeature(); ActionDescriptor actionDescriptor = odataFeature.ActionDescriptor; if (actionDescriptor == null) { // This means the request didn't match an OData endpoint. Just ignore it. return(Task.CompletedTask); } for (int i = 0; i < candidates.Count; i++) { if (candidates[i].Endpoint == null) { candidates.SetValidity(i, false); continue; } ActionDescriptor action = candidates[i].Endpoint.Metadata.GetMetadata <ActionDescriptor>(); if (action != null && !object.ReferenceEquals(action, actionDescriptor)) { // This candidate is not the one we matched earlier, so disallow it. candidates.SetValidity(i, false); } } return(Task.CompletedTask); }
internal void Select(HttpContext httpContext, CandidateSet candidateSet) { IODataFeature odataFeature = httpContext.ODataFeature(); ActionDescriptor actionDescriptor = odataFeature.ActionDescriptor; if (actionDescriptor != null) { int count = candidateSet.Count; for (int i = 0; i < count; i++) { CandidateState candidate = candidateSet[i]; ActionDescriptor action = candidate.Endpoint.Metadata.GetMetadata <ActionDescriptor>(); // Noted: we simple use the "ReferenceEquals" to compare the action descriptor. // So far, i don't know the risk, i need .NET team help me to verify it? if (object.ReferenceEquals(action, actionDescriptor)) { httpContext.SetEndpoint(candidate.Endpoint); httpContext.Request.RouteValues = candidate.Values; return; } } } }
/// <summary> /// Creates a set of transformed route values that will be used to select an action. /// </summary> /// <param name="httpContext">The <see cref="HttpContext"/> associated with the current request.</param> /// <param name="values">The route values associated with the current match.</param> /// <returns>A task which asynchronously returns a set of route values.</returns> public override ValueTask <RouteValueDictionary> TransformAsync(HttpContext httpContext, RouteValueDictionary values) { if (httpContext == null) { throw Error.ArgumentNull(nameof(httpContext)); } if (values == null) { throw Error.ArgumentNull(nameof(values)); } if (httpContext.ODataFeature().Path != null) { // Noted: if there's a route mapping with ODataPrefix == null, // for example: router.MapODataRoute(routeName: "odata", routePrefix: null, ...) // This route will match all requests. // Therefore, this route will be a candidate and its tranformer will be called. // So, we use the ODataPath setting to verify whether the request is transformed or not. // Maybe we have a better solution later. return(new ValueTask <RouteValueDictionary>(result: null)); } (string routeName, object oDataPathValue) = values.GetODataRouteInfo(); if (routeName != null) { HttpRequest request = httpContext.Request; // We need to call Uri.GetLeftPart(), which returns an encoded Url. // The ODL parser does not like raw values. Uri requestUri = new Uri(request.GetEncodedUrl()); string requestLeftPart = requestUri.GetLeftPart(UriPartial.Path); string queryString = request.QueryString.HasValue ? request.QueryString.ToString() : null; // Call ODL to parse the Request URI. ODataPath path = ODataPathRouteConstraint.GetODataPath(oDataPathValue as string, requestLeftPart, queryString, () => request.CreateRequestContainer(routeName)); if (path != null) { // Set all the properties we need for routing, querying, formatting IODataFeature odataFeature = httpContext.ODataFeature(); odataFeature.Path = path; odataFeature.RouteName = routeName; odataFeature.IsEndpointRouting = true; // mark as Endpoint routing // Noted: we inject the ActionSelector and use it to select the best OData action. // In .NET 5 or later, this maybe change. RouteContext routeContext = new RouteContext(httpContext); var condidates = _selector.SelectCandidates(routeContext); var actionDescriptor = _selector.SelectBestCandidate(routeContext, condidates); ControllerActionDescriptor controllerActionDescriptor = actionDescriptor as ControllerActionDescriptor; if (controllerActionDescriptor != null) { RouteValueDictionary newValues = new RouteValueDictionary(); foreach (var item in values) { newValues.Add(item.Key, item.Value); } foreach (var item in routeContext.RouteData.Values) { newValues[item.Key] = item.Value; } newValues["controller"] = controllerActionDescriptor.ControllerName; newValues["action"] = controllerActionDescriptor.ActionName; newValues["odataPath"] = oDataPathValue; // Noted, here's a working around for mulitiple actions in same controller. // For example, we have two "Get" methods in same controller, in order to help "EndpointSelector" // to select the correct Endpoint, we save the ActionDescriptor value into ODataFeature. odataFeature.ActionDescriptor = controllerActionDescriptor; return(new ValueTask <RouteValueDictionary>(newValues)); } } else { // The request doesn't match this route so dispose the request container. request.DeleteRequestContainer(true); } } return(new ValueTask <RouteValueDictionary>(result: null)); }
/// <summary> /// Create the <see cref="ODataResourceSet"/> to be written for the given resourceSet instance. /// </summary> /// <param name="resourceSetInstance">The instance representing the resourceSet being written.</param> /// <param name="resourceSetType">The EDM type of the resourceSet being written.</param> /// <param name="writeContext">The serializer context.</param> /// <returns>The created <see cref="ODataResourceSet"/> object.</returns> public virtual ODataResourceSet CreateResourceSet(IEnumerable resourceSetInstance, IEdmCollectionTypeReference resourceSetType, ODataSerializerContext writeContext) { if (writeContext == null) { throw Error.ArgumentNull(nameof(writeContext)); } ODataResourceSet resourceSet = new ODataResourceSet { TypeName = resourceSetType.FullName() }; IEdmStructuredTypeReference structuredType = GetResourceType(resourceSetType).AsStructured(); if (writeContext.NavigationSource != null && structuredType.IsEntity()) { ResourceSetContext resourceSetContext = ResourceSetContext.Create(writeContext, resourceSetInstance); IEdmEntityType entityType = structuredType.AsEntity().EntityDefinition(); var operations = writeContext.Model.GetAvailableOperationsBoundToCollection(entityType); var odataOperations = CreateODataOperations(operations, resourceSetContext, writeContext); foreach (var odataOperation in odataOperations) { ODataAction action = odataOperation as ODataAction; if (action != null) { resourceSet.AddAction(action); } else { resourceSet.AddFunction((ODataFunction)odataOperation); } } } if (writeContext.ExpandedResource == null) { // If we have more OData format specific information apply it now, only if we are the root feed. PageResult odataResourceSetAnnotations = resourceSetInstance as PageResult; if (odataResourceSetAnnotations != null) { resourceSet.Count = odataResourceSetAnnotations.Count; resourceSet.NextPageLink = odataResourceSetAnnotations.NextPageLink; } else if (writeContext.Request != null) { IODataFeature odataFeature = writeContext.Request.ODataFeature(); resourceSet.NextPageLink = odataFeature.NextLink; resourceSet.DeltaLink = odataFeature.DeltaLink; long?countValue = odataFeature.TotalCount; if (countValue.HasValue) { resourceSet.Count = countValue.Value; } } } else { ICountOptionCollection countOptionCollection = resourceSetInstance as ICountOptionCollection; if (countOptionCollection != null && countOptionCollection.TotalCount != null) { resourceSet.Count = countOptionCollection.TotalCount; } } return(resourceSet); }
/// <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); } }
/// <summary> /// Determines whether this instance equals a specified route. /// </summary> /// <param name="httpContext">The http context.</param> /// <param name="route">The route to compare.</param> /// <param name="routeKey">The name of the route key.</param> /// <param name="values">A list of parameter values.</param> /// <param name="routeDirection">The route direction.</param> /// <returns> /// True if this instance equals a specified route; otherwise, false. /// </returns> public bool Match(HttpContext httpContext, IRouter route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection) { if (httpContext == null) { throw Error.ArgumentNull("httpContext"); } if (values == null) { throw Error.ArgumentNull("values"); } if (routeDirection == RouteDirection.IncomingRequest) { object odataPathValue; if (!values.TryGetValue(ODataRouteConstants.ODataPath, out odataPathValue)) { return(false); } string odataPathString = odataPathValue as string; ODataPath odataPath; try { // Service root is the current RequestUri, less the query string and the ODataPath (always the // last portion of the absolute path). ODL expects an escaped service root and other service // root calculations are calculated using AbsoluteUri (also escaped). But routing exclusively // uses unescaped strings, determined using // address.GetComponents(UriComponents.Path, UriFormat.Unescaped) // // For example if the AbsoluteUri is // <http://localhost/odata/FunctionCall(p0='Chinese%E8%A5%BF%E9%9B%85%E5%9B%BEChars')>, the // oDataPathString will contain "FunctionCall(p0='Chinese西雅图Chars')". // // Due to this decoding and the possibility of unecessarily-escaped characters, there's no // reliable way to determine the original string from which oDataPathString was derived. // Therefore a straightforward string comparison won't always work. See RemoveODataPath() for // details of chosen approach. HttpRequest request = httpContext.Request; string serviceRoot = GetServiceRoot(request); // string requestLeftPart = request.Path..GetLeftPart(UriPartial.Path); //string serviceRoot = request.Path; /* * if (!String.IsNullOrEmpty(odataPathString)) * { * serviceRoot = RemoveODataPath(serviceRoot, odataPathString); * }*/ // As mentioned above, we also need escaped ODataPath. // The requestLeftPart and request.RequestUri.Query are both escaped. // The ODataPath for service documents is empty. string oDataPathAndQuery = String.Empty; if (!String.IsNullOrEmpty(odataPathString)) { oDataPathAndQuery = odataPathString; } if (request.QueryString.HasValue) { // Ensure path handler receives the query string as well as the path. oDataPathAndQuery += request.QueryString; } // Leave an escaped '/' out of the service route because DefaultODataPathHandler will add a // literal '/' to the end of this string if not already present. That would double the slash // in response links and potentially lead to later 404s. if (serviceRoot.EndsWith(_escapedSlash, StringComparison.OrdinalIgnoreCase)) { serviceRoot = serviceRoot.Substring(0, serviceRoot.Length - _escapedSlash.Length); } IODataPathHandler pathHandler = httpContext.RequestServices.GetRequiredService <IODataPathHandler>(); odataPath = pathHandler.Parse(_model, serviceRoot, oDataPathAndQuery, httpContext.ODataFeature().UriResolverSettings); } catch (ODataException odataException) { odataPath = null; } if (odataPath != null) { IODataFeature odataFeature = httpContext.ODataFeature(); odataFeature.Model = _model; odataFeature.IsValidODataRequest = true; odataFeature.Path = odataPath; odataFeature.RoutePrefix = _routePrefix; return(true); } else { IODataFeature odataFeature = httpContext.ODataFeature(); odataFeature.IsValidODataRequest = false; return(false); } } // This constraint only applies to incomming request. return(false); }
/// <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(nameof(query)); } if (querySettings == null) { throw Error.ArgumentNull(nameof(querySettings)); } IQueryable result = query; IODataFeature odataFeature = Request.ODataFeature(); // Update the query setting querySettings = Context.UpdateQuerySettings(querySettings, 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); odataFeature.ApplyClause = Apply.ApplyClause; this.Context.ElementClrType = Apply.ResultClrType; } // TODO: need pass the result from $compute to the remaining query options // Construct the actual query and apply them in the following order: filter, orderby, skip, top if (IsAvailableODataQueryOption(Filter, AllowedQueryOptions.Filter)) { if (IsAvailableODataQueryOption(Compute, AllowedQueryOptions.Compute)) { Filter.Compute = Compute; } result = Filter.ApplyTo(result, querySettings); } // If both $search and $filter are specified in the same request, only those items satisfying both criteria are returned // apply $search if (IsAvailableODataQueryOption(Search, AllowedQueryOptions.Search)) { result = Search.ApplyTo(result, querySettings); } if (IsAvailableODataQueryOption(Count, AllowedQueryOptions.Count)) { if (odataFeature.TotalCountFunc == null) { Func <long> countFunc = Count.GetEntityCountFunc(result); if (countFunc != null) { odataFeature.TotalCountFunc = countFunc; } } if (Request.IsCountRequest()) { 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 = GenerateStableOrder(); } if (IsAvailableODataQueryOption(orderBy, AllowedQueryOptions.OrderBy)) { if (IsAvailableODataQueryOption(Compute, AllowedQueryOptions.Compute)) { orderBy.Compute = Compute; } result = orderBy.ApplyTo(result, querySettings); } if (IsAvailableODataQueryOption(SkipToken, AllowedQueryOptions.SkipToken)) { result = SkipToken.ApplyTo(result, querySettings, this); } AddAutoSelectExpandProperties(); if (SelectExpand != null) { var tempResult = ApplySelectExpand(result, querySettings); if (tempResult != default(IQueryable)) { result = tempResult; } } if (IsAvailableODataQueryOption(Skip, AllowedQueryOptions.Skip)) { result = Skip.ApplyTo(result, querySettings); } if (IsAvailableODataQueryOption(Top, AllowedQueryOptions.Top)) { result = Top.ApplyTo(result, querySettings); } result = ApplyPaging(result, querySettings); return(result); }
protected string GetRoutePrefix() { IODataFeature feature = Request.ODataFeature(); return(feature.PrefixName); }
/// <summary> /// Initializes a new instance of the WebApiContext class. /// </summary> /// <param name="feature">The inner feature.</param> public WebApiContext(IODataFeature feature) { this.innerFeature = feature; }
/// <summary> /// Applies the policy to the CandidateSet. /// </summary> /// <param name="httpContext">The context associated with the current request.</param> /// <param name="candidates">The CandidateSet.</param> /// <returns>The task.</returns> public Task ApplyAsync(HttpContext httpContext, CandidateSet candidates) { if (httpContext == null) { throw Error.ArgumentNull(nameof(httpContext)); } IODataFeature odataFeature = httpContext.ODataFeature(); if (odataFeature.Path != null) { // If we have the OData path setting, it means there's some Policy working. // Let's skip this default OData matcher policy. return(Task.CompletedTask); } // The goal of this method is to perform the final matching: // Map between route values matched by the template and the ones we want to expose to the action for binding. // (tweaking the route values is fine here) // Invalidating the candidate if the key/function values are not valid/missing. // Perform overload resolution for functions by looking at the candidates and their metadata. for (var i = 0; i < candidates.Count; i++) { ref CandidateState candidate = ref candidates[i]; if (!candidates.IsValidCandidate(i)) { continue; } IODataRoutingMetadata metadata = candidate.Endpoint.Metadata.OfType <IODataRoutingMetadata>().FirstOrDefault(); if (metadata == null) { continue; } if (odataFeature.Path != null) { // If it's odata endpoint, and we have a path set, let other odata endpoints invalid. candidates.SetValidity(i, false); continue; } ODataTemplateTranslateContext translatorContext = new ODataTemplateTranslateContext(httpContext, candidate.Endpoint, candidate.Values, metadata.Model); ODataPath odataPath = _translator.Translate(metadata.Template, translatorContext); if (odataPath != null) { odataFeature.RoutePrefix = metadata.Prefix; odataFeature.Model = metadata.Model; odataFeature.Path = odataPath; MergeRouteValues(translatorContext.UpdatedValues, candidate.Values); // Shall we break the remaining candidates? // So far the answer is no. Because we can use this matcher to obsolete the unmatched endpoint. // break; } else { candidates.SetValidity(i, false); } }
/// <summary> /// Creates a set of transformed route values that will be used to select an action. /// </summary> /// <param name="httpContext">The <see cref="HttpContext"/> associated with the current request.</param> /// <param name="values">The route values associated with the current match.</param> /// <returns>A task which asynchronously returns a set of route values.</returns> public override ValueTask <RouteValueDictionary> TransformAsync(HttpContext httpContext, RouteValueDictionary values) { if (httpContext == null) { throw Error.ArgumentNull(nameof(httpContext)); } if (values == null) { throw Error.ArgumentNull(nameof(values)); } if (httpContext.ODataFeature().Path != null) { // Noted: if there's a route mapping with ODataPrefix == null, // for example: router.MapODataRoute(routeName: "odata", routePrefix: null, ...) // This route will match all requests. // Therefore, this route will be a candidate and its tranformer will be called. // So, we use the ODataPath setting to verify whether the request is transformed or not. // Maybe we have a better solution later. return(new ValueTask <RouteValueDictionary>(result: null)); } (string routeName, object oDataPathValue) = values.GetODataRouteInfo(); if (routeName != null) { HttpRequest request = httpContext.Request; string oDataPath = oDataPathValue as string; // Create request container request.CreateRequestContainer(routeName); // Check whether the request is a POST targeted at a resource path ending in /$query if (request.IsQueryRequest(oDataPath)) { request.TransformQueryRequest(); oDataPath = oDataPath.Substring(0, oDataPath.LastIndexOf('/' + ODataRouteConstants.QuerySegment, StringComparison.OrdinalIgnoreCase)); values[ODataRouteConstants.ODataPath] = oDataPath; } // We need to call Uri.GetLeftPart(), which returns an encoded Url. // The ODL parser does not like raw values. Uri requestUri = new Uri(request.GetEncodedUrl()); string requestLeftPart = requestUri.GetLeftPart(UriPartial.Path); string queryString = request.QueryString.HasValue ? request.QueryString.ToString() : null; // Call ODL to parse the Request URI. ODataPath path = ODataPathRouteConstraint.GetODataPath(oDataPath, requestLeftPart, queryString, () => request.GetRequestContainer()); if (path != null) { // Set all the properties we need for routing, querying, formatting IODataFeature odataFeature = httpContext.ODataFeature(); odataFeature.Path = path; odataFeature.RouteName = routeName; odataFeature.IsEndpointRouting = true; // mark as Endpoint routing // Noted: we inject the ActionSelector and use it to select the best OData action. // In .NET 5 or later, this maybe change. RouteContext routeContext = new RouteContext(httpContext); var condidates = _selector.SelectCandidates(routeContext); var actionDescriptor = _selector.SelectBestCandidate(routeContext, condidates); ControllerActionDescriptor controllerActionDescriptor = actionDescriptor as ControllerActionDescriptor; if (controllerActionDescriptor != null) { RouteValueDictionary newValues = new RouteValueDictionary(); foreach (var item in values) { newValues.Add(item.Key, item.Value); } foreach (var item in routeContext.RouteData.Values) { newValues[item.Key] = item.Value; } newValues["controller"] = controllerActionDescriptor.ControllerName; newValues["action"] = controllerActionDescriptor.ActionName; newValues["odataPath"] = oDataPathValue; // Noted, here's a working around for mulitiple actions in same controller. // For example, we have two "Get" methods in same controller, in order to help "EndpointSelector" // to select the correct Endpoint, we save the ActionDescriptor value into ODataFeature. odataFeature.ActionDescriptor = controllerActionDescriptor; // Add handler to handle options calls. The routing criteria has been patched to allow endpoint discovery using the correct cors headers if (request.Method.Equals("OPTIONS", StringComparison.OrdinalIgnoreCase)) { var metadata = actionDescriptor.EndpointMetadata; // For option request can set this as the action will be handled by the cors middleware var metadataCollection = metadata?.Any() == true ? new EndpointMetadataCollection(metadata) : EndpointMetadataCollection.Empty; // This workaround allows the default cors middleware to read the annotations if the user has them enabling fine-grained cors access control with endpoints var endpoint = new Endpoint(null, metadataCollection, controllerActionDescriptor.ActionName); httpContext.SetEndpoint(endpoint); } return(new ValueTask <RouteValueDictionary>(newValues)); } } else { // The request doesn't match this route so dispose the request container. request.DeleteRequestContainer(true); } } return(new ValueTask <RouteValueDictionary>(result: null)); }
public Task ApplyAsync(HttpContext httpContext, CandidateSet candidates) { if (httpContext == null) { throw new ArgumentNullException(nameof(httpContext)); } IODataFeature odataFeature = httpContext.ODataFeature(); if (odataFeature.Path != null) { // If we have the OData path setting, it means there's some Policy working. // Let's skip this default OData matcher policy. return(Task.CompletedTask); } for (var i = 0; i < candidates.Count; i++) { ref CandidateState candidate = ref candidates[i]; if (!candidates.IsValidCandidate(i)) { continue; } IODataRoutingMetadata metadata = candidate.Endpoint.Metadata.OfType <IODataRoutingMetadata>().FirstOrDefault(); if (metadata == null) { continue; } // Get api-version query from HttpRequest? QueryStringApiVersionReader reader = new QueryStringApiVersionReader("api-version"); string apiVersionStr = reader.Read(httpContext.Request); if (apiVersionStr == null) { candidates.SetValidity(i, false); continue; } ApiVersion apiVersion = ApiVersion.Parse(apiVersionStr); IEdmModel model = GetEdmModel(apiVersion); if (model == null) { candidates.SetValidity(i, false); continue; } if (!IsApiVersionMatch(candidate.Endpoint.Metadata, apiVersion)) { candidates.SetValidity(i, false); continue; } ODataTemplateTranslateContext translatorContext = new ODataTemplateTranslateContext(httpContext, candidate.Endpoint, candidate.Values, model); try { ODataPath odataPath = _translator.Translate(metadata.Template, translatorContext); if (odataPath != null) { odataFeature.RoutePrefix = metadata.Prefix; odataFeature.Model = model; odataFeature.Path = odataPath; ODataOptions options = new ODataOptions(); UpdateQuerySetting(options); options.AddRouteComponents(model); odataFeature.Services = options.GetRouteServices(string.Empty); MergeRouteValues(translatorContext.UpdatedValues, candidate.Values); } else { candidates.SetValidity(i, false); } } catch { candidates.SetValidity(i, false); } }