dotnet / aspnetcore

ASP.NET Core is a cross-platform .NET framework for building modern cloud-based web applications on Windows, Mac, or Linux.
https://asp.net
MIT License
35.19k stars 9.93k forks source link

Minimal API : Converting empty string to Nullable (ex: int?) with [FromForm] binding #55202

Open JimmyLahaie opened 4 months ago

JimmyLahaie commented 4 months ago

Is there an existing issue for this?

Is your feature request related to a problem? Please describe the problem.

From a Minimal API project

With the HTML form

<form method="post" action="/SomeUrl">
<input type="text" name="someProp" />
<button type="submit">OK</button>
</form>

And Mapping app.MapPost("/SomeUrl", ([FromForm] SomeModel theInputModel) => Results.Ok());

And Model

public class SomeModel
{
    public int? SomeProp { get; set; }
}

If I enter a number in "SomeProp" input field, it works ok. But if I leave "SomeProp" input field empty and the submit the form, I get the exception:

 Microsoft.AspNetCore.Http.BadHttpRequestException: The value '' is not valid for 'SomeProp'.

  ---> 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_method280(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_method280(Closure, Object, HttpContext, Object)

         at Microsoft.AspNetCore.Http.RequestDelegateFactory.<>c__DisplayClass104_2.<<HandleRequestBodyAndCompileRequestDelegateForForm>b__2>d.MoveNext()

      --- End of stack trace from previous location ---

That is because the request sent to the server when posting the form is a POST with content : someProp=

The only workaroung I found is using a string instead of int? and then convert string to int? by myself which is not the best solution.

Describe the solution you'd like

When binding [FromForm], I beleive that Minimal API should convert empty string to null when converting to a Nullable type (ex int?). Or at least give any option to do so.

Additional context

No response

KennethHoff commented 4 months ago

I've also experienced this issue. I "fixed" it by updating my frontend to delete all empty fields before sending it to the backend.

captainsafia commented 4 months ago

The form binding behavior that minimal APIs uses is shared with Blazor. I'm open to changing this behavior to be more inline with how empty strings are handled elsewhere in minimal APIs (and in forms in MVC).

@javiercn Any objections to modifying the form binding behavior for empty strings here?

dimenus commented 1 month ago

@captainsafia I have this same issue with [FromQuery] as well with e.g. Nullable<long>. It works just fine when the parameter is omitted, but if the clients sends the query parameter with an empty value, the validation fails.

curl 'http://localhost:5209/proui/field?uiCodeTypeId='

Microsoft.AspNetCore.Http.BadHttpRequestException: Failed to bind parameter "Nullable<long> uiCodeTypeId" from "".
   at lambda_method34(Closure, Object, HttpContext)
   at Microsoft.AspNetCore.HttpsPolicy.HttpsRedirectionMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware.Invoke(HttpContext context)
   at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext)
   at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
   at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)
dcernach commented 1 month ago

@captainsafia I'm also experiencing issues with [AsParameters] and all Nullable primitive types. This is causing significant problems. As a workaround, I've implemented an Axios interceptor to remove empty, null, and undefined values, but some of our customers are unhappy with this solution. I've tried other approaches, including custom JSON converters, without success. Is there a plan to improve Minimal API model binding to match the behavior of MVC Model Binder?

If anyone's interested, here's my Axios interceptor:

const $http = axios.create({
    baseURL: '/your-endpoint'
}); 

function cleanObject(obj) {
    for (let propName in obj) {
        if (Object.prototype.hasOwnProperty.call(obj, propName)) {
            if (obj[propName] === null || obj[propName] === undefined || obj[propName] === '') {
                delete obj[propName];
            }
        }
    }
    return obj;
}

$http.interceptors.request.use((req) => {
    if (req.data) req.data = cleanObject(req.data);
    if (req.params) req.params = cleanObject(req.params);
    return req;
});
dcernach commented 1 month ago

The form binding behavior that minimal APIs uses is shared with Blazor. I'm open to changing this behavior to be more inline with how empty strings are handled elsewhere in minimal APIs (and in forms in MVC).

@javiercn Any objections to modifying the form binding behavior for empty strings here?

Please do it :)