I don't think it would really make sense to make the validation generic as all of your models are likely to have different properties and property types that need to be validated differently. However, you could make what you have here more generic by adding a base validation class such as:
public abstract class BaseValidator<T> : AbstractValidator<T>
{
private readonly ValidationEntitySettingServices _validationEntitySettingService;
public BaseValidator(ValidationEntitySettingServices validationEntitySettingService)
{
_validationEntitySettingService = validationEntitySettingService;
AutoApplyEmptyRules();
}
private string ViewModelName
{
get { return GetType().Name.Replace("Validator", string.Empty); }
}
// no longer needed
//public bool IsPropertyRequired(string propertyName)
//{
// var validationSetting = _validationEntitySettingService.GetIncluding(ViewModelName);
// if (validationSetting != null)
// {
// return validationSetting.ValidationEntityProperties.Any(p => p.PropertyName.Equals(propertyName) && p.IsRequired);
// }
// else
// return false;
//}
protected void AddEmptyRuleFor<TProperty>(Expression<Func<T, TProperty>> expression, string message)
{
//RuleFor(expression).NotEmpty().When(x => IsPropertyRequired(((MemberExpression)expression.Body).Name)).WithMessage(message);
RuleFor(expression).NotEmpty().WithMessage(message);
}
private void AddEmptyRuleForProperty(PropertyInfo property)
{
MethodInfo methodInfo = GetType().GetMethod("AddEmptyRuleFor");
Type[] argumentTypes = new Type[] { typeof(T), property.PropertyType };
MethodInfo genericMethod = methodInfo.MakeGenericMethod(argumentTypes);
object propertyExpression = ExpressionHelper.CreateMemberExpressionForProperty<T>(property);
genericMethod.Invoke(this, new object[] { propertyExpression, $"{propertyInfo.Name} is a required field!" });
}
private PropertyInfo[] GetRequiredProperties()
{
var validationSetting = _validationEntitySettingService.GetIncluding(ViewModelName);
if (validationSetting != null)
{
return validationSetting.ValidationEntityProperties.Where(p => p.IsRequired);
}
else
return null;
}
private void AutoApplyEmptyRules()
{
PropertyInfo[] properties = GetRequiredProperties();
if (properties == null)
return;
foreach (PropertyInfo propertyInfo in properties)
{
AddEmptyRuleForProperty(property);
}
}
}
The main requirement here is the AddEmptyRuleForProperty method which will call the generic AddEmptyRuleFor method by constructing the method based on the PropertyType.
Then you can inherit this class instead and apply rules using the generic method:
public class EmployeeValidator : BaseValidator<EmployeeViewModel>
{
public EmployeeValidator(ValidationEntitySettingServices validationEntitySettingService) : base(validationEntitySettingService)
{
// no longer needed
//AddEmptyRuleFor(x => x.LastName, "Last Name is a required field!");
//AddEmptyRuleFor(x => x.FirstName, "First Name is a required field!");
//AddEmptyRuleFor(x => x.MiddleName, "Middle Name is a required field!");
}
}
This is the ExpressionHelper class which provides a method to create a generic member expression as an object that can be passed in when invoking the AddEmptyRuleFor method above:
public static class ExpressionHelper
{
public static Expression<Func<TModel, TProperty>> CreateMemberExpression<TModel, TProperty>(PropertyInfo propertyInfo)
{
if (propertyInfo == null)
throw new ArgumentException("Argument cannot be null", "propertyInfo");
ParameterExpression entityParam = Expression.Parameter(typeof(TModel), "x");
Expression columnExpr = Expression.Property(entityParam, propertyInfo);
if (propertyInfo.PropertyType != typeof(T))
columnExpr = Expression.Convert(columnExpr, typeof(T));
return Expression.Lambda<Func<TModel, TProperty>>(columnExpr, entityParam);
}
public static object CreateMemberExpressionForProperty<TModel>(PropertyInfo property)
{
MethodInfo methodInfo = typeof(ExpressionHelper).GetMethod("CreateMemberExpression", BindingFlags.Static | BindingFlags.Public);
Type[] argumentTypes = new Type[] { typeof(TModel), property.PropertyType };
MethodInfo genericMethod = methodInfo.MakeGenericMethod(argumentTypes);
return genericMethod.Invoke(null, new object[] { property });
}
}