Possible to post ODataQueryOptions from the Http Request body?

后端 未结 3 2099
盖世英雄少女心
盖世英雄少女心 2021-02-15 16:44

I\'m implementing a Web API interface to support some fairly complex queries to run against it and have run up against an issue with the maximum request URI length.

Th

3条回答
  •  野趣味
    野趣味 (楼主)
    2021-02-15 17:14

    My two cents for dotnet core 2.2. Should also work on dotnet core 3.x but not guaranteed.

    Handles all OData query parameters.

    This passes the raw argument from ODataActionParameters to the HttpRequest's Query property (excluding the host), or if not present, we create one base of the ODataActionParameters.

    Extension for IQueryable{T} which applies OData query options.

    /// 
    /// Extensions for  interface.
    /// 
    public static class IQueryableExtensions
    {
        /// 
        /// Apply the individual query to the given IQueryable in the right order, based on provided .
        /// 
        /// The  instance.
        /// The  instance.
        /// The  instance.
        /// The service provider.
        /// The  instance.
        /// The entity type.
        /// Returns  instance.
        public static IQueryable ApplyOData(this IQueryable self, HttpRequest request, ODataActionParameters actionParameters, IServiceProvider serviceProvider, ODataQuerySettings odataQuerySettings = default)
        {
            var queryOptionsType = typeof(ODataQueryOptions);
    
            if (self is null)
            {
                throw new ArgumentNullException(nameof(self));
            }
    
            if (actionParameters is null)
            {
                throw new ArgumentNullException(nameof(actionParameters));
            }
    
            if (odataQuerySettings is null)
            {
                odataQuerySettings = new ODataQuerySettings();
            }
    
            var rawQuery = string.Empty;
            if (actionParameters.ContainsKey("raw"))
            {
                rawQuery = HttpUtility.UrlDecode(actionParameters["raw"].ToString());
                actionParameters.Remove("raw");
    
                if (Uri.TryCreate(rawQuery, UriKind.Absolute, out Uri absRawQuery))
                {
                    rawQuery = absRawQuery.Query;
                }
    
                request.Query = new QueryCollection(HttpUtility.ParseQueryString(rawQuery).ToDictionary());
            }
            else
            {
                request.Query = new QueryCollection(actionParameters.ToDictionary(k => $"${HttpUtility.UrlDecode(k.Key)}", v => new StringValues(HttpUtility.UrlDecode(v.Value.ToString()))));
            }
    
            //// request.QueryString = new QueryString("?" + string.Join("&", request.Query.Select(x => x.Key + "=" + x.Value)));
    
            var edmModel = serviceProvider.GetRequiredService();
            var odataQueryContext = new ODataQueryContext(edmModel, typeof(TEntity), null);
            var odataQueryOptions = new ODataQueryOptions(odataQueryContext, request);
            var queryOptionParser = new ODataQueryOptionParser(
                edmModel,
                edmModel.FindType(typeof(TEntity).FullName).AsElementType(),
                edmModel.FindDeclaredNavigationSource(typeof(TEntity).FullName),
                request.Query.ToDictionary(k => k.Key, v => v.Value.ToString()),
                serviceProvider);
    
            return odataQueryOptions.ApplyTo(self, odataQuerySettings);
        }
    }
    

    In the example below you will need an extension for ActionConfiguration like this:

    // 
    /// Extensions for .
    /// 
    public static class ActionConfigurationExtensions
    {
        /// 
        /// Adds OData parameters to the .
        /// 
        /// The  instance.
        /// Returns current  instance.
        public static ActionConfiguration AddODataParameters(this ActionConfiguration actionConfiguration)
        {
            foreach (var name in typeof(ODataRawQueryOptions).GetProperties().Select(p => p.Name.ToLower()))
            {
                actionConfiguration
                    .Parameter(name)
                    .Optional();
            }
    
            actionConfiguration
                    .Parameter("raw")
                    .Optional();
    
            return actionConfiguration;
        }
    }
    

    Example how to use it:

    1. Create an action like follow:
    builder.EntityType()
       .Collection
       .Action(nameof(ExampleController.GetExamples))
       .ReturnsCollectionFromEntitySet("Examples")
       .AddODataParameters();
    
    1. Add an action in your controller:
    [HttpPost]
    public ActionResult> GetExamples(ODataActionParameters parameters, [FromServices] IServiceProvider serviceProvider)
    {
       if (parameters is null)
       {
           throw new ArgumentNullException(nameof(parameters));
       }
    
       if (serviceProvider is null)
       {
           throw new ArgumentNullException(nameof(serviceProvider));
       }
    
       return this.Ok(this.Repository.GetAll().ApplyOData(this.Request, parameters, serviceProvider));
    }
    

    Example HTTP Post requests:

    URL: /odata/examples/getexamples CONTENT:

    {
      "raw": "http://localhost/odata/examples?%24filter%3Dname%20eq%20%27test%27"
    }
    
    {
      "filter": "name eq 'test'",
      "skip": "20",
      "count": "true"
    }
    

提交回复
热议问题