Skip to content

Instantly share code, notes, and snippets.

@maca88
Created October 23, 2016 21:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save maca88/9e4551da22cf53056143bef76148a452 to your computer and use it in GitHub Desktop.
Save maca88/9e4551da22cf53056143bef76148a452 to your computer and use it in GitHub Desktop.
Example of a custom validation context in FluentValidation
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