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.
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