我正在实现一个Web API接口,以支持一些相当复杂的查询来运行它,并且遇到了最大请求URI长度的问题。
我的Web API方法的定义如下(使用Automapper执行DTO投影):
public IQueryable<ReportModel> Get(ODataQueryOptions<Report> queryOptions)
{
var query = DbContext.Query<Report>();
return (queryOptions.ApplyTo(query) as IQueryable<Report>).WithTranslations().Project(MappingEngine).To<ReportModel>().WithTranslations();
}
我的请求包含一个动态构建的OData查询,其中包含可能大量的&lt; field eq Id&#39;捕获到ODataQueryOptions参数中的过滤器,然后应用于IQueryable数据库上下文。例如:
http://example.com/api/Report?$filter=(Field1+eq+1%20or%20Field1+eq+5%20or%20Field1+eq+10%20or%20Field1+eq+15...
一旦请求URI的长度达到某个限制,就会出现问题。任何URI长度超过该限制的请求都会导致404错误。经过一些测试后,此限制似乎在2KB范围内(具有2065个字符的URI工作正常,而使用Chrome,IE或FF的2105个则无法使用)。
对此的简单解决方案似乎是将请求类型从GET更改为POST请求,在主体中发送搜索查询而不是URI。我遇到了一些试图使其工作的问题,因为我似乎无法从POST请求中正确填充ODataQueryOptions对象。我的Web API方法现在看起来像这样:
public IQueryable<ReportModel> Post([FromBody] ODataQueryOptions<Report> queryOptions)
{
var query = DbContext.Query<Report>();
return (queryOptions.ApplyTo(query) as IQueryable<Report>).WithTranslations().Project(MappingEngine).To<ReportModel>().WithTranslations();
}
正如您所看到的,我尝试从请求正文填充查询选项而不是URI。到目前为止,我还没有能够从请求中填充ODataQueryOptions参数,并且该参数导致为&#39; null&#39;。如果我删除了[FromBody]&#39;如果属性,查询选项对象将从请求URI中正确填充,但仍存在相同的URI长度问题。
以下是我如何从浏览器调用该方法的示例(使用jQuery):
$.ajax({
url: "/API/Report",
type: "POST",
data: ko.toJSON({
'$filter': 'Field1+eq+1%20or%20Field1+eq+5%20or%20Field1+eq+10%20or%20Field1+eq+15...'
}),
dataType: "json",
processData: false,
contentType: 'application/json; charset=utf-8',
});
首先,是否有可能做我在这里尝试做的事情(在请求正文中发布ODataQueryOptions)?如果是这样,我正确构建POST请求吗?我还有什么别的吗?
答案 0 :(得分:2)
您可以在帖子正文中传递查询选项的原始字符串值, 并在控制器的post方法中构造一个查询选项。
以下代码仅适用于过滤查询选项。 您可以用同样的方式添加其他查询选项。
public IQueryable<ReportModel> Post([FromBody] string filterRawValue)
{
var context = new ODataQueryContext(Request.ODataProperties().Model, typeof(Report));
var filterQueryOption = new FilterQueryOption(filterRawValue, context);
var query = DbContext.Query<Report>();
return (filterQueryOption.ApplyTo(query) as IQueryable<Report>).WithTranslations().Project(MappingEngine).To<ReportModel>().WithTranslations();
}
答案 1 :(得分:0)
我刚刚基于原始版本编写了ODataQueryOption的这种快速实现。区别在于odata的属性是从HttpRequest而不是原始版本的HttpRequestMessage中获取的。仍然我相信,最好增加Web服务器配置中的最大请求uri长度,并使用GET而不是POST和默认的ODataQueryOption,这是我最终在自己的项目中完成的。
public class ODataQueryOptionsPost<T> : ODataQueryOptions<T>
{
private RawValues2 rawValues;
private IAssembliesResolver _assembliesResolver2;
public FilterQueryOption FilterQueryOption { get; set; }
public ODataQueryOptionsPost(ODataQueryContext context, HttpRequestMessage request, HttpRequest httpRequest) :
base(context, request)
{
if (context == null)
throw new Exception(nameof(context));
if (request == null)
throw new Exception(nameof(request));
if (request.GetConfiguration() != null)
_assembliesResolver2 = request.GetConfiguration().Services.GetAssembliesResolver();
_assembliesResolver2 =
this._assembliesResolver2 ?? (IAssembliesResolver) new DefaultAssembliesResolver();
this.rawValues = new RawValues2();
var filter = GetValue(httpRequest.Params, "$filter");
if (!string.IsNullOrWhiteSpace(filter))
{
rawValues.Filter = filter;
FilterQueryOption = new FilterQueryOption(filter, context);
}
var orderby = GetValue(httpRequest.Params, "$orderby");
if (!string.IsNullOrWhiteSpace(orderby))
{
rawValues.OrderBy = orderby;
OrderbyOption = new OrderByQueryOption(orderby, context);
}
var top = GetValue(httpRequest.Params, "$top");
if (!string.IsNullOrWhiteSpace(top))
{
rawValues.Top = top;
TopOption = new TopQueryOption(top, context);
}
var skip = GetValue(httpRequest.Params, "$skip");
if (!string.IsNullOrWhiteSpace(skip))
{
rawValues.Skip = skip;
SkipOption = new SkipQueryOption(skip, context);
}
var select = GetValue(httpRequest.Params, "$select");
if (!string.IsNullOrWhiteSpace(select))
{
rawValues.Select = select;
}
var inlinecount = GetValue(httpRequest.Params, "$inlinecount");
if (!string.IsNullOrWhiteSpace(inlinecount))
{
rawValues.InlineCount = inlinecount;
InlineCountOption = new InlineCountQueryOption(inlinecount, context);
}
var expand = GetValue(httpRequest.Params, "$expand");
if (!string.IsNullOrWhiteSpace(expand))
{
rawValues.Expand = expand;
}
var format = GetValue(httpRequest.Params, "$format");
if (!string.IsNullOrWhiteSpace(format))
{
rawValues.Format = format;
}
var skiptoken = GetValue(httpRequest.Params, "$skiptoken");
if (!string.IsNullOrWhiteSpace(skiptoken))
{
rawValues.SkipToken = skiptoken;
}
}
public InlineCountQueryOption InlineCountOption { get; set; }
public SkipQueryOption SkipOption { get; set; }
public TopQueryOption TopOption { get; set; }
public OrderByQueryOption OrderbyOption { get; set; }
private static string GetValue(NameValueCollection httpRequestParams, string key)
{
return httpRequestParams.GetValues(key)?.SingleOrDefault();
}
public override IQueryable ApplyTo(IQueryable query, ODataQuerySettings querySettings)
{
if (query == null)
throw new Exception(nameof(query));
if (querySettings == null)
throw new Exception(nameof(querySettings));
IQueryable queryable = query;
if (this.FilterQueryOption != null)
queryable = this.FilterQueryOption.ApplyTo(queryable, querySettings, this._assembliesResolver2);
if (this.InlineCountOption != null && !this.Request.ODataProperties().TotalCount.HasValue)
{
long? entityCount = this.InlineCountOption.GetEntityCount(queryable);
if (entityCount.HasValue)
this.Request.ODataProperties().TotalCount = new long?(entityCount.Value);
}
OrderByQueryOption orderBy = this.OrderbyOption;
if (querySettings.EnsureStableOrdering &&
(this.Skip != null || this.Top != null || querySettings.PageSize.HasValue))
orderBy = orderBy == null
? GenerateDefaultOrderBy(this.Context)
: EnsureStableSortOrderBy(orderBy, this.Context);
if (orderBy != null)
queryable = (IQueryable) orderBy.ApplyTo(queryable, querySettings);
if (this.SkipOption != null)
queryable = this.SkipOption.ApplyTo(queryable, querySettings);
if (this.TopOption != null)
queryable = this.TopOption.ApplyTo(queryable, querySettings);
if (this.SelectExpand != null)
{
this.Request.ODataProperties().SelectExpandClause = this.SelectExpand.SelectExpandClause;
queryable = this.SelectExpand.ApplyTo(queryable, querySettings);
}
if (querySettings.PageSize.HasValue)
{
bool resultsLimited;
queryable = LimitResults(queryable as IQueryable<T>, querySettings.PageSize.Value, out resultsLimited);
if (resultsLimited && this.Request.RequestUri != (Uri) null &&
(this.Request.RequestUri.IsAbsoluteUri && this.Request.ODataProperties().NextLink == (Uri) null))
this.Request.ODataProperties().NextLink =
GetNextPageLink(this.Request, querySettings.PageSize.Value);
}
return queryable;
}
private static OrderByQueryOption GenerateDefaultOrderBy(ODataQueryContext context)
{
string rawValue = string.Join(",",
GetAvailableOrderByProperties(context)
.Select<IEdmStructuralProperty, string>(
(Func<IEdmStructuralProperty, string>) (property => property.Name)));
if (!string.IsNullOrEmpty(rawValue))
return new OrderByQueryOption(rawValue, context);
return (OrderByQueryOption) null;
}
private static OrderByQueryOption EnsureStableSortOrderBy(OrderByQueryOption orderBy, ODataQueryContext context)
{
HashSet<string> usedPropertyNames = new HashSet<string>(orderBy.OrderByNodes.OfType<OrderByPropertyNode>()
.Select<OrderByPropertyNode, string>((Func<OrderByPropertyNode, string>) (node => node.Property.Name)));
IEnumerable<IEdmStructuralProperty> source = GetAvailableOrderByProperties(context)
.Where<IEdmStructuralProperty>(
(Func<IEdmStructuralProperty, bool>) (prop => !usedPropertyNames.Contains(prop.Name)));
if (source.Any<IEdmStructuralProperty>())
{
orderBy = new OrderByQueryOption(orderBy.RawValue, context);
foreach (IEdmStructuralProperty structuralProperty in source)
orderBy.OrderByNodes.Add((OrderByNode) new OrderByPropertyNode((IEdmProperty) structuralProperty,
OrderByDirection.Ascending));
}
return orderBy;
}
private static IEnumerable<IEdmStructuralProperty> GetAvailableOrderByProperties(ODataQueryContext context)
{
IEdmEntityType elementType = context.ElementType as IEdmEntityType;
if (elementType != null)
return (IEnumerable<IEdmStructuralProperty>) (elementType.Key().Any<IEdmStructuralProperty>()
? elementType.Key()
: elementType.StructuralProperties()
.Where<IEdmStructuralProperty>(
(Func<IEdmStructuralProperty, bool>) (property => property.Type.IsPrimitive())))
.OrderBy<IEdmStructuralProperty, string>(
(Func<IEdmStructuralProperty, string>) (property => property.Name));
return Enumerable.Empty<IEdmStructuralProperty>();
}
internal static Uri GetNextPageLink(HttpRequestMessage request, int pageSize)
{
return GetNextPageLink(request.RequestUri, request.GetQueryNameValuePairs(), pageSize);
}
internal static Uri GetNextPageLink(Uri requestUri, IEnumerable<KeyValuePair<string, string>> queryParameters,
int pageSize)
{
StringBuilder stringBuilder = new StringBuilder();
int num = pageSize;
foreach (KeyValuePair<string, string> queryParameter in queryParameters)
{
string key = queryParameter.Key;
string str1 = queryParameter.Value;
switch (key)
{
case "$top":
int result1;
if (int.TryParse(str1, out result1))
{
str1 = (result1 - pageSize).ToString((IFormatProvider) CultureInfo.InvariantCulture);
break;
}
break;
case "$skip":
int result2;
if (int.TryParse(str1, out result2))
{
num += result2;
continue;
}
continue;
}
string str2 = key.Length <= 0 || key[0] != '$'
? Uri.EscapeDataString(key)
: 36.ToString() + Uri.EscapeDataString(key.Substring(1));
string str3 = Uri.EscapeDataString(str1);
stringBuilder.Append(str2);
stringBuilder.Append('=');
stringBuilder.Append(str3);
stringBuilder.Append('&');
}
stringBuilder.AppendFormat("$skip={0}", (object) num);
return new UriBuilder(requestUri)
{
Query = stringBuilder.ToString()
}.Uri;
}
}
public class RawValues2
{
public string Filter { get; set; }
public string OrderBy { get; set; }
public string Top { get; set; }
public string Skip { get; set; }
public string Select { get; set; }
public string InlineCount { get; set; }
public string Expand { get; set; }
public string Format { get; set; }
public string SkipToken { get; set; }
}
要使用它,我们将需要当前的请求对象
[HttpPost]
public async Task<PageResult<TypeOfYourViewModel>> GetDataViaPost(ODataQueryOptions<TypeOfYourViewModel> options)
{
IQueryable<TypeOfYourViewModel> result = await GetSomeData();
var querySettings = new ODataQuerySettings
{
EnsureStableOrdering = false,
HandleNullPropagation = HandleNullPropagationOption.False
};
var optionsPost = new ODataQueryOptionsPost<TypeOfYourViewModel>(options.Context, Request, HttpContext.Current.Request);
var finalResult = optionsPost.ApplyTo(result, querySettings);
var uri = Request.ODataProperties().NextLink;
var inlineCount = Request.ODataProperties().TotalCount;
var returnedResult = (finalResult as IQueryable<T>).ToList();
return new PageResult<TypeOfYourViewModel>(
returnedResult,
uri,
inlineCount
);
}
答案 2 :(得分:0)
我的2美分是dotnet core 2.2的价格。应该也可以在dotnet core 3.x上工作,但不能保证。
处理所有OData查询参数。
这会将raw
参数从ODataActionParameters
传递到HttpRequest
的{{1}}属性(不包括主机),或者如果不存在,我们将创建一个Query
。
适用于OData查询选项的ODataActionParameters
的扩展名。
IQueryable{T}
在下面的示例中,您将需要/// <summary>
/// Extensions for <see cref="IQueryable{T}" /> interface.
/// </summary>
public static class IQueryableExtensions
{
/// <summary>
/// Apply the individual query to the given IQueryable in the right order, based on provided <paramref name="actionParameters" />.
/// </summary>
/// <param name="self">The <see cref="IQueryable{TEntity}" /> instance.</param>
/// <param name="request">The <see cref="HttpRequest" /> instance.</param>
/// <param name="actionParameters">The <see cref="ODataRawQueryOptions" /> instance.</param>
/// <param name="serviceProvider">The service provider.</param>
/// <param name="odataQuerySettings">The <see cref="ODataQuerySettings" /> instance.</param>
/// <typeparam name="TEntity">The entity type.</typeparam>
/// <returns>Returns <see cref="IQueryable{TEntity}" /> instance.</returns>
public static IQueryable ApplyOData<TEntity>(this IQueryable<TEntity> 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<string, StringValues>());
}
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<IEdmModel>();
var odataQueryContext = new ODataQueryContext(edmModel, typeof(TEntity), null);
var odataQueryOptions = new ODataQueryOptions<TEntity>(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);
}
}
的扩展名,如下所示:
ActionConfiguration
示例用法:
// <summary>
/// Extensions for <see cref="ActionConfiguration" />.
/// </summary>
public static class ActionConfigurationExtensions
{
/// <summary>
/// Adds OData parameters to the <see cref="ActionConfiguration" />.
/// </summary>
/// <param name="actionConfiguration">The <see cref="ActionConfiguration" /> instance.</param>
/// <returns>Returns current <see cref="ActionConfiguration" /> instance.</returns>
public static ActionConfiguration AddODataParameters(this ActionConfiguration actionConfiguration)
{
foreach (var name in typeof(ODataRawQueryOptions).GetProperties().Select(p => p.Name.ToLower()))
{
actionConfiguration
.Parameter<string>(name)
.Optional();
}
actionConfiguration
.Parameter<string>("raw")
.Optional();
return actionConfiguration;
}
}
builder.EntityType<ExampleEntity>()
.Collection
.Action(nameof(ExampleController.GetExamples))
.ReturnsCollectionFromEntitySet<ExampleEntity>("Examples")
.AddODataParameters();
HTTP Post请求示例:
URL:/ odata / examples / getexamples 内容:
[HttpPost]
public ActionResult<IQueryable<ExampleEntity>> 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<ExampleEntity>().ApplyOData(this.Request, parameters, serviceProvider));
}
{
"raw": "http://localhost/odata/examples?%24filter%3Dname%20eq%20%27test%27"
}