The Error
Your controller action receives null:
[HttpPost]
public IActionResult Create(UserDto dto) // dto is null
{
return Ok(dto.Name); // NullReferenceException
}
Quick Fix - 1 Minute
Add [FromBody] and check JSON content-type.
[HttpPost]
public IActionResult Create([FromBody] UserDto dto) // Works
{
return Ok(dto.Name);
}
And send request with header: Content-Type: application/json
Why This Happens in.NET 8
1. Missing [FromBody]: Complex types need explicit attribute in Minimal APIs
2. Wrong Content-Type: Sending text/plain or form-data when API expects JSON
3. Case mismatch: JSON { "name": "John" } won't bind to public string Name unless case-insensitive is on
Step-by-Step Debug
- Check request: Use Postman. Body = raw JSON. Header =
Content-Type: application/json - Add
[FromBody]: Required for complex types in Controllers - Check property setters:
public string Name { get; }won't work. Need{ get; set; } - Enable logging:
builder.Logging.AddConsole();shows model binding errors
Real-World Scenario: Minimal API vs Controller Binding
.NET 8 Minimal APIs broke millions of apps. Binding rules changed:
// Controllers: Needs [FromBody] unless [ApiController]
// Controllers\UsersController.cs
[HttpPost]
public IActionResult Create([FromBody] UserDto dto) // Required without [ApiController]
{
return Ok(dto.Name);
}
// Minimal API: [FromBody] is inferred, but only for complex types
// Program.cs
app.MapPost("/users", (UserDto dto) => // [FromBody] inferred automatically
{
return Results.Ok(dto.Name);
});
// BREAKS: Primitives are [FromRoute] by default in Minimal API
app.MapPost("/users/{id}", (int id, UserDto dto) => // id from route, dto from body
{
return Results.Ok();
});
Gotcha: In Minimal APIs, string parameters come from route/query. If you send { "name": "John" } to (string name), name is null. Use (UserDto dto) or ([FromBody] string name).
Related Fixes You Should Know
Model binding null usually means these issues too:
- Minimal API 400 Bad Request - Your DTO has
requiredproperties but JSON is missing them..NET 8 returns 400 with ProblemDetails instead of null. - JSON Deserialization Error - Case sensitivity or property name mismatch.
{ "name": "John" }won't bind topublic string NamewithoutPropertyNameCaseInsensitive = true. - System.Text.Json Required Property -.NET 8 added
requiredkeyword support. Missing required field = model is null or 400 error. - Minimal API JSON Options Not Applied - You set
JsonSerializerOptionsinAddControllers()but Minimal API ignores it. UseConfigureHttpJsonOptions.
FAQ
Q: Do I need [FromBody] in.NET 8 Minimal APIs?
No for complex types. Yes for string, int, etc. Minimal APIs infer [FromBody] for classes/records, but [FromRoute] or [FromQuery] for primitives.
Q: Why is my DTO null but ModelState.IsValid is true?
You're missing [ApiController]. Without it, validation doesn't run and binding failures silently give you null. Add [ApiController] to get automatic 400 errors.
Best Practice for.NET 8
Use [ApiController] on controller. It auto-adds [FromBody] inference:
[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
[HttpPost]
public IActionResult Create(UserDto dto) // [FromBody] inferred
{
if (!ModelState.IsValid) return BadRequest(ModelState);
return Ok();
}
}
No comments yet. Be the first to share your thoughts!