22

In an ASP.NET Core View I have the following:

<div class="message" message=""></div>

And I the a Tag Helper with the following TagHelper:

[HtmlTargetElement("div", Attributes = MessageName)]
public class MessageTagHelper : TagHelper {

  private const String MessageName = "message";

  [HtmlAttributeName(MessageName)]
  public String Message { get; set; }

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


  public override void Process(TagHelperContext context, TagHelperOutput output) {

    String message;
    Boolean defined = ViewContext.HttpContext.Request.Cookies.TryGetValue("_message", out message);

    if (defined) {
      ViewContext.HttpContext.Response.Cookies.Delete("_message");
      output.Attributes.Add("class", "active");
      output.Content.Append(message);
    }
  }
}

On the following code line I need to add "active" as CSS Class.

output.Attributes.Add("class", "active");

However, this does nothing. Message remains only with class "message".

What am I missing?

S.Serpooshan
  • 7,608
  • 4
  • 33
  • 61
Miguel Moura
  • 36,732
  • 85
  • 259
  • 481

4 Answers4

43

As of ASP.NET Core 2.1 you can use the TagHelperOutputExtensions class provided by Microsoft to add your "active" CSS class in one line of code.

using Microsoft.AspNetCore.Mvc.TagHelpers;
using System.Text.Encodings.Web;
...
public override void Process(TagHelperContext context, TagHelperOutput output)
{
    ...
    output.AddClass("active", HtmlEncoder.Default);
    ...
}
Mathew Leger
  • 1,613
  • 17
  • 22
7

@Shyju's answer is mostly correct, but there's a lot of extraneous code that's unnecessary. The following is enough to handle it in ASP.NET Core 2.0:

var classes = output.Attributes.FirstOrDefault(a => a.Name == "class")?.Value;
output.Attributes.SetAttribute("class", $"active {classes}");
Chris Pratt
  • 232,153
  • 36
  • 385
  • 444
  • good! but this doesn't check for duplicate values if the class already contains new values to add. So, I post a more complete version [here](https://stackoverflow.com/a/51805520/2803565) – S.Serpooshan Aug 12 '18 at 03:52
2

I think you should remove the existing TagHelperAttribute for css class from the Attributes collection and add a new one which has all the classes (Existing and your new "active" class)

The below code should work.

var existingClass = output.Attributes.FirstOrDefault(f => f.Name == "class");
var cssClass = string.Empty;
if (existingClass != null)
{
    cssClass = existingClass.Value.ToString();       
}
cssClass = cssClass + " active";
var ta = new TagHelperAttribute("class", cssClass);
output.Attributes.Remove(existingClass);
output.Attributes.Add(ta);
Shyju
  • 214,206
  • 104
  • 411
  • 497
  • 1
    FYI `output.Attributes.Remove(existingClass);` should be located inside the `if` statement, otherwise it's trying to remove a null value. – Josh Schultz Jul 07 '17 at 19:17
  • **existingClass.Value.ToString()** won't work because **existingClass.Value** is of type **Microsoft.AspNetCore.Mvc.TagHelpers.TagHelperOutputExtensions.ClassAttributeHtmlContent** class. – Serg Oct 15 '17 at 08:24
  • To be precise it will work if there is **only one** class name. But not for 2 or more, for example **
    ** won't return classes in ToString().
    – Serg Oct 15 '17 at 08:37
  • My example above with **** is not correct. Actually method **.ToString()** works fine if there were no tag-helpers which added some extra classes via **output. Attributes.Add("class", ...)** before our tag-helper is invoked. If they did then method **.ToString()** won't return expected names of css classes. It will return "ClassAttributeHtml‌​Content" C# class name instead in ASP.NET Core 2. – Serg Oct 15 '17 at 13:25
  • yea. There was no built in support for multiple classes. There was a new extension method added (`AddClasses`) to handle this. I will update the answer with that when i play around with it later. – Shyju Oct 15 '17 at 14:10
  • 2
    Boy oh boy is this UGLY that we have to do it this way. I will be writing an extension method to greatly alleviate this pain, but I wonder why the team can't see the Need for Simplicity® here. – Nicholas Petersen Nov 22 '17 at 00:29
2

The other answers here does not check current css class to avoid duplicate values. Here, I wrote an extension to add a css class with proper checks to ensure clean html at output:

public static class GeneralExtionsions
{
    //the white space chars valid as separators between every two css class names
    static readonly char[] spaceChars = new char[] { ' ', '\t', '\r', '\n', '\f' };

    /// <summary> Adds or updates the specified css class to list of classes of this TagHelperOutput.</summary>
    public static void AddCssClass(this TagHelperOutput output, string newClass)
    {
        //get current class value:
        string curClass = output.Attributes["class"]?.Value?.ToString(); //output.Attributes.FirstOrDefault(a => a.Name == "class")?.Value?.ToString();

        //check if newClass is null or equal to current class, nothing to do
        if (string.IsNullOrWhiteSpace(newClass) || string.Equals(curClass, newClass, StringComparison.OrdinalIgnoreCase))
        {
            return;
        }

        //append newClass to end of curClass if curClass is not null and does not already contain newClass:
        if (!string.IsNullOrWhiteSpace(curClass) 
            && curClass.Split(spaceChars, StringSplitOptions.RemoveEmptyEntries).Contains(newClass, StringComparer.OrdinalIgnoreCase)
            )
        {
            newClass = $"{curClass} {newClass}";
        }

        //set new css class value:
        output.Attributes.SetAttribute("class", newClass);

    }

}
S.Serpooshan
  • 7,608
  • 4
  • 33
  • 61