Search code examples
c#asp.net-corerazorasp.net-core-tag-helpers

Tag Helper - Add all Attributes from a Collection


I have the following tag helper (this is the equivalent of saying Html.Editor):

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

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

    public ModelExpression For { get; set; }

    public IDictionary<string, string> HtmlAttributes { get; set; } = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

    public string TemplateName { get; set; }

    public IDictionary<string, object> ViewData { get; set; } = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);

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

    public override void Process(TagHelperContext context, TagHelperOutput output) {
        ((IViewContextAware)_htmlHelper).Contextualize(ViewContext);

        ViewData.Add("HtmlAttributes", HtmlAttributes);

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

        output.TagName = null;
    }
}

Which is called like so:

<editor for="Name" view-data-test="@("Foo")" html-attributes-class="Bar" />

Here's the code for the String view template:

@model string
<input asp-for="@Model" class="form-control" />
@ViewData["Test"]
@(((IDictionary<string, string>)ViewData["HtmlAttributes"])["Class"])

This works fine but ideally I'd like to add the HtmlAttributes dictionary as attributes to the input tag helper above. Previously I would have said the following to pass the attributes to a HTML helper:

Html.TextBoxFor(m => m, new { htmlAttributes = ViewData["HtmlAttributes"] })

But what is the equivalent to pass them to a tag helper?


Solution

  • To create a tag helper that can work with your Dictionary you'd need to

    • supply an attribute on the tag helper class that takes the HTML attributes dictionary
    • deconstruct and loop through that dictionary and add it to the output

    Start by specifying the html-attributes as a HTML attribute to the HtmlTargetElement's comma-separated Attributes list.

    [HtmlTargetElement("editor", Attributes = "for, html-attributes", TagStructure = TagStructure.WithoutEndTag)]
    public class EditorTagHelper : TagHelper {
    

    Then map those to a property on the tag helper itself:

    [HtmlAttributeName("html-attributes")]
    public Dictionary<string, string> HtmlAttributes { get; set; }
    

    In the process method, deconstruct and foreach through that dictionary

    foreach (var (key, value) in HtmlAttributes)
    {
        output.Attributes.SetAttribute(key, value);
    }
    

    Use it like:

    <editor for="Name" view-data-test="@("Foo")" html-attributes="@ViewData["HtmlAttributes"]" />
    

    Edit:

    If you want to only pass the ViewData to the template and then apply them to the input element inside you'd need to follow the same procedure as I told you. But you skip applying the htmlAttributes to the Editor element.

        [HtmlTargetElement("input", Attributes = "for, html-attributes")]
        public class InputTagHelper : TagHelper
        {
            public ModelExpression For { get; set; }
    
            [HtmlAttributeName("html-attributes")]
            public Dictionary<string, string> HtmlAttributes { get; set; }
    
            public override void Process(TagHelperContext context,
                TagHelperOutput output)
            {
                output.TagMode = TagMode.SelfClosing;
                output.Attributes.SetAttribute("name", EditorFor.Name);
                foreach (var (key, value) in HtmlAttributes)
                {
                    output.Attributes.SetAttribute(key, value);
                }
            }
    

    Then in your template, you can do:

    @model string
    <input asp-for="@Model" html-attributes="@((IDictionary<string, string>)ViewData["HtmlAttributes"])" />