Whether seasoned or just getting started in the .NET world, we’ve all encountered situations where we need to conditionally validate some attributes of data based on specific conditions. In this article, we’re going to learn about creating conditional required attributes for validation, and why we need them. We’ll also cover approaches we can use to do conditional validation.

To download the source code for this article, you can visit our GitHub repository.

Let’s dive in.

Why Create Conditional Required Attributes

Conditionally required attributes help us add a layer of flexibility to the validation process in our applications. In some cases, the application input varies from one user to another, in that, the data required from one user may not be the same data required for the next user. In such a case, we can dynamically validate the input based on the values of the other inputs. This way, we create more adaptive applications.

Support Code Maze on Patreon to get rid of ads and get the best discounts on our products!
Become a patron at Patreon!

Also, through conditional validation, we don’t impose unnecessary constraints on the users interacting with our applications. We get to tailor our application validation rules to the users’ specific choices, ensuring a streamlined user experience.

From time to time, application requirements change, sometimes introducing a new set of data to work with. Conditionally required attributes empower us to easily adapt our validation logic to changing requirements without compromising the integrity of our data validation.

Conditionally required attributes allow us to provide more context-specific error messages. Instead of generic “field is required” messages, we can offer more informative guidance to users, helping them understand why certain fields become required under specific conditions.

Having looked at the reasons why we need conditional required attributes in our applications, let’s look at some of the approaches we can use to achieve this.

Creating Custom ValidationAttribute

ValidationAttribute is a part of the System.ComponentModel.DataAnnotations namespace. We use it to define custom validation rules for our data models.

Let’s create a .NET Web API and add the WorkItem class:

public class WorkItem
{
    public long Id { get; set; }
    public string Title { get; set; }
    public bool IsAssigned { get; set; }
    public string Assignee { get; set; }
}

As it is right now, we don’t have validation on any of the fields. If we were to validate the incoming data, we’d have to do it manually. 

Let’s add an endpoint to create work items:

[HttpPost("create")]
public IActionResult CreateWorkItem([FromBody] WorkItem workItem)
{
    if (!ModelState.IsValid)
    {
        return BadRequest();
    }

    return Ok(workItem);
}

Here, we define the CreateWorkItem() endpoint, using model built-in validation to validate the request body. At this point, we’re only validating the data types for each of the fields. If any of them is of incorrect type, then the API throws a BadRequest exception. Microsoft has an excellent guide on Model Validation if we feel the need to brush up.

While the inbuilt model validation is good, it doesn’t cater to all our needs. For instance, when we create a work item, we have the option of assigning it to someone during creation or assigning it later. With this in mind, let’s define our own validation rules.

Using Custom ValidationAttribute in .NET

Let’s add a new RequiredIfCustomAttribute attribute:

public class RequiredIfCustomAttribute(string otherProperty, object targetValue) : ValidationAttribute
{
    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        var otherPropertyValue = validationContext.ObjectType
                                                  .GetProperty(otherProperty)?
                                                  .GetValue(validationContext.ObjectInstance);

        if (otherPropertyValue is null || !otherPropertyValue.Equals(targetValue)) return ValidationResult.Success;

        if (value is null || string.IsNullOrWhiteSpace(value.ToString()))
        {
            return new ValidationResult(ErrorMessage ?? "This field is required.");
        }
            
        return ValidationResult.Success;
    }
}

Here, we define our custom attribute, RequiredIfCustomAttribute. In our definition, we inherit the ValidationAttribute class, an inbuilt class in .NET that enforces model validation.

Our class takes two parameters in its constructor, otherProperty and targetValue. These parameters imply that the validation logic depends on the value of another property, in our case, otherProperty and a specific target value, in this case, targetValue. Inside this class, we have IsValid() method that performs the actual validation. Here, we compare the value of the otherProperty with the value of targetValue. If the values match, then the property in which we used the custom validation attribute is considered to be required. Our model validation passes if the target property is valued, otherwise we throw an exception.

Let’s modify the WorkItem class further to make use of the custom validation RequiredIfCustom attribute:

public class WorkItem
{
    public long Id { get; set; }

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

    public bool IsAssigned { get; set; }

    [RequiredIfCustom(nameof(IsAssigned), true,
        ErrorMessage = "Assignee is required if a work item is assigned")]
    public string Assignee { get; set; }
}

First, we’ve made the Title property mandatory. Since this is an independent field, a work item must always have a title. Then, we’ve used our custom validation attribute on the Assignee property, such that whenever the IsAssigned property is true, then Assignee must have a value.

Let’s test our custom validation attribute with invalid model data:

{
  "id": 1,
  "title": "Item 1",
  "isAssigned": true,
  "assignee": ""
}

Here, we are sending the isAssigned value as true and the assignee value as empty in the request.

In turn, our API will return with an error:

{
  "type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
  "title": "One or more validation errors occurred.",
  "status": 400,
  "errors": {
    "Assignee": [
      "Assignee is required if a work item is assigned"
    ]
  },
  "traceId": "00-0d3fe11fde49a7f5a6dba335b2aad6af-7609eef8fd69b8d2-00"
}

We get this response because the data in our request doesn’t match what our API expects. Our custom validator validates the incoming data and throws the validation error. Using this approach, all we have to do is add attributes to the class properties and test. We don’t have to worry about the respective field validations.

Having looked at using a custom validation attribute, let’s look at an alternative, the ExpressiveAnnotations library.

Using the ExpressiveAnnotations Library

The ExpressiveAnnotations library is .NET and JavaScript conditional validation library. It’s an alternative to using custom validation attributes.

Let’s look at some of the features of the library.

Features of the ExpressiveAnnotations Library

It is annotations-based, in that it uses attributes to declaratively express validation rules directly on model properties, reducing code clutter and enhancing readability.

It is a full-stack library. As such it works seamlessly on both server-side (.NET) and client-side (JavaScript), ensuring consistent validation across layers.

Having looked at the main features of the library, let’s see how we can incorporate the library into our project to streamline validation.

Install the ExpressiveAnnotations Library

The ExpressiveAnnotations library is available as a NuGet package which we can install by running the dotnet CLI command:

dotnet add package ExpressiveAnnotations

After installing the library, let’s see how can we use it.

Using ExpressiveAnnotations in .NET

Let’s modify our WorkItem model to use the ExpressiveAnnotations:

public class WorkItem
{
    public long Id { get; set; }

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

    public bool IsAssigned { get; set; }

    [RequiredIf(nameof(IsAssigned),
        ErrorMessage = "Assignee is required if a work item is assigned")]
    public string Assignee { get; set; }
}

Here, we’re using the RequiredIf attribute from the ExpressiveAnnotations library to validate our model. 

Let’s test our implementation using incomplete data:

{
  "id": 2,
  "title": "Work Item 2",
  "isAssigned": true,
  "assignee": ""
}

Again, we get an error response:

{
  "type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
  "title": "One or more validation errors occurred.",
  "status": 400,
  "errors": {
    "Assignee": [
      "Assignee is required if a work item is assigned"
    ]
  },
  "traceId": "00-709c3eefa07382cf8fbde97464c44deb-7f765b6ed231a6c1-00"
}

Here, once again we see that the model validation fails because the library checks our incoming data against the validation rules. We could also refine our error handling to handle errors better, but we won’t go into much detail in this article.

To learn more about error handling in .NET, be sure to check out our article How to Use IExceptionHandler to Handle Exceptions in .NET.

Having seen how we can use the ExpressiveAnnotations library in our projects, let’s look at some of the advantages of the library.

Advantages of the ExpressiveAnnotations Library

Without writing any custom logic, we get out-of-the-box validation rules that work across different data types.

Additionally, the ExpressiveAnnotations library has an AssertThat attribute that we can also leverage in our applications for validating our data models.

The library also doesn’t limit us to only using one validation attribute per field. We can combine multiple attributes to suit our specific needs.

Another advantage is that we get concise and expressive validation rules across our code base. Not only is this easily readable but it’s also easy to maintain.

Also, the library has an active community maintaining it. This implies that it has continuous development and support.

Conclusion

In this article, we’ve learned about creating conditional required attributes for validation in ASP.NET Core. We’ve learned its importance in ensuring data accuracy in our applications and the role the attributes play in streamlining data validation. We’ve also learned how to create and use custom conditional validation attributes in our applications. Finally, we’ve looked at the ExpressiveAnnotations library, how we can use it to conditionally validate data, and its advantages. 

Liked it? Take a second to support Code Maze on Patreon and get the ad free reading experience!
Become a patron at Patreon!