在 blazor UI 和 ASP .Net Core 控制器共享的域对象上进行验证的位置

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

我正在尝试找出将数据验证放在新应用程序中的位置。

该应用程序由 Blazor UI(主要由简单的表单组成)和一些控制器组成。 可以通过 Blazor UI 和控制器完成相同的操作,它们都调用相同的服务。

它们不共享相同的模型类。 在控制器端,我期望一个“xxxDto”类,在 blazor 端,我使用“xxxModel”类,它们都自动映射到“xxxInput”类,这是我的服务期望的类。

DTO 和 Model 略有不同(模型有默认值,部分值被拆分以便于输入)

我的问题是知道我应该将数据验证放在哪里(使用 FluentValidation)。 我认为在 Dto 和 Model 类上重复验证不是一个好主意。 在 Input 类上执行验证,并在 Model 和 Dto 类上添加一些“代理”验证是一个好习惯吗? (我正在考虑做一个验证器,它将值自动映射到输入类,然后使用该验证器。它会起作用吗?)

现在,我只有模型验证,我正在尝试添加控制器层。

asp.net-core .net-core blazor fluentvalidation
2个回答
0
投票

当您有不同的类(Model 和 Dto)但需要在将相同的数据传递给服务之前对其进行验证时,最好集中验证逻辑以避免重复。使用输入类进行验证然后映射到模型和 Dto 的方法听起来很合理。这是建议的方法:

第1步:创建输入类: 定义一个单独的输入类,其中包含您的服务所需的数据的组合和扁平表示。这将是您在将其传递给服务之前验证的类。

   public class MyEntityInput
{
    public string Name { get; set; }
    // Other properties with flattened structure
}

第 2 步:在输入类上使用 FluentValidation: 验证器将包含您的验证规则。

public class MyEntityInputValidator : AbstractValidator<MyEntityInput>
{
    public MyEntityInputValidator()
    {
        RuleFor(x => x.Name).NotEmpty().MaximumLength(50);
        // Other validation rules
    }
}

第3步:模型和Dto的代理验证: 创建一个方法,该方法采用 Model 或 Dto 的实例,将其映射到 Input 类,然后使用 MyEntityInputValidator 进行验证。您可以在 Blazor UI 和控制器中执行操作之前使用此方法。

public class MyValidationService
{
    private readonly IValidator<MyEntityInput> _inputValidator;

    public MyValidationService(IValidator<MyEntityInput> inputValidator)
    {
        _inputValidator = inputValidator;
    }

    public ValidationResult ValidateAndMapToInput(MyEntityModel model)
    {
        var input = Mapper.Map<MyEntityInput>(model);
        return _inputValidator.Validate(input);
    }

    public ValidationResult ValidateAndMapToInput(MyEntityDto dto)
    {
        var input = Mapper.Map<MyEntityInput>(dto);
        return _inputValidator.Validate(input);
    }
}

第 4 步:Blazor UI 和控制器中的使用: 在 Blazor UI 和控制器中调用服务之前,请使用“ValidateAndMapToInput”方法验证数据。

public class MyBlazorComponent : ComponentBase
{
    [Inject]
    public MyValidationService ValidationService { get; set; }

    public async Task HandleSubmit(MyEntityModel model)
    {
        var validationResult = ValidationService.ValidateAndMapToInput(model);

        if (validationResult.IsValid)
        {
            // Call the service
        }
        else
        {
            // Handle validation errors in the UI
        }
    }
}

[ApiController]
[Route("api/[controller]")]
public class MyController : ControllerBase
{
    private readonly MyValidationService _validationService;

    public MyController(MyValidationService validationService)
    {
        _validationService = validationService;
    }

    [HttpPost]
    public IActionResult Post([FromBody] MyEntityDto dto)
    {
        var validationResult = _validationService.ValidateAndMapToInput(dto);

        if (validationResult.IsValid)
        {
            // Call the service
        }
        else
        {
            // Return validation errors
            return BadRequest(validationResult.Errors);
        }
    }
}

在上述所有步骤中,您将验证逻辑集中在输入类中并避免重复。 MyValidationService 封装了验证和映射逻辑,使其易于在 Blazor UI 和控制器中使用。


0
投票

我也想要一个 AbstractValidator(来自 FluentValidator)来验证我的 DTO/模型。这是最困难的部分,我最终得到了这个(我不建议盲目复制/粘贴,但它适用于我的情况)

它为 DTO/模型创建一个验证器,其中包含对输入验证器的引用。验证 DTO/模型的工作方式如下:

  1. 验证 DTO/模型(可以在 .ctor 中添加规则)。
  2. 如果无效,则已返回错误。
  3. 将 DTO/模型映射到输入。
  4. 使用其他验证器验证DTO/模型,并返回结果。

我还使用了抽象方法,因此实现可以将输入中的字段名称映射到 DTO/模型

public abstract class MappedValidator<T, TMappedValidator, TMapped> : AbstractValidator<T>
where TMappedValidator : AbstractValidator<TMapped>
{
    private readonly IMapper _mapper;
    private readonly TMappedValidator _chainedValidator;

    public MappedValidator(IMapper mapper, TMappedValidator chainedValidator)
    {
        _mapper = mapper;
        _chainedValidator = chainedValidator;
    }

    public abstract string MapPropertyName(string propertyName);

    public override ValidationResult Validate(ValidationContext<T> context)
    {
        // Validate against the current validator
        var validationResult = base.Validate(context);

        if (!validationResult.IsValid)
        {
            return validationResult;
        }

        TMapped? mappedModel = _mapper.Map<TMapped>(context.InstanceToValidate);
        if (context.Selector is MemberNameValidatorSelector selector)
        {
            // If we are valid against the current type, make sure the linked validator is also correct
            string[] memberNames = selector.MemberNames.Select(MapPropertyName).ToArray();

            validationResult = _chainedValidator.Validate(ValidationContext<TMapped>.CreateWithOptions(mappedModel, x => x.IncludeProperties(memberNames)));
            validationResult.Errors.ForEach(e => { e.PropertyName = memberNames.FirstOrDefault(); });
        }
        else
        {
            validationResult = _chainedValidator.Validate(mappedModel);
        }

        return validationResult;
    }

    public override async Task<ValidationResult> ValidateAsync(ValidationContext<T> context, CancellationToken cancellation = default)
    {
        // Validate against the current validator
        var validationResult = await base.ValidateAsync(context, cancellation);

        if (!validationResult.IsValid)
        {
            return validationResult;
        }

        TMapped? mappedModel = _mapper.Map<TMapped>(context.InstanceToValidate);
        if (context.Selector is MemberNameValidatorSelector selector)
        {
            // If we are valid against the current type, make sure the linked validator is also correct
            string[] memberNames = selector.MemberNames.Select(MapPropertyName).ToArray();

            validationResult = await _chainedValidator.ValidateAsync(ValidationContext<TMapped>.CreateWithOptions(mappedModel, x => x.IncludeProperties(memberNames)), cancellation);
            validationResult.Errors.ForEach(e => { e.PropertyName = memberNames.FirstOrDefault(); });
        }
        else
        {
            validationResult = await _chainedValidator.ValidateAsync(mappedModel, cancellation);
        }

        return validationResult;
    }
}

实现如下所示(在本例中,属性 StartDateUtc 映射到自动映射器配置文件中的属性 StartDate)

public class Validator : MappedValidator<GenerationModel, GenerationInputValidator, GenerationInput>
{
    public Validator(IMapper mapper, GenerationInputValidator baseValidator)
        : base(mapper, baseValidator)
    {
    }
             
    public override string MapPropertyName(string propertyName)
        => propertyName switch
        {
            nameof(StartDateUtc) => nameof(GenerationInput.StartDate),
            nameof(EndDateUtc) => nameof(GenerationInput.EndDate),
            _ => propertyName
        };

}

我希望这可以帮助其他人

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