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
I've just wrote this quick implementation of ODataQueryOption based on the original version. The difference is that properties for odata are fetched from HttpRequest instead of HttpRequestMessage as in original version. Still I believe it is better just to increase the maximum request uri length in the web server configuration and use GET instead of POST with the default ODataQueryOption , which I did eventually in my own project.
public class ODataQueryOptionsPost : ODataQueryOptions
{
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, 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(
(Func) (property => property.Name)));
if (!string.IsNullOrEmpty(rawValue))
return new OrderByQueryOption(rawValue, context);
return (OrderByQueryOption) null;
}
private static OrderByQueryOption EnsureStableSortOrderBy(OrderByQueryOption orderBy, ODataQueryContext context)
{
HashSet usedPropertyNames = new HashSet(orderBy.OrderByNodes.OfType()
.Select((Func) (node => node.Property.Name)));
IEnumerable source = GetAvailableOrderByProperties(context)
.Where(
(Func) (prop => !usedPropertyNames.Contains(prop.Name)));
if (source.Any())
{
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 GetAvailableOrderByProperties(ODataQueryContext context)
{
IEdmEntityType elementType = context.ElementType as IEdmEntityType;
if (elementType != null)
return (IEnumerable) (elementType.Key().Any()
? elementType.Key()
: elementType.StructuralProperties()
.Where(
(Func) (property => property.Type.IsPrimitive())))
.OrderBy(
(Func) (property => property.Name));
return Enumerable.Empty();
}
internal static Uri GetNextPageLink(HttpRequestMessage request, int pageSize)
{
return GetNextPageLink(request.RequestUri, request.GetQueryNameValuePairs(), pageSize);
}
internal static Uri GetNextPageLink(Uri requestUri, IEnumerable> queryParameters,
int pageSize)
{
StringBuilder stringBuilder = new StringBuilder();
int num = pageSize;
foreach (KeyValuePair 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; }
}
To use it we're going to need current request object
[HttpPost]
public async Task> GetDataViaPost(ODataQueryOptions options)
{
IQueryable result = await GetSomeData();
var querySettings = new ODataQuerySettings
{
EnsureStableOrdering = false,
HandleNullPropagation = HandleNullPropagationOption.False
};
var optionsPost = new ODataQueryOptionsPost(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).ToList();
return new PageResult(
returnedResult,
uri,
inlineCount
);
}