FluentValidator 和 JsonPatchDocument

问题描述 投票:0回答:4

我有 WebAPI (.NET Core) 并使用

FluentValidator
来验证模型,包括更新。 我使用 PATCH 动词并有以下方法:

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

另外,我有一个

validator
课程:

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");
    }
}

并将此

validator
映射到
Startup
类中:

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

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

FluentValidator

asp.net-core-2.0 patch fluentvalidation json-patch
4个回答
1
投票

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

假设您有以下请求模型:

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

您的 Validator 类可以继承于

AbstractValidator
JsonPatchDocument
,而不是具体的请求模型类型。

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

结合这两个想法,你可以创建这样的东西:

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 中定义的操作可能会很乏味。

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


1
投票

public class JsonPatchDocumentValidator<T> : AbstractValidator<JsonPatchDocument<T>> where T: class, new() { private readonly IValidator<T> _validator; public JsonPatchDocumentValidator(IValidator<T> validator) { _validator = validator; } private static string NormalizePropertyName(string propertyName) { if (propertyName[0] == '/') { propertyName = propertyName.Substring(1); } return char.ToUpper(propertyName[0]) + propertyName.Substring(1); } // apply path to the model private static T ApplyPath(JsonPatchDocument<T> patchDocument) { var model = new T(); patchDocument.ApplyTo(model); return model; } // returns only updated properties private static string[] CollectUpdatedProperties(JsonPatchDocument<T> patchDocument) => patchDocument.Operations.Select(t => NormalizePropertyName(t.path)).Distinct().ToArray(); public override ValidationResult Validate(ValidationContext<JsonPatchDocument<T>> context) { return _validator.Validate(ApplyPath(context.InstanceToValidate), o => o.IncludeProperties(CollectUpdatedProperties(context.InstanceToValidate))); } public override async Task<ValidationResult> ValidateAsync(ValidationContext<JsonPatchDocument<T>> context, CancellationToken cancellation = new CancellationToken()) { return await _validator.ValidateAsync(ApplyPath(context.InstanceToValidate), o => o.IncludeProperties(CollectUpdatedProperties(context.InstanceToValidate)), cancellation); } }

必须手动注册:

builder.Services.AddScoped<IValidator<JsonPatchDocument<TollUpdateAPI>>, JsonPatchDocumentValidator<TollUpdateAPI>>();



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(); }



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();
}

© www.soinside.com 2019 - 2024. All rights reserved.