Search code examples
asp.net-web-apiodata

Microsoft.AspNet.OData [EnableQueryAttribute] fails when combined with [Route] attribute


I am using the nuget package Microsoft.AspNet.OData 6.1.0. to create an odata enabled action. I have ran into an issue with [Route] attribute in the following setup.

    // The Setup 
    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            // configure non odata ...

            //OData support
            config.AddODataQueryFilter();
            config.EnableDependencyInjection();

            ODataConventionModelBuilder builder = new ODataConventionModelBuilder();

            builder.EnableLowerCamelCase(NameResolverOptions.ProcessReflectedPropertyNames | NameResolverOptions.ProcessExplicitPropertyNames);

            builder.EntitySet<MixedRealityCatalogItem>("KISS");

            var edmModel = builder.GetEdmModel();

            config.MapODataServiceRoute("ODataRoute", "/", edmModel);

            config.Count().Filter().OrderBy().Select().MaxTop(2000);
        }
    }


   // The Controller
    public class KISSController : ApiController
    {
        public KISSController(IRepository<MixedRealityCatalogItem> repository)
        {
            this.Repository = repository ?? throw new ArgumentNullException(nameof(repository));
        }

        private IRepository<MixedRealityCatalogItem> Repository { get; set; }

        [EnableQuery(PageSize = 2000, MaxAnyAllExpressionDepth = 2)]
        [HttpGet]
        [Route("KISS")] // <-- this causes and exception
        public IEnumerable<MixedRealityCatalogItem> GetAll(ODataQueryOptions<MixedRealityCatalogItem> options)
        {

            IQueryable<MixedRealityCatalogItem> results = this.Repository.GetAll();

            var settings = new ODataQuerySettings
            {
                HandleNullPropagation = HandleNullPropagationOption.False,
                EnableConstantParameterization = false,
                EnsureStableOrdering = false
            };
            //Builds, but does not execute the Iqueryable
            IQueryable finalQuery = options.ApplyTo(results, settings);

            var finalResults = new List<MixedRealityCatalogItem>();

            //Will actually execute the query against the DB
            foreach (MixedRealityCatalogItem item in finalQuery)
            {
                finalResults.Add(item);
            }
            return finalResults;
        }
    }

If I comment out the Route attribute the GetAll action works for all queries, but if the route attribute is present, I get the following exception when every I user a filter that evaluates an 'any' statement.

{
  "Message": "An error has occurred.",
  "ExceptionMessage": "Value cannot be null.\r\nParameter name: type",
  "ExceptionType": "System.ArgumentNullException",
  "StackTrace": "   at System.Linq.Expressions.Expression.Parameter(Type type, String name)\r\n   at System.Web.OData.Query.Expressions.FilterBinder.HandleLambdaParameters(IEnumerable`1 rangeVariables)\r\n   at System.Web.OData.Query.Expressions.FilterBinder.BindAnyNode(AnyNode anyNode)\r\n   at System.Web.OData.Query.Expressions.FilterBinder.BindExpression(SingleValueNode expression, RangeVariable rangeVariable, Type elementType)\r\n   at System.Web.OData.Query.Expressions.FilterBinder.BindFilterClause(FilterBinder binder, FilterClause filterClause, Type filterType)\r\n   at System.Web.OData.Query.FilterQueryOption.ApplyTo(IQueryable query, ODataQuerySettings querySettings)\r\n   at System.Web.OData.Query.ODataQueryOptions.ApplyTo(IQueryable query, ODataQuerySettings querySettings)\r\n   at AssetManager.Api.Controllers.Contracts.KISSController.GetAll(ODataQueryOptions`1 options) in D:\\git\\LSource\\AssetManagerAPI\\src\\AssetManager.Web\\Controllers\\Contracts\\AssetManagerBaseApiController.cs:line 48\r\n   at lambda_method(Closure , Object , Object[] )\r\n   at System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ActionExecutor.<>c__DisplayClass10.<GetExecutor>b__9(Object instance, Object[] methodParameters)\r\n   at System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ExecuteAsync(HttpControllerContext controllerContext, IDictionary`2 arguments, CancellationToken cancellationToken)\r\n--- End of stack trace from previous location where exception was thrown ---\r\n   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n   at System.Web.Http.Controllers.ApiControllerActionInvoker.<InvokeActionAsyncCore>d__0.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n   at System.Web.Http.Filters.ActionFilterAttribute.<CallOnActionExecutedAsync>d__5.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n   at System.Web.Http.Filters.ActionFilterAttribute.<CallOnActionExecutedAsync>d__5.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n   at System.Web.Http.Filters.ActionFilterAttribute.<ExecuteActionFilterAsyncCore>d__0.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n   at System.Web.Http.Controllers.ActionFilterResult.<ExecuteAsync>d__2.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n   at System.Web.Http.Controllers.AuthenticationFilterResult.<ExecuteAsync>d__0.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n   at System.Web.Http.Dispatcher.HttpControllerDispatcher.<SendAsync>d__1.MoveNext()"
}

What am I doing wrong? In this simple example the route attribute is not necessary but in my real situation it matters.

Note:

  • This maybe a bug in odata see: 1471
  • There is a source repo setup for this issue: repo

Solution

  • It turns out there was a bug related to the DataContract attribute this was fixed in version 7.0.0

    Pull Request that fixed the bug: https://github.com/OData/WebApi/pull/1484

    Nuget Version with bug fix: https://www.nuget.org/packages/Microsoft.AspNet.OData/7.0.0