首页 > 解决方案 > 如何使用 ASP.NET Core 更改输入字段的客户端验证错误 CSS 类?

问题描述

我有一个带有客户端验证的表单,当检测到错误输入时,会更改输入字段的类属性;它被更改为包括"input-validation-error"类。

我想更改此类,以便不使用它,而是使用 Bootstraps 类"is-invalid"

我尝试使用 ASP.NET Core 的TagHelpers,但这没有效果;我相信这不会起作用,因为只有加载“整个页面”时,帮助程序才会起作用,它对客户端验证没有帮助。

当我在 .NET 项目中搜索时,会发现在 .NET 项目中定义的 css 类"Unobtrusive validation support library for jQuery"

改变这个类的最好方法是什么?

CSS 可以通过将一个类从一个类更改为另一个类来提供帮助吗? (覆盖原始类,不确定这是否可能)

还是应该使用 JavaScript 重新配置JQuery

这是我的 TagHelper,添加了助手:validation-for,validation-error-class,validation-valid-class

表单/HTML...

<input type="email" asp-for="Email" id="inputEmail" class="form-control" placeholder="Email address" required
                            validation-for="Email" validation-error-class="is-invalid" validation-valid-class="is-valid"/>
                            <span class="small" asp-validation-for="Email"></span>

这是我的 TagHelper 的代码片段。

[HtmlTargetElement("input", Attributes = "validation-for,validation-error-class,validation-valid-class")]
public class ValidationErrorClassTagHelper : TagHelper
{
    [HtmlAttributeName("validation-for")]
    public ModelExpression For { get; set; }

    [HtmlAttributeName("validation-error-class")]
    public string ErrorClass { get; set; }
    [HtmlAttributeName("validation-valid-class")]
    public string ValidClass { get; set; }


    [HtmlAttributeNotBound]
    [ViewContext]
    public ViewContext ViewContext { get; set; }

    public override void Process(TagHelperContext context, TagHelperOutput output)
    {
        output.RemoveClass(ErrorClass,HtmlEncoder.Default);
        output.RemoveClass(ValidClass,HtmlEncoder.Default);

        if (ViewContext.ViewData.ModelState.IsValid) {                
            output.AddClass(ValidClass,HtmlEncoder.Default);
        } else 
        {
            output.AddClass(ErrorClass,HtmlEncoder.Default);                
        }          
    }
}

新方法不能 100% 工作。

我尝试了另一种方法,通过修改jQuery defaultOptions、更改errorClass和validClass。

在 [ https://github.com/brecons/jquery-validation-unobtrusive-bootstrap][gitHub]上找到的代码片段

function ($) {
    if($.validator && $.validator.unobtrusive){
        var defaultOptions = {
            validClass: 'is-valid',
            errorClass: 'is-invalid',

这适用于errorClass,但对我来说validClass保持不变,它仍然被命名valid

标签: c#jqueryasp.net-core

解决方案


我也遇到过这个问题,但我真的不喜欢在客户端修复它的想法,因为 jQuery 技巧仅在浏览器中启用 JavaScript 时才有效。正因为如此,我认为这个问题应该在服务器端解决。

不幸的是,该框架没有提供配置与验证相关的 css 类的方法,这些字符串只是硬编码的

但是,这些字段不是常量,而是声明为静态的,因此我们可以通过反射在运行时更改它们的值(以某种方式呈现在这里)。但这类事情是肮脏的黑客,应该是我们最后的手段。

OP 的自定义标签助手的想法看起来要好得多。但它有一个缺点:它只修复了标签助手生成的标记。经典的,类似Html.TextBox(...)的方法仍然会被打破。

那么,我们还能做得更好吗?幸运的是,是的!

Html和标签助手实现都在后台使用服务IHtmlGenerator来生成它们的标记。得益于 ASP.NET Core 的模块化架构,我们可以提供该服务的定制版本。(更重要的是,我们甚至可以在不复制一堆代码的情况下做到这一点,因为默认实现将相关方法声明为虚拟。)

因此,我可以想出这个解决方案:

public sealed class CustomHtmlGenerator : DefaultHtmlGenerator
{
    private static IHtmlHelper GetHtmlHelperFor(ViewContext viewContext)
    {
        const string htmlHelperViewDataKey = nameof(CustomHtmlGenerator) + "_" + nameof(IHtmlHelper);

        if (!viewContext.ViewData.TryGetValue(htmlHelperViewDataKey, out var htmlHelperObj) || !(htmlHelperObj is IHtmlHelper htmlHelper))
            viewContext.ViewData[htmlHelperViewDataKey] = htmlHelper = GetViewHtmlHelper(viewContext) ?? CreateHtmlHelper(viewContext);

        return htmlHelper;

        static IHtmlHelper GetViewHtmlHelper(ViewContext viewContext)
        {
            if (!(viewContext.View is RazorView razorView))
                return null;

            dynamic razorPage = razorView.RazorPage;

            try { return (IHtmlHelper)razorPage.Html; }
            catch { return null; }
        }

        static IHtmlHelper CreateHtmlHelper(ViewContext viewContext)
        {
            var htmlHelper = viewContext.HttpContext.RequestServices.GetRequiredService<IHtmlHelper>();
            (htmlHelper as IViewContextAware)?.Contextualize(viewContext);
            return htmlHelper;
        }
    }

    private static TagBuilder AddBootstrapValidationCssClasses(ViewContext viewContext, string expression, TagBuilder tagBuilder)
    {
        // we need to get the model property key from the expression, which functionality is buried in an internal class unfortunately
        // (https://github.com/dotnet/aspnetcore/blob/v3.1.6/src/Mvc/Mvc.ViewFeatures/src/NameAndIdProvider.cs#L147)
        // however, this internal API is exposed via the IHtmlHelper.Name method:
        // (https://github.com/dotnet/aspnetcore/blob/v3.1.6/src/Mvc/Mvc.ViewFeatures/src/HtmlHelper.cs#L451)
        var htmlHelper = GetHtmlHelperFor(viewContext);
        var fullName = htmlHelper.Name(expression);

        if (viewContext.ModelState.TryGetValue(fullName, out var entry))
        {
            if (entry.ValidationState == ModelValidationState.Invalid)
                tagBuilder.AddCssClass("is-invalid");
            else if (entry.ValidationState == ModelValidationState.Valid)
                tagBuilder.AddCssClass("is-valid");
        }

        return tagBuilder;
    }

    public CustomHtmlGenerator(IAntiforgery antiforgery, IOptions<MvcViewOptions> optionsAccessor, IModelMetadataProvider metadataProvider, IUrlHelperFactory urlHelperFactory, HtmlEncoder htmlEncoder, ValidationHtmlAttributeProvider validationAttributeProvider)
        : base(antiforgery, optionsAccessor, metadataProvider, urlHelperFactory, htmlEncoder, validationAttributeProvider) { }

    protected override TagBuilder GenerateInput(ViewContext viewContext, InputType inputType, ModelExplorer modelExplorer, string expression, object value, bool useViewData, bool isChecked, bool setId, bool isExplicitValue, string format, IDictionary<string, object> htmlAttributes) =>
        AddBootstrapValidationCssClasses(viewContext, expression, base.GenerateInput(viewContext, inputType, modelExplorer, expression, value, useViewData, isChecked, setId, isExplicitValue, format, htmlAttributes));

    public override TagBuilder GenerateSelect(ViewContext viewContext, ModelExplorer modelExplorer, string optionLabel, string expression, IEnumerable<SelectListItem> selectList, ICollection<string> currentValues, bool allowMultiple, object htmlAttributes) =>
        AddBootstrapValidationCssClasses(viewContext, expression, base.GenerateSelect(viewContext, modelExplorer, optionLabel, expression, selectList, currentValues, allowMultiple, htmlAttributes));

    public override TagBuilder GenerateTextArea(ViewContext viewContext, ModelExplorer modelExplorer, string expression, int rows, int columns, object htmlAttributes) =>
        AddBootstrapValidationCssClasses(viewContext, expression, base.GenerateTextArea(viewContext, modelExplorer, expression, rows, columns, htmlAttributes));
}

剩下的就是通过在方法末尾添加以下内容来配置 DI 以解决此自定义实现Startup.ConfigureService

services.Replace(ServiceDescriptor.Singleton<IHtmlGenerator, CustomHtmlGenerator>());

推荐阅读