AspNetCoreOData
AspNetCoreOData copied to clipboard
TargetInvocationException when returning a null enumerable
Assemblies affected ASP.NET Core OData 8.0.11
Describe the bug
If we return a model which has an enumerable property for example an IEnumerable
- The endpoint has a PageSize defined
- We are using query parameters to select the IEnumerable property which value is null
Reproduce steps
namespace Issue
{
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.OData;
using Microsoft.AspNetCore.OData.Query;
using Microsoft.AspNetCore.OData.Routing.Controllers;
using Microsoft.OData.Edm;
using Microsoft.OData.ModelBuilder;
public class WeatherForecast
{
[Key]
public Guid Key { get; set; }
public IEnumerable<string> Additional => null;
}
public class WeatherForecastController : ODataController
{
[EnableQuery(PageSize = 4)]
public IEnumerable<WeatherForecast> Get()
{
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Key = Guid.NewGuid(),
});
}
}
public class Program
{
public static void Main(string[] args)
{
IEdmModel GetEdmModel()
{
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
var entity = builder.EntitySet<WeatherForecast>("WeatherForecast");
return builder.GetEdmModel();
}
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers().AddOData(opt =>
{
opt.AddRouteComponents("odata", GetEdmModel()).Select().Expand().OrderBy().SetMaxTop(10).Count().Filter().SkipToken();
});
var app = builder.Build();
app.UseDeveloperExceptionPage();
app.UseRouting();
app.UseEndpoints(endpoints => endpoints.MapControllers());
app.Run();
}
}
}
Request/Response
Query http://localhost:54321/odata/WeatherForecast
and we can see the Additional
property is serialized as an empty array which I guess is the correct behavior
{
"@odata.context": "http://localhost:54321/odata/$metadata#WeatherForecast",
"value": [
{
"Key": "10fcb5d4-7558-4527-a88d-59ebd7ed18be",
"Additional": []
},
{
"Key": "55542ada-9a3a-4e28-a338-7b2908360654",
"Additional": []
},
{
"Key": "a57bb67c-ce54-4b1e-b51e-b929068e9371",
"Additional": []
},
{
"Key": "b035b3f9-244b-4251-9d4a-104ce0c26b68",
"Additional": []
}
],
"@odata.nextLink": "http://localhost:54321/odata/WeatherForecast?$skiptoken=Key-b035b3f9-244b-4251-9d4a-104ce0c26b68"
}
But if we make a query to http://localhost:54321/odata/WeatherForecast?$select=Key,Additional
a System.Reflection.TargetInvocationException
is thrown
System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation.
---> System.ArgumentNullException: Value cannot be null. (Parameter 'source')
at System.Linq.ThrowHelper.ThrowArgumentNullException(ExceptionArgument argument)
at lambda_method15(Closure , WeatherForecast )
at System.Linq.Enumerable.SelectIPartitionIterator`2.MoveNext()
at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
at Microsoft.AspNetCore.OData.Query.Container.TruncatedCollection`1..ctor(IQueryable`1 source, Int32 pageSize, Boolean parameterize)
at Microsoft.AspNetCore.OData.Query.ODataQueryOptions.LimitResults[T](IQueryable`1 queryable, Int32 limit, Boolean parameterize, Boolean& resultsLimited)
--- End of inner exception stack trace ---
at System.RuntimeMethodHandle.InvokeMethod(Object target, Span`1& arguments, Signature sig, Boolean constructor, Boolean wrapExceptions)
at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
at Microsoft.AspNetCore.OData.Query.ODataQueryOptions.LimitResults(IQueryable queryable, Int32 limit, Boolean parameterize, Boolean& resultsLimited)
at Microsoft.AspNetCore.OData.Query.ODataQueryOptions.ApplyPaging(IQueryable result, ODataQuerySettings querySettings)
at Microsoft.AspNetCore.OData.Query.ODataQueryOptions.ApplyTo(IQueryable query, ODataQuerySettings querySettings)
at Microsoft.AspNetCore.OData.Query.EnableQueryAttribute.ExecuteQuery(Object responseValue, IQueryable singleResultCollection, ControllerActionDescriptor actionDescriptor, HttpRequest request)
at Microsoft.AspNetCore.OData.Query.EnableQueryAttribute.OnActionExecuted(ActionExecutedContext actionExecutedContext, Object responseValue, IQueryable singleResultCollection, ControllerActionDescriptor actionDescriptor, HttpRequest request)
at Microsoft.AspNetCore.OData.Query.EnableQueryAttribute.OnActionExecuted(ActionExecutedContext actionExecutedContext)
at Microsoft.AspNetCore.Mvc.Filters.ActionFilterAttribute.OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()
--- End of stack trace from previous location ---
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|20_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
If we have the attribute like [EnableQuery]
instead of [EnableQuery(PageSize = 4)]
the issue doesn't happen
Expected behavior Should be consistent and not throw the exception in that specific case
You should not have null
collection properties. I'd strongly suggest initializing them to empty lists instead.
Disclaimer: I'm not saying this is not a bug necessarily...
I am seeing the same behavior when attempting to $expand property in a derived type within a the broader collection (i.e. /myBaseTypes?$expand=my.derived.type.fullname/myNavigationProperty
)