EditorFor Tag Helper не отображает атрибуты проверки при использовании FluentValidator - PullRequest
6 голосов
/ 05 февраля 2020

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

@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 что-то не так. Возможно, есть проблема конфигурации.

Я использую Autofa c внедрение зависимостей для сканирования моих сборок и регистрации типов валидаторов:

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))});
})

Это также, похоже, было хорошо.

Одна вещь, на которую стоит обратить внимание, это то, что у меня была более ранняя проблема, заключающаяся в том, что использование сканирования сборок Autofa c разрушало приложение, когда в него были добавлены помощники тегов. Я добавил фильтр, чтобы гарантировать, что помощники по тегам не включаются при регистрации этих зависимостей, например:

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

Я загрузил рабочий пример кода здесь: https://github.com/ciaran036/coresample2

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

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

Спасибо.

1 Ответ

0 голосов
/ 29 февраля 2020

Я полагаю, что проблема заключается в помощнике тегов, который использует 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 c.

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

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

Editor vs EditorFor 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>();

рабочий раствор

...