EditorFor Tag Helper не отображает атрибуты проверки при использовании FluentValidator

У меня есть такая простая форма, в которой используется расширение @ 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>

У меня есть шаблон string, который отображается расширением @ Html.EditorFor:

@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="[email protected]" 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="[email protected]" 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

Перейдите на страницу настроек, чтобы увидеть поле, которое я пытаюсь проверить.

Эта проблема также влияет на компоненты представления.

Спасибо.


person Ciaran Gallagher    schedule 05.02.2020    source источник
comment
Не могли бы вы пояснить: вы говорите, что я обнаружил, что если я удалил AbstractorValidator и просто добавил атрибут [Required] к своему свойству модели, проверка тогда будет работать правильно. ‹- это при использовании вашего EditorTagHelper или в вызове @ Html.EditorFor?   -  person Jack    schedule 07.02.2020
comment
При использовании атрибута [Required] проверка работает как для вспомогательной функции тега редактора, так и для метода расширения @ Html.EditorFor. Спасибо.   -  person Ciaran Gallagher    schedule 08.02.2020


Ответы (1)


Я считаю, что проблема заключается в помощнике тега, поскольку он использует IHtmlHelper.Editor, а не IHtmlHelper<TModel>.EditorFor для генерации содержимого HTML. Они не совсем такие.

Как вы указываете, FluentValidation вводит атрибуты проверки, как и следовало ожидать от @Html.EditorFor(x => x.SystemSettings.EmailFromAddress). Однако для @Html.Editor("SystemSettings.EmailFromAddress"), что и делает ваш помощник настраиваемого тега, FluentValidation не вводит атрибуты проверки. Так что это исключает сам помощник тега и переносит проблему на вызов Editor.

Я также заметил, что Editor не разрешает <label asp-for (или другой помощник тега <span asp-description-for, который вы используете), поэтому это говорит о том, что это не проблема, специфичная для FluentValidation.

Мне не удалось воспроизвести ваш успех с атрибутом Required для вспомогательного тега пользовательского тега / Editor - атрибут Required вводил атрибуты проверки только при использовании EditorFor.

Внутреннее устройство для Editor и EditorFor похоже, но с одним ключевым отличием: способ разрешения экземпляра ModelExplorer, используемого для генерации содержимого HTML, отличается, и я подозреваю, что это проблема. Эти различия см. Ниже.

Редактор против Редактора для ModelExplorer

Такие вещи, как PropertyName, установленный на ноль и Metadata.Property, не установленный для Editor, но установленный на EmailFromAddress и SystemSettings.EmailFromAddress для EditorFor, выделяются как потенциальные причины наблюдаемого поведения.

Проблема в том, что у помощника тега есть действительный экземпляр ModelExplorer через свойство For. Но нет встроенного средства для предоставления его помощнику html.

Что касается разрешения, очевидным кажется использование EditorFor, а не Editor, однако это выглядит непросто. Скорее всего, это потребует размышлений и создания выражения.

Другой вариант, учитывая, что помощник по тегу правильно разрешает ModelExplorer, - это расширить HtmlHelper и переопределить метод GenerateEditor - который в конечном итоге вызывают и Editor, и EditorFor, чтобы вы могли передать 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>();

Рабочее решение

person rgvlee    schedule 29.02.2020