【问题标题】:EditorFor Tag Helper doesn't render validation attributes when using FluentValidatorEditorFor Tag Helper 在使用 FluentValidator 时不呈现验证属性
【发布时间】:2020-05-21 12:36:19
【问题描述】:

我有一个像这样的简单表单,它使用了@Html.EditorFor 扩展:

<form method="post">
    @Html.EditorFor(x => x.SystemSettings.EmailFromAddress)
    <submit-button title="Save"></submit-button>
</form>

我想利用 .NET Core 的标签助手,让我的表单看起来像这样:

<form method="post">
    <editor asp-for="SystemSettings.EmailFromAddress"/>
    <submit-button title="Save"></submit-button>
</form>

我最终也想拥有自己的自定义标签助手,这样我就可以这样做:

<text-box asp-for="SystemSettings.EmailFromAddress"></text-box>

我有一个由@Html.EditorFor 扩展渲染的string 模板:

@model string
<div class="form-group">
    <label asp-for="@Model" class="m-b-none"></label>
    <span asp-description-for="@Model" class="help-block m-b-none small m-t-none"></span>
    <div class="input-group">
        <input asp-for="@Model" class="form-control" />
        <partial name="_ValidationIcon" />
    </div>
    <span asp-validation-for="@Model" class="validation-message"></span>
</div>

为此,我看到有人实现了一个 EditorTagHelper,如下所示:

[HtmlTargetElement("editor", TagStructure = TagStructure.WithoutEndTag,
    Attributes = ForAttributeName)]
public class EditorTagHelper : TagHelper
{
    private readonly IHtmlHelper _htmlHelper;

    private const string ForAttributeName = "asp-for";
    private const string TemplateAttributeName = "asp-template";

    [HtmlAttributeName(ForAttributeName)]
    public ModelExpression For { get; set; }


    [HtmlAttributeName(TemplateAttributeName)]
    public string Template { get; set; }

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

    public EditorTagHelper(IHtmlHelper htmlHelper)
    {
        _htmlHelper = htmlHelper;
    }

    public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
    {
        if (context == null)
            throw new ArgumentNullException(nameof(context));

        if (output == null)
            throw new ArgumentNullException(nameof(output));

        if (!output.Attributes.ContainsName(nameof(Template)))
        {
            output.Attributes.Add(nameof(Template), Template);
        }

        output.SuppressOutput();

        (_htmlHelper as IViewContextAware).Contextualize(ViewContext);

        output.Content.SetHtmlContent(_htmlHelper.Editor(For.Name, Template));

        await Task.CompletedTask;
    }
}

当我使用 EditorTagHelper 时,它似乎缺少不显眼的 Javascript 验证属性:

使用 @Html.EditorFor,它会被渲染:

<input class="form-control valid" type="text" data-val="true" data-val-required="Email From Address cannot be empty" id="SystemSettings_EmailFromAddress" name="SystemSettings.EmailFromAddress" value="whatever@test.com" aria-required="true" aria-invalid="false" aria-describedby="SystemSettings_EmailFromAddress-error">

它具有 data-val 属性,因此可以应用客户端验证。

当我改用 EditorTagHelper 时,它会被渲染:

<input class="form-control valid" type="text" id="SystemSettings_EmailFromAddress" name="SystemSettings.EmailFromAddress" value="whatever@test.com" aria-invalid="false">

未应用不显眼的验证属性。我正在使用 FluentValidation,并且我指定了这样的 AbstractValidator:

public class SystemSettingsValidator : AbstractValidator<SystemSettings>
{
    public SystemSettingsValidator()
    {
        RuleFor(x => x.EmailFromAddress).NotEmpty()
            .WithMessage("Email From Address cannot be empty");
    }
}

我发现,如果我删除 AbstractorValidator 并简单地向我的模型属性添加一个 [Required] 属性,那么验证就会正常工作。这表明 FluentValidation 有问题。可能存在配置问题。

我正在使用 Autofac 依赖注入来扫描我的程序集并注册验证器类型:

builder.RegisterAssemblyTypes(Assembly.Load(assembly))
    .Where(t => t.IsClosedTypeOf(typeof(IValidator<>)))
    .AsImplementedInterfaces()
    .PropertiesAutowired()
    .InstancePerLifetimeScope();

这似乎工作正常。如果它不好,我还尝试从流式验证选项中注册验证器,如下所示:

.AddFluentValidation(fv =>
{
    fv.RegisterValidatorsFromAssemblies(new List<Assembly>
        {Assembly.GetExecutingAssembly(), Assembly.Load(nameof(Entities))});
})

这似乎也很好。

需要注意的一点是,我之前遇到的一个问题是,当包含标签助手时,使用 Autofac 程序集扫描会破坏应用程序。我添加了一个过滤器以确保在注册这些依赖项时不包含标签助手,例如

builder.RegisterAutowiredAssemblyInterfaces(Assembly.Load(Web))
    .Where(x => !x.Name.EndsWith("TagHelper"));

我已在此处上传了代码的工作示例:https://github.com/ciaran036/coresample2

导航到Settings Page 以查看我要验证的字段。

此问题似乎也会影响视图组件。

谢谢。

【问题讨论】:

  • 你能澄清一下吗:你说“我发现如果我删除了 AbstractorValidator 并简单地向我的模型属性添加了一个 [Required] 属性,那么验证就会正常工作。”
  • 当使用 [Required] 属性时,验证对编辑器标签助手和 @Html.EditorFor 扩展方法都有效。谢谢。

标签: asp.net-core autofac fluentvalidation tag-helpers


【解决方案1】:

我认为问题出在标签助手中,因为它使用 IHtmlHelper.Editor 而不是 IHtmlHelper&lt;TModel&gt;.EditorFor 来生成 HTML 内容。它们并不完全相同。

正如您所指出的,FluentValidation 注入了您对@Html.EditorFor(x =&gt; x.SystemSettings.EmailFromAddress) 所期望的验证属性。但是对于@Html.Editor("SystemSettings.EmailFromAddress"),这是您的自定义标签助手正在做的事情,FluentValidation 不会注入验证属性。这样就排除了标签助手本身并将问题转移到Editor 调用。

我还注意到 Editor 无法解析 &lt;label asp-for(或您正在使用的其他 &lt;span asp-description-for 标签助手),因此这表明它不是 FluentValidation 特定的问题。

我无法使用自定义标签助手/EditorRequired 属性复制您的成功 - Required 属性仅在使用 EditorFor 时注入验证属性。

EditorEditorFor 的内部结构相似,但有一个关键区别,它们解决用于生成 HTML 内容的 ModelExplorer 实例的方式不同,我怀疑这就是问题所在。有关这些差异,请参见下文。

PropertyName 设置为 null 和 Metadata.Property 未设置为 Editor,但设置为 EmailFromAddressSystemSettings.EmailFromAddressEditorFor 是我们所看到的行为的潜在原因.

痛苦的部分是标签助手通过For 属性有一个有效的ModelExplorer 实例。但是没有内置规定可以将其提供给 html 帮助器。

至于分辨率,显而易见的似乎是使用EditorFor 而不是Editor,但这看起来并不容易。它可能涉及反思和构建表达式。

另一个选择是,考虑到标签助手正确解析 ModelExplorer,是扩展 HtmlHelper 并覆盖 GenerateEditor 方法 - EditorEditorFor 最终都会调用 - 这样你就可以传入ModelExplorer 并解决该问题。

public class CustomHtmlHelper : HtmlHelper, IHtmlHelper
{
    public CustomHtmlHelper(IHtmlGenerator htmlGenerator, ICompositeViewEngine viewEngine, IModelMetadataProvider metadataProvider, IViewBufferScope bufferScope, HtmlEncoder htmlEncoder, UrlEncoder urlEncoder) : base(htmlGenerator, viewEngine, metadataProvider, bufferScope, htmlEncoder, urlEncoder) { }

    public IHtmlContent CustomGenerateEditor(ModelExplorer modelExplorer, string htmlFieldName, string templateName, object additionalViewData)
    {
        return GenerateEditor(modelExplorer, htmlFieldName, templateName, additionalViewData);
    }

    protected override IHtmlContent GenerateEditor(ModelExplorer modelExplorer, string htmlFieldName, string templateName, object additionalViewData)
    {
        return base.GenerateEditor(modelExplorer, htmlFieldName, templateName, additionalViewData);
    }
}

更新您的标签助手以使用它:

public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
    if (context == null)
        throw new ArgumentNullException(nameof(context));

    if (output == null)
        throw new ArgumentNullException(nameof(output));

    if (!output.Attributes.ContainsName(nameof(Template)))
    {
        output.Attributes.Add(nameof(Template), Template);
    }

    output.SuppressOutput();

    (_htmlHelper as IViewContextAware).Contextualize(ViewContext);

    var customHtmlHelper = _htmlHelper as CustomHtmlHelper;
    var content = customHtmlHelper.CustomGenerateEditor(For.ModelExplorer, For.Metadata.DisplayName ?? For.Metadata.PropertyName, Template, null);
    output.Content.SetHtmlContent(content);

    await Task.CompletedTask;
}

最后注册新的助手,越早越好

services.AddScoped<IHtmlHelper, CustomHtmlHelper>();

Working solution

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多