一个很好的 FluentValidation 示例

2022年11月24日 1098点热度 0人点赞 0条评论
内容纲要

WorkflowDefinitionValidator 是整个验证入口,上一个非常复杂的对象结构。
参考:https://github.com/serverlessworkflow/specification/blob/main/specification.md

通过依赖注入,继续检查下一层的字段。

    /// <summary>
    /// Represents the service used to validate <see cref="WorkflowDefinition"/>s
    /// </summary>
    public class WorkflowDefinitionValidator
        : AbstractValidator<WorkflowDefinition>
    {

        /// <summary>
        /// Initializes a new <see cref="WorkflowDefinitionValidator"/>
        /// </summary>
        /// <param name="serviceProvider">The current <see cref="IServiceProvider"/></param>
        public WorkflowDefinitionValidator(IServiceProvider serviceProvider)
        {
            this.ServiceProvider = serviceProvider;
            this.RuleFor(w => w.Id)
                .NotEmpty()
                .When(w => string.IsNullOrWhiteSpace(w.Key));
            this.RuleFor(w => w.Key)
                .NotEmpty()
                .When(w => string.IsNullOrWhiteSpace(w.Id));
            this.RuleFor(w => w.Name)
                .NotEmpty();
            this.RuleFor(w => w.Version)
                .NotEmpty();
            this.RuleFor(w => w.ExpressionLanguage)
                .NotEmpty();
            this.RuleFor(w => w.Start!)
                .Must(ReferenceExistingState)
                .When(w => w.Start != null)
                .WithMessage((workflow, start) => $"Failed to find the state with name '{start.StateName}' specified by the workflow's start definition");
            this.RuleFor(w => w.StartStateName!)
                .Must(ReferenceExistingState)
                .When(w => w.StartStateName != null)
                .WithMessage((workflow, startState) => $"Failed to find the state with name '{startState}' specified by the workflow's start definition");
            this.RuleFor(w => w.Events)
                .Must(events => events!.Select(s => s.Name).Distinct().Count() == events!.Count)
                .When(w => w.Events != null)
                .WithMessage("Duplicate EventDefinition name(s) found");
            this.RuleFor(w => w.Events)
                .SetValidator(new CollectionPropertyValidator<EventDefinition>(this.ServiceProvider))
                .When(w => w.Events != null);
            this.RuleFor(w => w.Functions)
                .Must(functions => functions!.Select(s => s.Name).Distinct().Count() == functions!.Count)
                .When(w => w.Functions != null)
                .WithMessage("Duplicate FunctionDefinition name(s) found");
            this.RuleFor(w => w.Functions!)
                .SetValidator(new FunctionDefinitionCollectionValidator())
                .When(w => w.Functions != null);
            this.RuleFor(w => w.Retries)
                .Must(retries => retries!.Select(s => s.Name).Distinct().Count() == retries!.Count)
                .When(w => w.Retries != null)
                .WithMessage("Duplicate RetryPolicyDefinition name(s) found");
            this.RuleFor(w => w.Retries)
                .SetValidator(new CollectionPropertyValidator<RetryDefinition>(this.ServiceProvider))
                .When(w => w.Retries != null);
            this.RuleFor(w => w.Auth!)
                .Must(auths => auths.Select(s => s.Name).Distinct().Count() == auths.Count)
                .When(w => w.Auth != null)
                .WithMessage("Duplicate AuthenticationDefinition name(s) found");
            this.RuleFor(w => w.Auth!)
                .SetValidator(new CollectionPropertyValidator<AuthenticationDefinition>(this.ServiceProvider))
                .When(w => w.Auth != null);
            this.RuleFor(w => w.States)
                .NotEmpty();
            this.RuleFor(w => w.States)
                .Must(states => states.Select(s => s.Name).Distinct().Count() == states.Count)
                .When(w => w.States != null)
                .WithMessage("Duplicate StateDefinition name(s) found");
            this.RuleFor(w => w.States)
                .SetValidator(new WorkflowStatesPropertyValidator(this.ServiceProvider))
                .When(w => w.States != null);
        }

        /// <summary>
        /// Gets the current <see cref="IServiceProvider"/>
        /// </summary>
        protected IServiceProvider ServiceProvider { get; }

        /// <inheritdoc/>
        public override ValidationResult Validate(ValidationContext<WorkflowDefinition> context)
        {
            ValidationResult validationResult = base.Validate(context);
            if (context.InstanceToValidate.States != null 
                && !context.InstanceToValidate.States.Any(s => s.End != null))
                validationResult.Errors.Add(new ValidationFailure("End", $"The workflow's main control flow must specify an EndDefinition"));
            return validationResult;
        }

        /// <summary>
        /// Determines whether or not the specified <see cref="StartDefinition"/> references an existing <see cref="StateDefinition"/>
        /// </summary>
        /// <param name="workflow">The <see cref="WorkflowDefinition"/> to validate</param>
        /// <param name="start">The <see cref="StartDefinition"/> to check</param>
        /// <returns>A boolean indicating whether or not the specified <see cref="StateDefinition"/> exists</returns>
        protected virtual bool ReferenceExistingState(WorkflowDefinition workflow, StartDefinition start)
        {
            return workflow.TryGetState(start.StateName, out _);
        }

        /// <summary>
        /// Determines whether or not the specified <see cref="StartDefinition"/> references an existing <see cref="StateDefinition"/>
        /// </summary>
        /// <param name="workflow">The <see cref="WorkflowDefinition"/> to validate</param>
        /// <param name="startStateName">The name of the start <see cref="StateDefinition"/></param>
        /// <returns>A boolean indicating whether or not the specified <see cref="StateDefinition"/> exists</returns>
        protected virtual bool ReferenceExistingState(WorkflowDefinition workflow, string startStateName)
        {
            return workflow.TryGetState(startStateName, out _);
        }

    }

集合 PropertyValidator<WorkflowDefinition, IEnumerable<TElement>?> 验证结构:


    /// <summary>
    /// Represents the service used to validate a workflow's <see cref="ICollection{T}"/>s
    /// </summary>
    internal class CollectionPropertyValidator<TElement>
        : PropertyValidator<WorkflowDefinition, IEnumerable<TElement>?>
    {

        /// <summary>
        /// Initializes a new <see cref="CollectionPropertyValidator{TElement}"/>
        /// </summary>
        /// <param name="serviceProvider">The current <see cref="IServiceProvider"/></param>
        public CollectionPropertyValidator(IServiceProvider serviceProvider)
        {
            this.ServiceProvider = serviceProvider;
        }

        /// <inheritdoc/>
        public override string Name => "CollectionValidator";

        /// <summary>
        /// Gets the current <see cref="IServiceProvider"/>
        /// </summary>
        protected IServiceProvider ServiceProvider { get; }

        /// <inheritdoc/>
        public override bool IsValid(ValidationContext<WorkflowDefinition> context, IEnumerable<TElement>? value)
        {
            int index = 0;
            if (value == null)
                return true;
            foreach (TElement elem in value)
            {
                IEnumerable<IValidator<TElement>> validators = this.ServiceProvider.GetServices<IValidator<TElement>>();
                foreach (IValidator<TElement> validator in validators)
                {
                    ValidationResult validationResult = validator.Validate(elem);
                    if (validationResult.IsValid)
                        continue;
                    foreach (var failure in validationResult.Errors)
                    {
                        context.AddFailure(failure);
                    }
                    return false;
                }
                index++;
            }
            return true;
        }

    }
    /// <summary>
    /// Represents the <see cref="PropertyValidator"/> used to validate a <see cref="FunctionDefinition"/> collection
    /// </summary>
    internal class FunctionDefinitionCollectionValidator
        : PropertyValidator<WorkflowDefinition, IEnumerable<FunctionDefinition>>
    {

        /// <inheritdoc/>
        public override string Name => "FunctionDefinitionCollection";

        /// <inheritdoc/>
        public override bool IsValid(ValidationContext<WorkflowDefinition> context, IEnumerable<FunctionDefinition> value)
        {
            WorkflowDefinition workflow = context.InstanceToValidate;
            int index = 0;
            IValidator<FunctionDefinition> validator = new FunctionDefinitionValidator(workflow);
            foreach (FunctionDefinition function in value)
            {

                ValidationResult validationResult = validator.Validate(function);
                if (validationResult.IsValid)
                {
                    index++;
                    continue;
                }
                foreach(var failure in validationResult.Errors)
                {
                    context.AddFailure(failure);
                }
                return false;
            }
            return true;
        }

    }

痴者工良

高级程序员劝退师

文章评论