Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save Kaffeegangster/fe3be747e2479698c670e5cb67851d10 to your computer and use it in GitHub Desktop.
Save Kaffeegangster/fe3be747e2479698c670e5cb67851d10 to your computer and use it in GitHub Desktop.
namespace Demo
{
using FluentValidation;
using FluentValidation.Internal;
using FluentValidation.Validators;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using StructureMap;
using StructureMap.Graph;
using StructureMap.Graph.Scanning;
using System.Reflection;
using Expression = System.Linq.Expressions.Expression;
public class AttributeValidationConvention : IRegistrationConvention
{
public void ScanTypes(TypeSet types, Registry registry)
{
foreach (var type in types.AllTypes())
{
var validatedProperties = type.GetProperties()
.Where(x => Attribute.IsDefined(x, typeof(ValidationAttribute)))
.ToList();
if (validatedProperties.Any())
{
var validator = CreateValidator(type, validatedProperties);
registry.For(typeof(IValidator<>).MakeGenericType(type)).Add(validator).Singleton();
}
}
}
private IValidator CreateValidator(Type type, IEnumerable<PropertyInfo> validatedProperties)
{
var customValidator = typeof(CustomValidator<>).MakeGenericType(type);
var propertyRules = GetRules(type, validatedProperties);
var validator = (IValidator)Activator.CreateInstance(customValidator, propertyRules);
return validator;
}
private IEnumerable<PropertyRule> GetRules(Type instanceType, IEnumerable<PropertyInfo> validatedProperties)
{
foreach (var property in validatedProperties)
{
var propertyFunc = typeof(Func<,>).MakeGenericType(instanceType, property.PropertyType);
var instance = Expression.Parameter(instanceType, "x");
var memberExpr = Expression.Property(instance, property.Name);
var expr = Expression.Lambda(propertyFunc, memberExpr, instance);
var rule = (PropertyRule)typeof(ValidatorExtensions)
.GetMethod(nameof(ValidatorExtensions.CreateRule))
.MakeGenericMethod(instanceType, property.PropertyType)
.Invoke(null, new object[] { expr });
var validationAttributes = property
.GetCustomAttributes(typeof(ValidationAttribute), true)
.Cast<ValidationAttribute>()
.ToList();
validationAttributes.ForEach(attr => rule.AddValidator(property, attr));
yield return rule;
}
}
}
public static class ValidatorExtensions
{
private static readonly List<AttributeValidator> _attributeValidators = typeof(AttributeValidator)
.Assembly
.GetTypes()
.Where(x => typeof(AttributeValidator).IsAssignableFrom(x) && !x.IsAbstract)
.Select(Activator.CreateInstance)
.Cast<AttributeValidator>()
.ToList();
public static void AddValidator(this PropertyRule rule, PropertyInfo property, ValidationAttribute attribute)
{
var attributeValidator = _attributeValidators.SingleOrDefault(x => x.Matches(attribute)) ??
throw new Exception(
$"Could not find a validator for attribute {attribute.GetType()}. " +
$"Please add an implementation of AttributeValidator<{attribute.GetType()}>");
var propertyValidator = attributeValidator.GetValidator(property, attribute);
rule.AddValidator(propertyValidator);
}
//https://github.com/JeremySkinner/FluentValidation/blob/64b78d6bdc9595d221b4d56ce70a00e6de08aa4e/src/FluentValidation/Internal/PropertyRule.cs
public static PropertyRule CreateRule<TInstance, TProperty>(Expression<Func<TInstance, TProperty>> expression)
{
var member = expression.GetMember();
var compiled = AccessorCache<TInstance>.GetCachedAccessor(member, expression);
return new PropertyRule(member,
compiled.CoerceToNonGeneric(),
expression,
() => CascadeMode.Continue,
typeof(TProperty),
typeof(TInstance));
}
}
public class CustomValidator<T> : AbstractValidator<T>
{
public CustomValidator(IEnumerable<PropertyRule> rules)
{
foreach (var rule in rules)
AddRule(rule);
}
}
public class RequiredValidator : AttributeValidator<Required>
{
protected override IPropertyValidator GetValidator(PropertyInfo property, Required attribute)
{
var defaultValue = property.PropertyType.IsValueType
? Activator.CreateInstance(property.PropertyType)
: null;
return new NotEmptyValidator(defaultValue);
}
}
public class MaxLengthValidator : AttributeValidator<MaxLength>
{
protected override IPropertyValidator GetValidator(PropertyInfo property, MaxLength attr)
{
return new MaximumLengthValidator(attr.Max);
}
}
public abstract class AttributeValidator<T> : AttributeValidator where T : ValidationAttribute
{
public bool Matches(ValidationAttribute attr) => typeof(T) == attr.GetType();
public IPropertyValidator GetValidator(PropertyInfo property, ValidationAttribute attribute) => GetValidator(property, (T)attribute);
protected abstract IPropertyValidator GetValidator(PropertyInfo property, T attribute);
}
public interface AttributeValidator
{
bool Matches(ValidationAttribute attribute);
IPropertyValidator GetValidator(PropertyInfo property, ValidationAttribute attribute);
}
public class Required : ValidationAttribute
{
}
public class MaxLength : ValidationAttribute
{
public int Max { get; }
public MaxLength(int max)
{
Max = max;
}
}
[AttributeUsage(AttributeTargets.Property)]
public abstract class ValidationAttribute : Attribute
{
}
public class Person
{
[Required]
public string Name { get; set; }
[MaxLength(3)]
public string Age { get; set; }
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment