Forked from JoeGannon/FluentValidation-Attributes.cs
Created
February 3, 2020 16:27
-
-
Save Kaffeegangster/fe3be747e2479698c670e5cb67851d10 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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