If you're open to using
Fluent Validation you can do something like this:
This validator can validate any dto using reflection
Validator:
using FluentValidation;
using System;
using System.Collections.Generic;
using System.Text;
namespace ConsoleTests
{
public class MyValidator : AbstractValidator<ModelDTO>
{
public MyValidator()
{
foreach (var item in typeof(ModelDTO).GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance))
{
Console.WriteLine($"Name: {item.Name}, Type: {item.PropertyType}");
if (item.PropertyType == typeof(int))
{
RuleFor(x => (int)item.GetValue(x, null)).NotEmpty<ModelDTO,int>();
}
else
{
RuleFor(x => (string)item.GetValue(x, null)).NotEmpty<ModelDTO, string>();
}
//Other stuff...
}
}
}
}
The NotEmpty
refuses null and default values.
Program.cs
using System;
using System.ComponentModel.DataAnnotations;
using System.IO;
namespace ConsoleTests
{
class Program
{
static void Main(string[] args)
{
try
{
ModelDTO modelDTO = new ModelDTO
{
MyProperty1 = null, //string
MyProperty2 = 0, //int
MyProperty3 = null, //string
MyProperty4 = 0 //int
};
MyValidator validationRules = new MyValidator();
FluentValidation.Results.ValidationResult result = validationRules.Validate(modelDTO);
foreach (var error in result.Errors)
{
Console.WriteLine(error);
}
Console.WriteLine("Done!");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
}
Output:
Name: MyProperty1, Type: System.String
Name: MyProperty2, Type: System.Int32
Name: MyProperty3, Type: System.String
Name: MyProperty4, Type: System.Int32
must not be empty.
must not be empty.
must not be empty.
must not be empty.
Done!
And for a more complete solution:
See this SO question how-to-use-reflection-in-fluentvalidation.
It uses the same concept, the validator takes a PropertyInfo
and does its validation by getting the type and value.
The below code is from the mentioned question.
Property Validator:
public class CustomNotEmpty<T> : PropertyValidator
{
private PropertyInfo _propertyInfo;
public CustomNotEmpty(PropertyInfo propertyInfo)
: base(string.Format("{0} is required", propertyInfo.Name))
{
_propertyInfo = propertyInfo;
}
protected override bool IsValid(PropertyValidatorContext context)
{
return !IsNullOrEmpty(_propertyInfo, (T)context.Instance);
}
private bool IsNullOrEmpty(PropertyInfo property, T obj)
{
var t = property.PropertyType;
var v = property.GetValue(obj);
// Omitted for clarity...
}
}
Rule Builder:
public static class ValidatorExtensions
{
public static IRuleBuilderOptions<T, T> CustomNotEmpty<T>(
this IRuleBuilder<T, T> ruleBuilder, PropertyInfo propertyInfo)
{
return ruleBuilder.SetValidator(new CustomNotEmpty<T>(propertyInfo));
}
}
And finally the validator:
public class FooValidator : AbstractValidator<Foo>
{
public FooValidator(Foo obj)
{
// Iterate properties using reflection
var properties = ReflectionHelper.GetShallowPropertiesInfo(obj);//This is a custom helper that retrieves the type properties.
foreach (var prop in properties)
{
// Create rule for each property, based on some data coming from other service...
RuleFor(o => o)
.CustomNotEmpty(obj.GetType().GetProperty(prop.Name))
.When(o =>
{
return true; // do other stuff...
});
}
}
}