In this article, we will see why we created our own validation library and how it was needed.
Everything started in 2018 when we needed a flexible and easy-to-use validation tool for a commercial project. The goal was to create a library that could validate POCO (Plain Old CLR Objects) while following the Single Responsibility Principle (SRP). This is how DynamicVNET was born, a rule-based validation library that has now been downloaded over 13,000 times on NuGet.
The main aim of DynamicVNET was to give developers a solution they could easily add to their projects, so they would not have to repeat the same validation code. With a simple API, developers can create validation rules that work at runtime, making it perfect for projects that need flexible validation options.
Let’s start to demonstrate our library with an ASP.net core example.
ASP.NET Core Environment Preparation
In our example, we have asp.net core web api which has the phone validation controller and validate action that accepts input argument phone number object. So, in the requested action, we need to validate the input argument with rules defined by languages.
To implement this scenario we have to integrate the validation library into the request lifecycle. So, in our case, we will use an action filter to achieve this.
First of all, we need to install the DynamicVNET Nuget package. Also, keep that we are using the stable version of it.
Install-Package DynamicVNET
Let’s start using it.
To bind validation for each input parameter we have to define the BindValidator attribute.
[System.AttributeUsage(AttributeTargets.Parameter, Inherited = false, AllowMultiple = false)]
public class BindValidator : Attribute { }
So simple to understand, the next step is to declare an action filter which provides validation.
public class LangValidatorFilter : IActionFilter
{
private readonly ProfileValidator _validator;
public LangValidatorFilter(ProfileValidator validator)
{
this._validator = validator;
}
public void OnActionExecuted(ActionExecutedContext context) { }
public void OnActionExecuting(ActionExecutingContext context)
{
if (context.ActionArguments != null && context.ActionArguments.Count >= 1)
{
var validatableArguments = context.ActionDescriptor.Parameters
.Where(ar => context.ActionArguments.ContainsKey(ar.Name))
.Select(s => s as ControllerParameterDescriptor)
.Where(a => a != null && a.ParameterInfo.CustomAttributes.Any(at => at.AttributeType == typeof(BindValidator)));
// That part can be replaced by any globalization technique.
string langName = context.HttpContext.Request.Query["culture"].ToString() ?? "en";
foreach (var argument in validatableArguments)
{
try
{
var validationResults = this._validator.Validate(langName, context.ActionArguments[argument.Name]);
if (validationResults != null && validationResults.Any())
{
foreach (var validationResultItem in validationResults)
{
context.ModelState.AddModelError(validationResultItem.MemberName,
validationResultItem.ErrorMessage);
}
}
}
catch (InvalidProfileNameException)
{
context.Result = new StatusCodeResult(404);
break;
}
}
}
}
}
As you can see code above, in the executing step we are getting arguments marked by BindValidator attributes. After that, we got the language key and escaped the globalization stuff. Instead, we used query param for language detection (just for simulation). In the last step, we looped all arguments with ProfileValidator (I should add that ProfileValidator is simple, it provides validation rules by string name) validated them, and got the results. We then filled in the errors using modelState.
After all these steps we should implement a factory for action filter.
[System.AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = false, AllowMultiple = false)]
public class VNETFilterFactory : Attribute, IFilterFactory
{
public bool IsReusable => false;
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
{
DynamicVNET.Lib.ProfileValidator instance = serviceProvider.GetService(typeof(DynamicVNET.Lib.ProfileValidator)) as DynamicVNET.Lib.ProfileValidator;
return new LangValidatorFilter(instance);
}
}


Join the conversation! Your thoughts help the community grow.