Required and BindRequired Data Annotations

In ASP.NET, we have Data Annotations, which are attributes that can be placed above properties or classes to validate them.

The default annotations are simple because ASP.NET will create the validation needed for the server and client side.

The most used data annotation is [Required]. This validation has a downside, and I have seen it many times from junior programmers. Using the validation Required for value types doesn’t work as expected. An attacker can easily send a model without that value.

public class Employee
{
    [Required]
    public int Id { get; set; }
    [Required]
    public string Name { get; set; }
}

public class EmployeeController : Controller
{
    public IActionResult Index()
    {
        return View();
    }

    [HttpPost]
    public IActionResult Index(Employee e)
    {
        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }
        return View();
    }
}

The above validation can easily be tricked. Look how I did with Postman.ASP.NET Core Required Attribute

You can see that I didn’t send the Id as a parameter, and the Model State is still valid. That happens because the property’s type is Integer, a value type variable.

A value-type variable cannot be null. It always has a default value. In our case, for integer type is 0. So, the required attribute will look at the value which is not null and will validate the property successfully.

If you want to require the user to send a value for the Id property, then you should use [BindRequired]. This data annotation will ensure that the user has sent an actual value.

Using nullable types for parameters

An alternative to this approach is to use nullable types. For example, the above class will be changed like this:

public class Employee
{
    [Required]
    public int? Id { get; set; }
    [Required]
    public string Name { get; set; }
}

IBindingMetadataProvider implementation

Also, you can change how the Required attribute works:

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

Then in ConfigureServices:

services.AddControllersWithViews(m =>
{
    m.ModelMetadataDetailsProviders.Add(new RequiredBindingProvider());
});

Using this approach, you will verify if the value was bound for the Required attribute.

In conclusion, recheck your application to see if you use the required attribute on value types and change it in BindRequired.

1 thought on “Required and BindRequired Data Annotations”

Leave a Comment