Select select 元素中带有标签助手的选项

Select an option in a select element with tag helpers

这是我的情况:在 ASP.NET 核心应用程序中,我需要在模型实例的 Razor 视图中生成以下 HTML 代码:

<select name="CultureName">
    <option value="de-DE" data-summary="Deutsch (Deutschland)">* Deutsch (Deutschland)</option>
    <option value="de-AT" data-summary="Deutsch (Österreich)">* Deutsch (Österreich)</option>
    <option value="de-CH" data-summary="Deutsch (Schweiz)">* Deutsch (Schweiz)</option>
    <option value="en-GB" data-summary="Englisch (Großbritannien)" selected>Englisch (Großbritannien)</option>
                                                                   ^^^^^^^^- important!
    <option value="en-US" data-summary="Englisch (USA)">Englisch (USA)</option>
</select>

模型的CultureName属性的值为"en-GB"。该模型还有一个所有可用选项的列表。我使用的 Web 前端框架支持单独的 data-summary 属性,需要在每个 <option> 元素上设置,以及为简洁起见此处省略的其他属性。 selected 属性预 select 选项之一。

我无法使用预定义的 SelectListItem class,因为它不支持附加属性。所以我创建了自己的 SelectListOption class ,它看起来很相似但有额外的属性。我有一个标签助手,可以让我这样写:

<select asp-for="CultureName">
    @foreach (var culture in Model.Cultures)
    {
        <option item="@culture"></option>
    }
</select>

代码如下:

[HtmlTargetElement("option", ParentTag = "select", Attributes = "item")]
public class OptionTagHelper : TagHelper
{
    public SelectListOption Item { get; set; }

    public override void Process(TagHelperContext context, TagHelperOutput output)
    {
        if (Item != null)
        {
            output.Content.SetContent(Item.Text);
            output.Attributes.SetAttribute("value", Item.Value);
            if (Item.Summary != null)
                output.Attributes.SetAttribute("data-summary", Item.Summary);
            if (Item.HtmlText != null)
                output.Attributes.SetAttribute("data-html", Item.HtmlText);
            if (Item.HtmlSummary != null)
                output.Attributes.SetAttribute("data-summary-html", Item.HtmlSummary);
            if (Item.Selected)
                output.Attributes.SetAttribute("selected", true);
            if (Item.Disabled)
                output.Attributes.SetAttribute("disabled", true);
            output.Attributes.RemoveAll("item");
        }
    }
}

它填充模型中 <option> 元素的其他属性。这很好用,但是 selection 在渲染页面时被忽略了。因此用户将看到一个包含所有选项的列表,但是 当前 selection 丢失了.

我发现我做的不一定正确,我应该这样做:

<select asp-for="CultureName" asp-items="@Model.Cultures">
</select>

预定义的 select 标签助手然后会为我填写选项。但同样,我不能使用它,因为我需要额外的标准属性。所以我也试着自己做这个。

这是代码,但我真的不知道我在做什么,因为没有太多关于它的文档:

[HtmlTargetElement("select", Attributes = "options")]
public class SelectTagHelper : TagHelper
{
    public IEnumerable<SelectListOption> Options { get; set; }

    public override void Process(TagHelperContext context, TagHelperOutput output)
    {
        if (Options != null)
        {
            foreach (var option in Options)
            {
                // TODO: Use HtmlEncoder.HtmlEncode() on values?
                output.Content.AppendHtml($"<option value=\"{option.Value}\"");
                if (option.Summary != null)
                    output.Content.AppendHtml($" data-summary=\"{option.Summary}\"");
                if (option.HtmlText != null)
                    output.Content.AppendHtml($" data-html=\"{option.HtmlText}\"");
                if (option.HtmlSummary != null)
                    output.Content.AppendHtml($" data-summary-html=\"{option.HtmlSummary}\"");
                if (option.Selected)
                    output.Content.AppendHtml(" selected");
                if (option.Disabled)
                    output.Content.AppendHtml(" disabled");
                output.Content.AppendHtml($">{option.Text}</option>");
            }
        }
    }
}

我将其用作:

<select asp-for="CultureName" options="@Model.Cultures">
</select>

这似乎可行,我在 select 框中获得了所有选项。但是当然现在仍然没有最初的selection

现在我卡住了。如何将 selected 属性获取到正确的 <option> 元素?如何找出 <select> 元素的当前模型值是多少?它有一个 asp-for 属性,但这只是名称,而不是我需要匹配的值。

我正在寻找这样的东西:

    public override void Process(TagHelperContext context, TagHelperOutput output)
    {
        if (Options != null)
        {
            foreach (var option in Options)
            {
                output.Content.AppendHtml($"<option value=\"{option.Value}\"");
                if (option.Value == CURRENT_SELECT_VALUE_FROM_MODEL)
                    output.Content.AppendHtml(" selected");
                // (Other attributes)
                output.Content.AppendHtml($">{option.Text}</option>");
            }
        }
    }

接下来我还有一个要求:在某些地方我需要向所有 <option> 元素添加另一个特定于页面的属性,这些元素将用于表单的其他部分:

<select asp-for="CultureName">
    @foreach (var culture in Model.Cultures)
    {
        <option item="@culture" data-separator="@culture.ValueObject.TextInfo.ListSeparator"></option>
    }
</select>

这使得每个 CultureInfo 的 TextInfo.ListSeparator 值通过 data-separator 属性可用于页面脚本。这显然不能再由 default case 处理,所以我仍然需要自己在循环中创建 <option> 元素。幸运的是,我的第一个标签助手已经涵盖了这一点,因此所有额外的属性都已经存在。但是同样,缺少 selected 属性 .

虽然我可以尝试修改模型中的选项列表,以便其中之一具有 Selected 属性 集,但这是很多愚蠢的工作,应该已经涵盖了像 asp-for.

在这里,我还需要从每个选项标签助手中找出特定选项是否应该 selected。

或者,select 标签助手(父级)可以在其内容被处理后找到 select 的选项元素。因此,如果可能的话,我将不得不触发结束 </select> 标记并迭代所有 <option> 子元素以匹配它们的 value 属性并向其中之一添加 selected 属性。

标签助手甚至可以帮我解决这个问题,还是说他们对这项任务来说太有限了?这些帮手真的有多大帮助?

您没有对 asp-for 做任何事情。这不是魔术。如果您查看其中一个内置标签助手的代码,您会看到它们有 属性 用于捕获此代码,如下所示:

[HtmlAttributeName("asp-for")]
public ModelExpression For { get; set; }

然后,您必须使用此 属性 通过读取值来实际设置所选选项。尽管如此,您可能会通过简单地从 SelectTagHelper 继承标签助手并覆盖 Process 来执行您自己的逻辑来获得更好的服务。不过,请记住也要调用 base.Process