用于不显眼的客户端验证的 ValidationAttribute 注入服务

ValidationAttribute injecting services for unobtrusive client validation

我有一个相对简单的 ValidationAttribute,它需要一些服务才能完成 IsValid 方法 - 基本上只是检索 ID 列表以与所选值进行比较。 正如此 post here 所建议的,我正在使用服务定位器模式在服务器端获取这些内容,并且工作正常。

但是,我也想在客户端上执行相同的验证,但尽管我可能会尝试,但我找不到通过 AttributeAdapter 添加相同数据的方法,或者就此而言,将其设置在属性本身足够早 - 这样 AttributeAdapter 就可以获取它 - 这样我就可以将 ID 添加到 MergeAttribute 调用中,从而在客户端使用一些 JavaScript 访问它们。

这可能吗?任何想法都是最有用的

上下文的一些简化代码...

验证属性:

public class InvalidIdAttribute : ValidationAttribute
{
    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        var configService = validationContext.GetService<IProductService>();
        var productIds = configService.GetIds()

        var selectedId = (int)value;

        var invalidId = productIds.SingleOrDefault(p => p.Invalid && p.Id == selectedId);

        if (invalidId != null)
            return new ValidationResult(FormatErrorMessage(invalidId.Name));

        return ValidationResult.Success;
    }
}

和属性适配器

public class InvalidIdAttributeAdapter : AttributeAdapterBase<InvalidIdAttribute>
{
    private readonly InvalidIdAttribute _attribute;

    public InvalidIdAttributeAdapter(InvalidIdAttribute attribute, IStringLocalizer stringLocalizer)
        : base(attribute, stringLocalizer)
    {
        _attribute = attribute;
    }

    public override void AddValidation(ClientModelValidationContext context)
    {
        //how do I get the productService in here?
        var invalidIds = productService.GetIds().Where(p=>p.Invalid==true).Select(p=>p.Id);
        var pipedIds = string.Join("|", invalidIds);

        MergeAttribute(context.Attributes, "data-val", "true");
        MergeAttribute(context.Attributes, "data-val-invalidid", GetErrorMessage(context));
        MergeAttribute(context.Attributes, "data-val-invalidid-props", pipedIds); 
    }

    public override string GetErrorMessage(ModelValidationContextBase validationContext)
    {
        return _attribute.FormatErrorMessage(validationContext.ModelMetadata.GetDisplayName());
    }
}

正确的方法是将该服务作为属性适配器的一部分。所以只需在其构造函数中添加额外的依赖项:

public class InvalidIdAttributeAdapter : AttributeAdapterBase<InvalidIdAttribute>
{
    private readonly InvalidIdAttribute _attribute;
    private readonly IProductService _productService;

    public InvalidIdAttributeAdapter(InvalidIdAttribute attribute, IStringLocalizer stringLocalizer, IProductService _productService)
        : base(attribute, stringLocalizer)
    {
        _attribute = attribute;
        _productService = productService;
    }

    public override void AddValidation(ClientModelValidationContext context)
    {
        var invalidIds = _productService.GetIds()…;

        // …
    }
}

如果您通过自定义属性适配器提供程序构建属性适配器,则必须使其依赖于服务并将其传递给属性适配器。由于属性适配器提供者是通过依赖注入容器注册和解析的,所以你可以在它的构造函数中添加额外的依赖。

请注意,如果您的 IProductService 依赖于范围依赖项,例如数据库上下文,那么您必须将属性适配器提供程序注册为作用域依赖项本身而不是单例:

services.AddScoped<IValidationAttributeAdapterProvider, InvalidIdAttributeAdapterProvider>();