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

How to create custom tag helper containing other tag helpers


public class FormControlTextBoxHelper:TagHelper
{

    public override void Process(TagHelperContext context, TagHelperOutput output)
    {
        using (var writer = new StringWriter())
        {
            writer.Write(@"<div class=""form-group"">");

            //Here I want to write <label asp-for="InputModel.Title" class="control-label"></label> and other tag helpers

            writer.Write(@"</div>");

            output.Content.SetHtmlContent(writer.ToString());

        }

    }
}

I could not found for 1-2 hours any information how to have custom tag helper which is containing other tag helpers. The Microsoft docs show really simple examples which doesn't contain a lot of meaningful information.


Solution

  • I figure it out after reading multiple different articles which had part of the answer.

    So first you need to have inject in the constructor of the tag helper IHtmlGenerator, which will generate the tag helper html, also generation of the elements requires ViewContext, which you can have only have as a property with Attribute ViewContext.

    This is the summary of ViewContext attribute:

    Summary:

    Specifies that a tag helper property should be set with the current Microsoft.AspNetCore.Mvc.Rendering.ViewContext when creating the tag helper. The property must have a public set method.

    The code look likes this:

    [HtmlTargetElement("form-control-text-box")]
    public class FormControlTextBoxHelper:TagHelper
    {
        [HtmlAttributeName("asp-for")]
        public ModelExpression For { get; set; }
    
        private readonly IHtmlGenerator _generator;
    
        [ViewContext]
        public ViewContext ViewContext { get; set; }
    
        public FormControlTextBoxHelper(IHtmlGenerator generator)
        {
            _generator = generator;
        }
    
        public override void Process(TagHelperContext context, TagHelperOutput output)
        {
            using (var writer = new StringWriter())
            {
                writer.Write(@"<div class=""form-group"">");
    
                var label = _generator.GenerateLabel(
                                ViewContext, 
                                For.ModelExplorer, 
                                For.Name, null, 
                                new { @class = "control-label" });
    
                label.WriteTo(writer, NullHtmlEncoder.Default);
    
                var textArea = _generator.GenerateTextBox(ViewContext, 
                                    For.ModelExplorer, 
                                    For.Name, 
                                    For.Model, 
                                    null, 
                                    new { @class = "form-control" });
    
                textArea.WriteTo(writer, NullHtmlEncoder.Default);
    
                var validationMsg = _generator.GenerateValidationMessage(           
                                        ViewContext, 
                                        For.ModelExplorer, 
                                        For.Name, 
                                        null, 
                                        ViewContext.ValidationMessageElement, 
                                        new { @class = "text-danger" });
    
                validationMsg.WriteTo(writer, NullHtmlEncoder.Default);
    
                writer.Write(@"</div>");
    
                output.Content.SetHtmlContent(writer.ToString());
    
            }
    
        }
    }
    

    So this tag helper called like <form-control-text-box asp-for="InputModel.Title"></form-control-text-box> will produce the html which is equal to:

    <div class="form-group">
        <label asp-for="InputModel.Title" class="control-label"></label>
        <input asp-for="InputModel.Title" class="form-control" />
        <span asp-validation-for="InputModel.Title" class="text-danger"></span>
    </div>
    

    I hope I help someone to figure it out faster.