Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

How to Create Custom Validation Attribute in C#

0.00/5 (No votes)
21 Jan 2020 2  
How to create a simple validation attribute using C#

Introduction

When creating web applications such as REST, we must handle the situation in which we receive incorrect data. Often, ready-made attributes are not enough for us to check the data correctly, so in this tip, I will try to explain how we can write our own validator.

Using the Code

The first step will be to create a new application.

Image 1

The next step will be installing the required nuget packs. You can do it directly from the console or using the menu manager.

Install-Package System.Linq.Dynamic -Version 1.0.7

This post will create a custom validator using the conditional required example.

Now let's create a class that inherits from "ValidationAttribute". Something like that:

using System;
using System.ComponentModel.DataAnnotations;

namespace HRP.Services.Attributes
{
    public class RequiredIfAttribute : ValidationAttribute
    {
        protected override ValidationResult IsValid
                  (object value, ValidationContext validationContext)
        {

        }
    }
}

This class should override the inherited IsValid method. This will allow us to connect to the place where the data is validated.

The whole will be based on passing to the attribute an expression in the form of a string which will then be reparsed and applied to the data. To do this, we need to add a constructor that takes a string value and then assigns it to a variable.

private readonly string _condition;

public RequiredIfAttribute(string condition)
{
    _condition = condition;
}

The next step will be to create a method that accepts this expression in the form of text and parses it to a specific value.

private static Delegate CreateExpression(Type objectType, string expression)
        {
            var lambdaExpression =
                DynamicExpression.ParseLambda(
                    objectType, typeof(bool), expression);
            var func = lambdaExpression.Compile();
            return func;
        }

Now that we have such a method prepared, we should use it in the method for checking data correctness.

protected override ValidationResult IsValid(object value, ValidationContext validationContext)
        {
            var conditionFunction = CreateExpression(
                validationContext.ObjectType, _condition);
            var conditionMet = (bool) conditionFunction.DynamicInvoke(
                validationContext.ObjectInstance);
            if (!conditionMet) return null;

            if (value != null && int.TryParse(value.ToString(), out var parsedValue))
            {
                return parsedValue == 0
                    ? new ValidationResult($"Field {validationContext.MemberName} is required")
                    : null;
            }

            return new ValidationResult($"Field {validationContext.MemberName} is required");
        }

We already have everything ready, now it is enough to apply this validator in the model, e.g., in such a way.

public class GetHotelReviewRequest
   {
       [RequiredIf("AllRecords==false")]
       [Range(0, 10, ErrorMessage = "Value for {0} must be between {1} and {2}.")]
       public int ItemsPerPage { get; set; }

       [RequiredIf("AllRecords==false")]
       [Range(0, int.MaxValue, ErrorMessage = "Value for {0} must be between {1} and {2}.")]
       public int Page { get; set; }

       [Required] public bool AllRecords { get; set; }
   }

I personally used this attribute for paging. I have one "AllRecords" value that is required. And when the application receives false value, all fields in the model that have the attribute [RequiredIf ("AllRecords == false")] will also have to be filled.

Summary

To sum up, the attribute created by us only checks if a given field has any value and in the case of "int", it is different from zero or default values. The attribute can, of course, be expanded to check different types of values, however in this tip, the main goal was to show the use of DynamicExpression to create your own attribute.

History

  • 21st January, 2020: Initial version

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here