Created
October 23, 2016 21:23
-
-
Save maca88/9e4551da22cf53056143bef76148a452 to your computer and use it in GitHub Desktop.
Example of a custom validation context in FluentValidation
This file contains hidden or 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
using System.Collections.Generic; | |
using System.Linq; | |
namespace ConsoleApplication1 | |
{ | |
using FluentValidation; | |
using FluentValidation.Internal; | |
public class ParentEntity { | |
public List<ChildEntity> Children { get; set; } = new List<ChildEntity>(); | |
} | |
public class ChildEntity { | |
public string Code { get; set; } | |
} | |
public class ParentEntityValidator : AbstractValidator<ParentEntity> { | |
public ParentEntityValidator() { | |
RuleFor(o => o.Children).SetCollectionValidator(new ChildEntityValidator()); | |
} | |
} | |
public class ChildEntityValidator : AbstractValidator<ChildEntity> { | |
public ChildEntityValidator() { | |
RuleFor(m => m.Code) | |
.Must((entity, code, ctx) => { | |
// Get the cached existing codes in order to avoid a db query | |
var cctx = ctx.ParentContext as ICustomValidationContext; | |
var rootCtx = cctx.GetRootData<CachedData>(); | |
if (!rootCtx.ExistingCodes.Contains(code)) { | |
return false; | |
} | |
return true; | |
}) | |
.WithMessage("Code does not exist in the database"); | |
} | |
} | |
public interface ICustomValidationContext : IValidationContext { | |
IValidationContext Parent { get; } | |
TData GetRootData<TData>(); | |
} | |
public interface IRootValidationContext<out TData> { | |
TData Data { get; } | |
} | |
public class CustomValidationContext : ValidationContext, ICustomValidationContext { | |
public CustomValidationContext(object instanceToValidate) : base(instanceToValidate) { | |
} | |
public CustomValidationContext(object instanceToValidate, PropertyChain propertyChain, IValidatorSelector validatorSelector) : base(instanceToValidate, propertyChain, validatorSelector) { | |
} | |
public IValidationContext Parent { get; set; } | |
public virtual IRootValidationContext<TData> GetRootContext<TData>() { | |
var parent = Parent as ICustomValidationContext; | |
while (parent != null) { | |
var root = parent as IRootValidationContext<TData>; | |
if (root != null) { | |
return root; | |
} | |
parent = parent.Parent as ICustomValidationContext; | |
} | |
return null; | |
} | |
public virtual TData GetRootData<TData>() { | |
return GetRootContext<TData>().Data; | |
} | |
public override IValidationContext Clone(PropertyChain chain = null, object instanceToValidate = null, IValidatorSelector selector = null) { | |
var clone = (CustomValidationContext)base.Clone(chain, instanceToValidate, selector); | |
clone.Parent = Parent; | |
return clone; | |
} | |
public override IValidationContext<TType> Clone<TType>(PropertyChain chain = null, TType instanceToValidate = default(TType), IValidatorSelector selector = null, bool? isChildContext = null) { | |
var clone = (CustomValidationContext<TType>)base.Clone(chain, instanceToValidate, selector, isChildContext); | |
clone.Parent = Parent; | |
return clone; | |
} | |
public override IValidationContext CloneForChildValidator(object instanceToValidate) { | |
var clone = (CustomValidationContext)base.CloneForChildValidator(instanceToValidate); | |
clone.Parent = this; | |
return clone; | |
} | |
} | |
public class CustomValidationContext<T> : CustomValidationContext, IValidationContext<T> { | |
public CustomValidationContext(T instanceToValidate) : base(instanceToValidate) { | |
} | |
public CustomValidationContext(T instanceToValidate, PropertyChain propertyChain, IValidatorSelector validatorSelector) : base(instanceToValidate, propertyChain, validatorSelector) { | |
InstanceToValidate = instanceToValidate; | |
} | |
public CustomValidationContext(T instanceToValidate, PropertyChain propertyChain, IValidatorSelector validatorSelector, bool isChildContext = false, IValidationContext parentContext = null) : base(instanceToValidate, propertyChain, validatorSelector) { | |
InstanceToValidate = instanceToValidate; | |
IsChildContext = isChildContext; | |
Parent = parentContext; | |
} | |
public new T InstanceToValidate { get; } | |
} | |
public class RootValidationContext<T, TData> : CustomValidationContext<T>, IRootValidationContext<TData> { | |
public RootValidationContext(T instanceToValidate, TData data) : base(instanceToValidate) { | |
Data = data; | |
} | |
public override IRootValidationContext<TType> GetRootContext<TType>() { | |
return this as IRootValidationContext<TType>; | |
} | |
public TData Data { get; } | |
} | |
public class CachedData { | |
public CachedData(HashSet<string> existingCode) { | |
ExistingCodes = existingCode; | |
} | |
public HashSet<string> ExistingCodes { get; } | |
} | |
public class CustomValidationContextFactory : IValidationContextFactory { | |
public virtual IValidationContext<T> Get<T>(T instanceToValidate, PropertyChain propertyChain, IValidatorSelector validatorSelector) { | |
return new CustomValidationContext<T>(instanceToValidate, propertyChain, validatorSelector); | |
} | |
public IValidationContext<T> Get<T>(T instanceToValidate, PropertyChain propertyChain, IValidatorSelector validatorSelector, bool isChildContext) { | |
return new CustomValidationContext<T>(instanceToValidate, propertyChain, validatorSelector, isChildContext); | |
} | |
public virtual IValidationContext Get(object instanceToValidate, PropertyChain propertyChain, IValidatorSelector validatorSelector) { | |
return new CustomValidationContext(instanceToValidate, propertyChain, validatorSelector); | |
} | |
public virtual IValidationContext Get(object instanceToValidate) { | |
return new CustomValidationContext(instanceToValidate); | |
} | |
} | |
class Program { | |
static void Main(string[] args) { | |
// Set our custom factory for ValidationContext | |
ValidatorOptions.ValidationContextFactory = new CustomValidationContextFactory(); | |
// Create entities to validate | |
var parent = new ParentEntity(); | |
for (var i = 0; i < 500; i++) { | |
parent.Children.Add(new ChildEntity {Code = $"Code{i}"}); | |
} | |
// Fake our database of codes | |
var dbCodeSet = Enumerable.Range(0, 499).Select(i => $"Code{i}"); | |
// Simulate a query that will find all existing codes from the database | |
var childCodes = parent.Children.Select(o => o.Code).ToList(); | |
var existingCodes = new HashSet<string>(dbCodeSet.Where(o => childCodes.Contains(o))); | |
// Create a custom validation context with the fetched existing codes | |
var rootCtx = new RootValidationContext<ParentEntity, CachedData>(parent, new CachedData(existingCodes)); | |
var result = new ParentEntityValidator().Validate(rootCtx); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment