Inconsistent Form model-binding behaviour whether an `IFormFileCollection` property exists or not.
- Dominant language
- C#
- Stars
- 38.4k
- Forks
- 10.9k
- Avg merge
- 2d 6h
- Merged PRs (30d)
- 290
Description
### Is there an existing issue for this?
- [X] I have searched the existing issues
### Describe the bug
Calling an endpoint with a model _without_ an `IFormFileCollection` without providing any valid fields causes the handler to receive a `null` value:
```sh
curl --location --request POST 'https://localhost:7173/withForm' \
--header 'Content-Type: application/x-www-form-urlencoded'
```
Calling an endpoint with a model _with_ an `IFormFileCollection` without providing any valid fields correctly validates the model and promptly throws a `FormDataMappingException` (`Missing required value for property 'Name'`)
```sh
curl --location --request POST 'https://localhost:7173/withoutForm' \
--header 'Content-Type: application/x-www-form-urlencoded'
```
If, however, I pass in a single valid field (even with an empty value), then all works as expected:
```sh
curl --location 'https://localhost:7173/withFileEnumerable' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'name='
```
Note: This is specifically for `IFormFileCollection`; `IEnumerable`, `List` (and presumably other collection interfaces) behaves similarly to not having one (i.e. `null` value being passed). `application/x-www-urlencoded` and `multipart/form-data` behaves identically.
### Expected Behavior
Calling an endpoint - with or without an `IFormFileCollection` - without providing any valid fields correctly validates the model and promptly throws a `FormDataMappingException`.
Alternatively (same meaning, different wording): Calling an endpoint without a single valid field should work (In terms of model binding) identically to calling an endpoint with one or more valid fields
### Steps To Reproduce
```c#
using Microsoft.AspNetCore.Mvc;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapPost("withoutFiles", ( [FromForm] WithoutFiles request) =>
{
return request switch
{
null => Results.BadRequest(), // This can happen for some reason.
_ => Results.Ok()
};
}).DisableAntiforgery();
app.MapPost("withFileCollection", ( [FromForm] WithFileCollection request) =>
{
return request switch
{
null => Results.BadRequest(), // This can never happen (as expected).
_ => Results.Ok()
};
}).DisableAntiforgery();
app.MapPost("withFileList", ( [FromForm] WithFileList request) =>
{
return request switch
{
null => Results.BadRequest(), // This can happen for some reason.
_ => Results.Ok()
};
}).DisableAntiforgery();
app.MapPost("withFileEnumerable", ( [FromForm] WithFileEnumerable request) =>
{
return request switch
{
null => Results.BadRequest(), // This can happen for some reason.
_ => Results.Ok()
};
}).DisableAntiforgery();
app.Run();
public class WithoutFiles
{
public required string Name { get; init; }
public required string Description { get; init; }
}
public class WithFileCollection
{
public required string Name { get; init; }
public required string Description { get; init; }
public IFormFileCollection Attachments { get; init; } = default!;
}
public class WithFileEnumerable
{
public required string Name { get; init; }
public required string Description { get; init; }
public IEnumerable Attachments { get; init; } = default!;
}
public class WithFileList
{
public required string Name { get; init; }
public required string Description { get; init; }
public List Attachments { get; init; } = default!;
}
```
### Exceptions (if any)
This is the exception I get in the `IFormFileCollection` case - the one I'm expecting in all cases.
```
Microsoft.AspNetCore.Http.BadHttpRequestException: Missing required value for property 'Name'.
---> Microsoft.AspNetCore.Components.Endpoints.FormMapping.FormDataMappingException: An error occurred while trying to map a value from form data. For more details, see the 'Error' property and the 'InnerException' property.
at Microsoft.AspNetCore.Components.Endpoints.FormMapping.FormDataReader.AddMappingError(Exception exception, String attemptedValue)
at Microsoft.AspNetCore.Components.Endpoints.FormMapping.CompiledComplexTypeConverter`1.TryRead(FormDataReader& context, Type type, FormDataMapperOptions options, T& result, Boolean& found)
at Microsoft.AspNetCore.Components.Endpoints.FormMapping.FormDataMapper.Map[T](FormDataReader reader, FormDataMapperOptions options)
at lambda_method1(Closure, Object, HttpContext, Object)
--- End of inner exception stack trace ---
at Microsoft.AspNetCore.Http.RequestDelegateFactory.Log.FormDataMappingFailed(HttpContext httpContext, String parameterTypeName, String parameterName, FormDataMappingException exception, Boolean shouldThrow)
at lambda_method1(Closure, Object, HttpContext, Object)
at Microsoft.AspNetCore.Http.RequestDelegateFactory.<>c__DisplayClass104_2.<b__2>d.MoveNext()
--- End of stack trace from previous location ---
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)
```
### .NET Version
`8.0.201` (but was also broke in `8.0.102` and possibly earlier)
### Anything else?
_No response_
Contributor guide
Assessment
This issue has not been assessed yet.