/// <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);
            }
        }
Exemple #3
0
        /// <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);
        }
Exemple #4
0
        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);
                }
            }
Exemple #5
0
        /// <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);
        }
Exemple #6
0
        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);
        }
Exemple #7
0
        /// <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);
        }
Exemple #8
0
        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());
        }
Exemple #10
0
        /// <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));
        }
Exemple #11
0
        /// <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);
        }
Exemple #17
0
        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));
        }
Exemple #22
0
        /// <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);
        }
Exemple #23
0
        /// <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);
        }
Exemple #27
0
 /// <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));
        }
Exemple #30
0
        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);
                }
            }