OData / AspNetCoreOData

ASP.NET Core OData: A server library built upon ODataLib and ASP.NET Core

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

[Versions >= 8.2.0] EntitySet.EntityType.Action returns "Value cannot be null. (Parameter 'type')"

CronKz opened this issue · comments

Assemblies affected
Not working -> Versions >= 8.2.0.
Working -> Versions between 8.0.2 and 8.1.2 (Tested)

Describe the bug
Sending a POST request to an endpoint which maps to an EdmModel.EntitySet.EntityType.Action fails.

Reproduce steps

  1. Create a fresh Net 8 ASP .NET Web API Project using controllers
  2. Add <PackageReference Include="Microsoft.AspNetCore.OData" Version="8.2.4" /> to your project
  3. Add prop public int Id { get; set; } to WeatherForecast class to comply with OData key
  4. In Program.cs add this code
static IEdmModel GetEdmModel()
{
    var builder = new ODataConventionModelBuilder();
    var entitySet = builder.EntitySet<WeatherForecast>(nameof(WeatherForecast));
    entitySet.EntityType.Action(nameof(WeatherForecastController.Execute));
    return builder.GetEdmModel();
}
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers().AddOData(options => options.EnableQueryFeatures().AddRouteComponents(GetEdmModel()));
  1. In WeatherForecastController.cs replace with:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.OData.Query;
using Microsoft.AspNetCore.OData.Routing.Controllers;

namespace ODataActionFailing.Controllers;

[EnableQuery]
public class WeatherForecastController(ILogger<WeatherForecastController> logger) : ODataController
{
    [HttpPost]
    public WeatherForecast Execute(int key)
    {
        logger.LogInformation("We will only reach here with versions <= 8.1.2.\n" +
                               "Any Version >= 8.2.0 will not reach here.");
        return new WeatherForecast() { Id = key };
    }
}
  1. Run the api. Make a POST request to the "Execute" endpoint.
  2. Get an exception

Data Model
Please share your Data model, for example, your C# class.

public class WeatherForecast
{
    public int Id { get; set; }

    public DateOnly Date { get; set; }

    public int TemperatureC { get; set; }

    public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);

    public string? Summary { get; set; }
}

Request/Response
Request: POST https://localhost:7034/WeatherForecast(123)/Execute
Response: 500 - Value cannot be null. (Parameter 'type')

Expected behavior
The POST request should reach to the controller and be handled there further.

Additional context
Exception Stack trace

System.ArgumentNullException: Value cannot be null. (Parameter 'type')
   at Microsoft.OData.Edm.EdmUtil.CheckArgumentNull[T](T value, String parameterName)
   at Microsoft.OData.Edm.EdmTypeSemantics.IsUntyped(IEdmType type)
   at Microsoft.AspNetCore.OData.Query.EnableQueryAttribute.CreateQueryOptionsOnExecuting(ActionExecutingContext actionExecutingContext)
   at Microsoft.AspNetCore.OData.Query.EnableQueryAttribute.OnActionExecuting(ActionExecutingContext actionExecutingContext)
   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.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
   at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext)
   at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)

From the stacktrace, it looks like it's something inside [EnableQuery] attribute that is breaking it. I take it if you remove the attribute, the problem goes away?

Not suggesting that as a solution, just to pin down where the issue could be.

From the stacktrace, it looks like it's something inside [EnableQuery] attribute that is breaking it. I take it if you remove the attribute, the problem goes away?

Not suggesting that as a solution, just to pin down where the issue could be.

Yes indeed. Removing [EnableQuery] "fixes" the issue.
But as you mentioned, that's of course not a suitable solution.

It is caused by this call here:
8.1.2...8.2.0#diff-c2093e957abe0f043940441b15300600c6c65de2216e2e2409d82ee2666a22c2R139

Opend PR #1178 to fix the issue. Thank you @julealgon for pointing in the right direction!