Skip to content

DefaultODataBatchHandler request body binding failing on .NET 10 #1564

Description

@pcn-michaelulloa

Assemblies affected
Microsoft.AspNetCore.OData 10.0.0-preview.1 on .NET 10

Describe the bug
When using DefaultODataBatchHandler with [ApiController] conventional MVC controllers on .NET 10, any batch sub-request with a POST body returns 400 with "The input does not contain any JSON tokens" and the controller action is never reached.

Reproduce steps

  1. Create a new ASP.NET Core Web API project targeting .NET 10
  2. Install Microsoft.AspNetCore.OData 10.0.0-preview.1
  3. Register DefaultODataBatchHandler in Program.cs:
var builder = WebApplication.CreateBuilder(args);

var modelBuilder = new ODataConventionModelBuilder();

builder.Services.AddControllers().AddOData(options =>
    options.AddRouteComponents("", modelBuilder.GetEdmModel(), new DefaultODataBatchHandler()));

var app = builder.Build();

app.UseODataBatching();

app.UseRouting();

app.MapControllers();

app.Run();
  1. Create a controller with a POST action
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
    [HttpPost]
    public IEnumerable<WeatherForecast> Post(WeatherForecast forecast)
    {
        return [forecast];
    }
}
  1. Send a POST request to /$batch
{
  "requests": [
    {
      "id": "1",
      "method": "POST",
      "url": "WeatherForecast",
      "headers": {
        "Content-Type": "application/json"
      },
      "body": {
        "date": "2026-03-18",
        "temperatureC": 25,
        "summary": "Hot"
      }
    }
  ]
}
  1. $batch returns 400 response
{
  "responses": [
    {
      "id": "1",
      "status": 400,
      "headers": {
        "content-type": "application/problem+json; charset=utf-8"
      },
      "body": {
        "type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
        "title": "One or more validation errors occurred.",
        "status": 400,
        "errors": {
          "$": [
            "The input does not contain any JSON tokens. Expected the input to start with a valid JSON token, when isFinalBlock is true. Path: $ | LineNumber: 0 | BytePositionInLine: 0."
          ],
          "forecast": [
            "The forecast field is required."
          ]
        },
        "traceId": "00-70c875bb5b4167f4786641f8337411bf-153a138a2870aa7b-00"
      }
    }
  ]
}

Data Model

public class WeatherForecast
{
    public DateOnly Date { get; set; }

    public int TemperatureC { get; set; }

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

    public string? Summary { get; set; }
}

Expected behavior
The POST sub-request body should be correctly deserialized by the model binder and the controller action should be reached, returning 200.

Additional context
The following middleware workaround fixes the issue by forcing ASP.NET Core to recreate a fresh PipeReader from HttpRequest.Body:

app.UseODataBatching();

app.Use(async (context, next) =>
{
    context.Features.Set<Microsoft.AspNetCore.Http.Features.IRequestBodyPipeFeature>(null);

    await next();
});

app.UseRouting();

This affects all methods with a body, not just POST. As far as I've researched, this is a regression introduced by the .NET 10 change to use PipeReader over Stream in [FromBody] model binding.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions