9

I'm using ASP.NET Core 2.0, and I have a request object annotated like this:

public class MyRequest
{
    [Required]
    public Guid Id { get; set; }

    [Required]
    public DateTime EndDateTimeUtc { get; set; }

    [Required]
    public DateTime StartDateTimeUtc { get; set; }
}

And in my controller:

public async Task<IActionResult> HandleRequest([FromBody] MyRequest request)
{ /* ... */ }

I noticed an issue with model binding: When I send a request containing the header Content-Type set to application/json and a an empty body, as I expect, the request in my controller is null and ModelState.IsValid is false.

But when I have a body like this:

{
  "hello": "a-string-value!"
}

my request is NOT null, it has default values for everything, and ModelState.IsValid is true

This is happening of course while I'm missing all the Required properties, and the only existing one's name doesn't match a property there (even the type for this single parameter is string, which doesn't match any type on my model).

So in a way, those Required attributes seem to be working if there's nothing in my request, but they don't do anything if my request is not empty!

As I was preparing this question, I noticed that there's also a JsonRequired attribute, and it seems to take care of the properties being present.

So, what's the difference between Required and JsonRequired?

Keyur Ramoliya
  • 1,900
  • 2
  • 16
  • 17
Farzad
  • 1,770
  • 4
  • 26
  • 48

3 Answers3

20

For correct work of Required attribute, you should make the properties nullable:

public class MyRequest
{
    [Required]
    public Guid? Id { get; set; }

    [Required]
    public DateTime? EndDateTimeUtc { get; set; }

    [Required]
    public DateTime? StartDateTimeUtc { get; set; }
}

Now if you send request with missing Id, EndDateTimeUtc or StartDateTimeUtc, corresponding field will be set to null, ModelState.IsValid will be set to false and ModelState will contain error(s) description, e.g. The EndDateTimeUtc field is required.

JsonRequired attribute is specific to JSON.Net. It plays during deserialization, while Required attribute (as other attributes from System.ComponentModel.DataAnnotations namespace) plays after model is deserialized, during model validation. If JsonRequired attribute is violated, the model will not be deserialized at all and corresponding action parameter will be set to null.

The main reason why you should prefer Required attribute over JsonRequired is that JsonRequired will not work for other content types (like XML). Required in its turn is universal since it's applied after the model is deserialized.

CodeFuller
  • 30,317
  • 3
  • 63
  • 79
  • Thank you @CodeFuller. So, if I make everything nullable, then it seems to me that there will be additional things to take care of. For example, now my`EndDateTimeUtc` cannot directly call `AddMinutes` (as an example). Is it the proper way to do it? I mean with all those null checks that will be introduced in my controller code? – Farzad Mar 12 '18 at 16:09
  • 1
    Yep, that complicates things a little. But if those properties aren't nullable, they will be left with their default values (e.g. `DateTime.MinValue` for `DateTime`). I believe calling methods like `AddMinutes()` on such values will not make good. There are different ways to handle this. You could use null propagation, e.g. `EndDateTimeUtc?.AddMinutes()`. Another option, if such requests should be considered as invalid and result to http error, you could use action filter that checks model state (check this [answer](https://stackoverflow.com/a/11724405/5740031) for details). – CodeFuller Mar 12 '18 at 16:19
  • 1
    So, do you think, to avoid the complication, if I decide that my API is only going to support JSON, it's better to use `JsonRequired` to make things a little bit simpler? – Farzad Mar 12 '18 at 16:29
  • No, I don't think it's a good way. You shouldn't have so tight coupling with Json.Net. It will also not save you from checking model parameters against `null` in each action. Consider adding action filter that just checks that model is valid (I've posted the link in above comment). It's really simple and clean solution. – CodeFuller Mar 12 '18 at 16:37
  • Thank you for the reply. Just one thing: the filter example you provided, is it equivalent to doing this in the controller method: `if (!ModelState.IsValid) { return BadRequest(ModelState); }` – Farzad Mar 12 '18 at 17:33
  • Yes, both will result to the same `400 Bad Request` error. – CodeFuller Mar 12 '18 at 17:50
  • Isn't this strange from a semantic point-of-view for the client? – rumblefx0 Aug 07 '22 at 15:16
1

When you use [FromBody] as binding source, the Model properties will get default values and [BindRequired] will be ignored. There is a related issue on "Problems with Parameter Validation".

In this case is better to use [JsonRequired] instead of [BindRequired] to enforce binding properties.

Note that [JsonRequired] affects serialization and deserialization both.

Mohammad Barbast
  • 1,753
  • 3
  • 17
  • 28
1

Yes, the difficulty is that if you choose Required, the semantics for the client of the web request change incorrectly - i.e. you are saying you can pass in a null, when you really need a proper value.

Using JsonRequired sorts this, but this is provided by NewtonSoft, so stops working when you upgrade to .Net Core 3. This is because .Net Core 3 uses its own Json parser instead of NewtonSoft.

Peter Owen
  • 19
  • 1