Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Validating non-nullable property in Body as Required - AspNetCore 3.1

I am trying to validate that if a property / field is completely left off on a request that the ModelState is invalid and a BadRequest is sent back to the client, however I am struggling with handling of non-nullable types in request bodies.

Works for nullable types

[Required] public string NullableString { get; set; }

Works for non-nullable and nullable parameters

public IActionResult RequiredNonNullableIntQueryString([Required]int nonNullableInt)

public IActionResult RequiredNullableStringQueryString([Required]string nullableString)

However, it does NOT work for non-nullable types in request bodies

 public IActionResult RequiredNonNullableIntBody([FromBody]NonNullablesRequest request)

 public class NonNullablesRequest
 {
    [Required] // I have also tried [BindRequired] with the same result.
    public int NonNullableInt { get; set; }
 }

I have read:

  • Microsoft documentation - Model Validation # [Required] validation on the server
  • Good article on [Required] and [BindRequired] in ASP.NET Core MVC

The Microsoft documentation states:

The validation system in .NET Core 3.0 and later treats non-nullable parameters or bound properties as if they had a [Required] attribute. Value types such as decimal and int are non-nullable.

That's cool... However later says

On the server, a required value is considered missing if the property is null. A non-nullable field is always valid, and the [Required] attribute's error message is never displayed.

Why? This really doesn't seem to make sense. Why make sure all non-nullables are required but then ignore errors if they were not supplied?

I know many suggestions indicating that one can do the hacky workaround of the following, whereby one sets the required parameter as nullable. To me this does not seem like a reasonable solution.

 public class NonNullablesRequest
 {
    [Required]
    public int? NonNullableInt { get; set; }
 }

This just feels wrong.

  • The datatype is not accurately representing the expectations of the request arriving
  • One has to use .HasValue and .Value every time one is accessing the property to avoid "Possible Null" warnings.
  • Seems like an anti-pattern to C# 8.0's Nullable reference types (C# reference)

Is there a way to configure the ModelBinding to invalidate the ModelState if non-nullable types are not supplied?

Edit 1:

It seems there is quite some debate: ASP.NET Core [Require] non-nullable types I am not sure I agree with Chris Pratt. It is not that we expect the value to not be supplied. In fact the opposite, I want ensure the caller gives me the value. But one has to be defensive against a consumer not supplying the adequate data and therefore the system should reject the requests with a 400 BadRequest.

From this then the expected result is an int not an int?. If no data was supplied the ModelBinder should indicate that the ModelState is invalid.

I can, however, see the challenge where there are two parts 1) Deserialization and then 2) ModelBinding.

like image 607
Oliver Avatar asked Nov 07 '22 07:11

Oliver


1 Answers

You could use two different classes for this.

One that represents what your web-application sends to the backend and another that represents your domain model (e.g. entity model).

Http Transport Object

 public class YourWebAppDto
 {
    [Required]
    public int? NonNullableInt { get; set; }
 }

Domain Model Object

 public class YourDomainModelObject
 {
    public int NonNullableInt { get; set; }
 }

By separating those two concerns, you can keep your domain model clean while addressing the potential invalid input of your web application.

Of course, you need a mapping between the two. We use automapper for this.

I know, this does not directly answer your question but it might help as an other way of looking at things.

Cheers, Mike

like image 117
MikeJ82 Avatar answered Nov 12 '22 20:11

MikeJ82