[Required] and [BindRequired] in ASP.NET Core MVC

Β· 1171 words Β· 6 minutes to read

When controlling the binding behavior of models in ASP.NET Core MVC applications, it is very common to perform some validation on them. For that, data annotations are a perfect tool.

One of the most typical use cases of data annotations is to ensure that a value of a certain property has been provided by the caller of the API - and this, historically (in “classic” ASP.NET MVC), has been controlled by RequiredAttribute. The attribute can still be used in ASP.NET Core MVC, but there is also a new one - BindRequiredAttribute.

Let’s have a look at the subtle differences between them.

The typical usage of RequiredAttribute πŸ”—

Imagine that your model is the following BookOrder class. In order to force the Author and Title properties to be always present on the incoming requests, we’d decorate them with the RequiredAttribute.

public class BookOrder
{
    [Required]
    public string Author { get; set; }

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

This is the normal approach that’s been in place since the early versions of classic ASP.NET MVC (so long before .NET Core). With that in place, you can now validate the ModelState for errors, as failed validation against the attributes would be reflected there.

For example:

[Route("bookorder")]
public IActionResult PostBook(BookOrder bookOrder)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }

    // continue with the happy code path
}

In case of an incoming request with either of the two required properties missing (so null), the response will be a 400 status with the model state errors serialized into the response body.

But this is all well known, and it behaves the same way in ASP.NET Core MVC as it did in classic MVC.

The usage of BindRequiredAttribute πŸ”—

It gets more interesting when you start thinking about non-nullable properties. Let’s add something like Quantity, of type integer to our model.
Now, RequiredAttribute would never work correctly in such case, because the default value is __ and the property can never be null. Even if the client submits a request where the Quantity is completely missing, the model instance would have a value __. In other words, the RequiredAttribute would have no effect. In our case we have an int, but the same applies to all non-nullable types such as for example DateTime or Guid.

Historically, in classic ASP.NET MVC the way to solve this was to use a nullable version of the type, as its shown below.

public class BookOrder
{
    [Required]
    public string Author { get; set; }

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

    [Required]
    public int? Quantity { get; set; }
}

This is not the most elegant way, but it solves the problem. Quantity would be mandatory, and if it doesn’t get provided by the caller of the API, the value remains null. This, in turn, plays nicely together with our RequiredAttribute as the ModelState would be invalid.

The downside of such approach is that on the “happy path” you are forced to access the Value of the nullable property.

[Route("bookorder")]
public IActionResult PostBook(BookOrder bookOrder)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }

    // continue with the happy code path
    var requestedQuantity = bookOrder.Quantity.Value;
}

This could - for example - get flagged by static analysis tools, as they’d not see a null-check and warn you against a possible null reference exception (even though, technically, the ModelState check was enough). It wasn’t also the most pleasing on the eyes.

This is where BindRequiredAttribute comes in. It works the same way as RequiredAttribute, except it mandates that the value comes from the request - so it not only rejects null values, but also “unbound” values.

As a result, the following code is perfectly valid in ASP.NET Core.

public class BookOrder
{
    [Required]
    public string Author { get; set; }

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

    [BindRequired]
    public int Quantity { get; set; }
}

But I don’t want to mix BindRequiredAttribute and RequiredAttribute πŸ”—

Of course having [Required] and [BindRequired] mixed up in your code might not be the most pleasing result too. Thankfully, ASP.NET Core MVC gives you enough flexibility to change the behaviour of RequiredAttribute - and force it to behave as if it was BindRequiredAttribute.

You can achieve that by making your own IBindingMetadataProvider and registering in your app globally.

public class RequiredBindingMetadataProvider : IBindingMetadataProvider
{
    public void GetBindingMetadata(BindingMetadataProviderContext context)
    {
        if (context.PropertyAttributes.OfType<RequiredAttribute>().Any())
        {
            context.BindingMetadata.IsBindingRequired = true;
        }
    }
}

You just need to make sure to register it:

services.AddMvc(o =>
{
    o.ModelMetadataDetailsProviders.Add(new RequiredBindingMetadataProvider());
});

Our RequiredBindingMetadataProvider will detect every usage of RequiredAttribute and apply the BindRequiredAttribute semantics to a given property. In other words, using [Required] will be like using [BindRequired].

So now your model can go back to the most elegant form:

public class BookOrder
{
    [Required]
    public string Author { get; set; }

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

    [Required]
    public int Quantity { get; set; }
}

There is an extra hidden benefit in such approach. I experienced - when working with different clients - that some people like to build “.NET SDK clients” for their APIs. To do that, they end up sharing the DTOs assembly between the client (perhaps consumed from Xamarin or WPF app) with the Web app project. By skipping BindRequiredAttribute (in favor of RequiredAttribute), you’d free such DTO (and indirectly Client SDK) of a dependency on any MVC Nuget packages, as RequiredAttribute is obviously defined in System.ComponentModel.Annotations Nuget package.

One thing to watch out for is that this behavior (BindRequiredAttribute) only applies to situations when MVC performs model binding - form requests, querystring binding etc. So it would not work (would have no effect) for requests that go through input formatters, such as, primarily, JSON requests. In those cases the workaround is to use attribute that is specific to a given formatter. For example, for JSON.NET, you can use JsonRequiredAttribute, which will give you the same semantics as BindRequiredAttribute.

RequiredAttribute vs entire request model πŸ”—

So we have already managed to get RequiredAttribute to work as we need it to, but the one open question that remains, is how do we apply it to the entire request model?

I mean, it works fine on the properties, but what if the whole model is null? Unfortunately, in “classic” ASP.NET MVC, as well as in ASP.NET Core 1.0, ModelState validation passes happily. The solution there was to manually validate the parameter that was read from the body - perhaps using a filter - and reject the request that way.

However, this has finally been fixed in ASP.NET Core MVC 2.0 (which, by the way, is a breaking change). Using the latest framework, finally, marking a model with FromBodyAttribute will guarantee tha a null input will be treated as invalid ModelState.

If this behavior for some reason doesn’t make sense to you (or you are upgrading an older project and want to retain the old behavior), you can revert back to the old approach using MvcOptions:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc(o =>
    {
        o.AllowEmptyInputInBodyModelBinding = true;
    });
}

Happy coding!

About


Hi! I'm Filip W., a software architect from ZΓΌrich πŸ‡¨πŸ‡­. I like Toronto Maple Leafs πŸ‡¨πŸ‡¦, Rancid and quantum computing. Oh, and I love the Lowlands 🏴󠁧󠁒󠁳󠁣󠁴󠁿.

You can find me on Github, on Mastodon and on Bluesky.

My Introduction to Quantum Computing with Q# and QDK book
Microsoft MVP