Search code examples
asp.net-mvcasp.net-core-mvctag-helpersasp.net-core-tag-helpers

Custom .NET Core Tag Helper not working


I created a new ASP.NET Core web application project, now for simplicity let's say its root namespace is MyWeb.Mvc which is also the assembly name specified in the project properties.

In the same web application project I created a folder titled TagHelpers and in it I added a class like this:

namespace MyWeb.Mvc.TagHelpers {
   public abstract class JsonLdBase : TagHelper
   {
      protected string addKeyValue(string key, string value, bool noClosing = true)
      {
         return string.Format("\"{0}\": \"{1}\"{2}{3}", key, value, noClosing ? "," : "", Environment.NewLine);
      }
    }

    public class JsonLdWebsiteTagHelper : JsonLdBase
    {
      public string Name { get; set; }
      public string Alt { get; set; }
      public string Url { get; set; }

      public override void Process(TagHelperContext context, TagHelperOutput output) {
          output.TagName = "script";
          output.TagMode = TagMode.StartTagAndEndTag;
          output.Attributes.SetAttribute("type", new HtmlString("application/ld+json"));
          output.Content.SetContent(
              "{" + Environment.NewLine +
              addKeyValue("@context", "http://schema.org") + 
              addKeyValue("@type", "WebSite") +
              addKeyValue("name", Name) + 
                addKeyValue("alternateName", Alt) +
                addKeyValue("url", Url) +
            "}"
          );
      }
    }

}

Then in the _ViewImports.cshtml I have this:

@using MyWeb.Mvc.TagHelpers
@addTagHelper MyWeb.Mvc.TagHelpers.*, MyWeb.Mvc

And in the _Layout.cshtml I use it like this:

<jsonldwebsite name="Panama Vibes" alt="Panama Vibes" url="PanamaVibes.com"/>

Given it was placed in _Layout.cshtml it should appear in all pages. But when I compile and run the web application and view the source of the main page I see the custom tag has been rendered as a normal HTML tag without TagHelper processing.

Now, originally I was thinking of using the json-ld NuGet package but I desisted when I saw it was not updated since 2016 and that the programmer had not taken the time to document how it can be used or given any examples whatsoever.

UPDATE #1 As suggested I explicitely set the TagMode property. However the whole output becomes HTML encoded and therefore does not pass the JSON+LD validation.

I now use HtmlString("application/json+ld") and the type entry is now properly rendered without encoding of the "+" sign. However, I tried using the same approach for the Content but there


Solution

  • By default, the name of the tag in the razor page is json-ld-website:

    <json-ld-website name="Panama Vibes" alt="Panama Vibes" url="PanamaVibes.com" />
    

    You can customize the name by using the HtmlTargetElementAttribute:

    [HtmlTargetElement("jsonldwebsite")]
    public class JsonLdWebsiteTagHelper : JsonLdBase
    {
        public string Name { get; set; }
        public string Alt { get; set; }
        public string Url { get; set; }
    
        public override void Process(TagHelperContext context, TagHelperOutput output)
        {
            output.TagName = "script";
            output.TagMode = TagMode.StartTagAndEndTag;
            output.Attributes.SetAttribute("type", "application/ld+json");
            output.Content.SetContent(
                "{" + Environment.NewLine +
                addKeyValue("@context", "http://schema.org") +
                addKeyValue("@type", "WebSite") +
                addKeyValue("name", Name) +
                  addKeyValue("alternateName", Alt) +
                  addKeyValue("url", Url) +
              "}"
            );
        }
    }
    

    Also, you should are changing the name of the tag to script, which is not a self closed tag. This means you should use output.TagMode = TagMode.StartTagAndEndTag; to ensure there is a close tag. This way your sample will generate the following html:

    <script type="application/ld&#x2B;json">{&#xD;&#xA;&quot;@context&quot;: &quot;http://schema.org&quot;,&#xD;&#xA;&quot;@type&quot;: &quot;WebSite&quot;,&#xD;&#xA;&quot;name&quot;: &quot;Panama Vibes&quot;,&#xD;&#xA;&quot;alternateName&quot;: &quot;Panama Vibes&quot;,&#xD;&#xA;&quot;url&quot;: &quot;PanamaVibes.com&quot;,&#xD;&#xA;}</script>
    

    EDIT

    All values are encoded by default (for security reasons). You can write Raw html using the SetHtmlContent method. For attribute values, you'll have to provide a custom IHtmlContent which doesn't encode the value. However, any valid html parser should unencode the attribute value.

    public class JsonLdWebsiteTagHelper : JsonLdBase
    {
        public string Name { get; set; }
        public string Alt { get; set; }
        public string Url { get; set; }
    
        public override void Process(TagHelperContext context, TagHelperOutput output)
        {
            output.TagName = "script";
            output.TagMode = TagMode.StartTagAndEndTag;
            output.Attributes.Add(new TagHelperAttribute("type", new HtmlContent("application/ld+json")));
            output.Content.SetHtmlContent(
                "{" + Environment.NewLine +
                addKeyValue("@context", "http://schema.org") +
                addKeyValue("@type", "WebSite") +
                addKeyValue("name", Name) +
                  addKeyValue("alternateName", Alt) +
                  addKeyValue("url", Url) +
              "}"
            );
        }
    
        private class HtmlContent : IHtmlContent
        {
            private string _value;
            public HtmlContent(string value)
            {
                _value = value;
            }
            public void WriteTo(TextWriter writer, HtmlEncoder encoder)
            {
                writer.Write(_value);
            }
        }
    }