4

我有 WebAPI (.NET Core) 并使用 FluentValidator 来验证模型,包括更新。我使用 PATCH 动词并具有以下方法:

    public IActionResult Update(int id, [FromBody] JsonPatchDocument<TollUpdateAPI> jsonPatchDocument)
    {

另外,我有一个验证器类:

public class TollUpdateFluentValidator : AbstractValidator<TollUpdateAPI>
{
    public TollUpdateFluentValidator ()
    {
        RuleFor(d => d.Date)
            .NotNull().WithMessage("Date is required");

        RuleFor(d => d.DriverId)
            .GreaterThan(0).WithMessage("Invalid DriverId");

        RuleFor(d => d.Amount)
            .NotNull().WithMessage("Amount is required");

        RuleFor(d => d.Amount)
            .GreaterThanOrEqualTo(0).WithMessage("Invalid Amount");
    }
}

并在 Startup 类中映射此验证器:

        services.AddTransient<IValidator<TollUpdateAPI>, TollUpdateFluentValidator>();

但它不起作用。如何为我的任务编写有效的 FluentValidator?

4

3 回答 3

1

您将需要手动触发验证。您的操作方法将是这样的:

public IActionResult Update(int id, [FromBody] JsonPatchDocument<TollUpdateAPI> jsonPatchDocument)
{
    // Load your db entity
    var myDbEntity = myService.LoadEntityFromDb(id);

    // Copy/Map data to the entity to patch using AutoMapper for example
    var entityToPatch = myMapper.Map<TollUpdateAPI>(myDbEntity);

    // Apply the patch to the entity to patch
    jsonPatchDocument.ApplyTo(entityToPatch);

    // Trigger validation manually
    var validationResult = new TollUpdateFluentValidator().Validate(entityToPatch);
    if (!validationResult.IsValid)
    {
        // Add validation errors to ModelState
        foreach (var error in validationResult.Errors)
        {
            ModelState.AddModelError(error.PropertyName, error.ErrorMessage);
        }

        // Patch failed, return 422 result
        return UnprocessableEntity(ModelState);
    }

    // Map the patch to the dbEntity
    myMapper.Map(entityToPatch, myDbEntity);
    myService.SaveChangesToDb();

    // So far so good, patch done
    return NoContent();
}
于 2020-10-05T00:15:58.630 回答
1

您可以为此使用自定义规则构建器。它可能不是最优雅的处理方式,但至少验证逻辑是您期望的。

假设您有以下请求模型:

public class CarRequestModel
{
    public string Make { get; set; }
    public string Model { get; set; }
    public decimal EngineDisplacement { get; set; }
}

AbstractValidator您的 Validator 类可以从ofJsonPatchDocument而不是具体的请求模型类型继承。

另一方面,流利的验证器为我们提供了不错的扩展点,例如自定义规则。

结合这两个想法,您可以创建如下内容:

public class Validator : AbstractValidator<JsonPatchDocument<CarRequestModel>>
{
    public Validator()
    {
        RuleForEach(x => x.Operations)
           .Custom(HandleInternalPropertyValidation);
    }

    private void HandleInternalPropertyValidation(JsonPatchOperation property, CustomContext context)
    {
        void AddFailureForPropertyIf<T>(
            Expression<Func<T, object>> propertySelector,
            JsonPatchOperationType operation,
            Func<JsonPatchOperation, bool> predicate, string errorMessage)
        {
            var propertyName = (propertySelector.Body as MemberExpression)?.Member.Name;
            if (propertyName is null)
                throw new ArgumentException("Property selector must be of type MemberExpression");

            if (!property.Path.ToLowerInvariant().Contains(propertyName.ToLowerInvariant()) ||
                property.Operation != operation) return;

            if (predicate(property)) context.AddFailure(propertyName, errorMessage);
        }

        AddFailureForPropertyIf<CarRequestModel>(x => x.Make, JsonPatchOperationType.remove,
            x => true, "Car Make cannot be removed.");
        AddFailureForPropertyIf<CarRequestModel>(x => x.EngineDisplacement, JsonPatchOperationType.replace,
            x => (decimal) x.Value < 12m, "Engine displacement must be less than 12l.");
    }
}

在某些情况下,写下从域角度来看不允许但在JsonPatch RFC中定义的所有操作可能很乏味。

这个问题可以通过定义规则来缓解,这些规则将定义从您的域的角度来看有效的操作集。

于 2020-11-05T07:41:22.397 回答
0

下面的实现允许IValidator<Model>在内部使用IValidator<JsonPatchDocument<Model>>,但您需要创建具有有效属性值的模型。

public class ModelValidator : AbstractValidator<JsonPatchDocument<Model>>
{
    public override ValidationResult Validate(ValidationContext<JsonPatchDocument<Model>> context)
    {
        return _validator.Validate(GetRequestToValidate(context));
    }

    public override Task<ValidationResult> ValidateAsync(ValidationContext<JsonPatchDocument<Model>> context, CancellationToken cancellation = default)
    {
        return _validator.ValidateAsync(GetRequestToValidate(context), cancellation);
    }

    private static Model GetRequestToValidate(ValidationContext<JsonPatchDocument<Model>> context)
    {
        var validModel = new Model()
                           {
                               Name = nameof(Model.Name),
                               Url = nameof(Model.Url)
                           };

        context.InstanceToValidate.ApplyTo(validModel);
        return validModel;
    }

    private class Validator : AbstractValidator<Model>
    {
        /// <inheritdoc />
        public Validator()
        {
            RuleFor(r => r.Name).NotEmpty();
            RuleFor(r => r.Url).NotEmpty();
        }
    }

    private static readonly Validator _validator = new();
}
于 2021-07-26T11:07:03.740 回答