Cleaner Conditional HTML Attributes In Razor Web Pages

4.77 (13 votes)

Every so often, you will want to conditionally render HTML attributes or their values within your Razor Web Pages based on the outcome of some runtime logic. Often, the logic required to manage this can become messy and lead to unnecessary spaghetti code. This article explores a few common scenarios and provides some solutions, as well as introducing a nice new feature that was released as part of Razor v 2.0.

The first scenario involves dynamically applying one value or another to an attribute based on a condition. A typical example would be when you want to apply an alternating style to items in a list. Various ASP.NET Grid controls offer this functionality built-in with AlternatingItem templates or in the case of the WebGrid, an alternatingRowStyle parameter that takes a CSS class name as a value. But if you simply want to display a list of items without the aid of a control, you have to manage this yourself.

Most solutions that you see make use of a counter of some kind when attempting to calculate which style to apply. As each item is rendered, the counter is incremented by one and then tested to see if it is odd or even using an if... else construct. The basis of this approach is sound, although there is a slightly nicer way to establish the relative position of an item in the list rather than having to maintain a counter, and that is to use its index. Here is some sample code that obtains a list of employees from the Northwind database and then renders them to the browser:

@{
    var db = Database.Open("Northwind");
    var data = db.Query("SELECT TitleOfCourtesy, FirstName, LastName FROM Employees");
    var people = data.Select((person, index) => new {Index = index, Person = person});
}

<!DOCTYPE html>

<html lang="en">
    <head>
        <meta charset="utf-8" />
        <title></title>
        <style>
            #content { width: 400px; font: 0.8em Arial; }
            .even { background-color:  #dcf1b8; }
            .odd { background-color: #f4ffe1; }
        </style>
    </head>
    <body>
        <div id="content">
        @foreach(var record in people){
            <div class="@(record.Index % 2 == 0 ? "even" : "odd")">
                @record.Person.TitleOfCourtesy @record.Person.FirstName @record.Person.LastName
            </div>
        }
        </div>
    </body>
</html>

The first two lines of code are common enough. Data is retrieved from the database using a simple SELECT statement. The last line in the code block uses an overload of the Enumerable.Select extension method that projects each element in the sequence (the data from the database) into a new form while incorporating the element's index. The "new form" is a sequence of anonymous types that each have an Index property which is assigned the value if the item's (zero-based) index in the original sequence, and a Person property which is assigned the value of the item itself (a DynamicRecord object).

You can see a couple of CSS styles declared in the head of the document, and then the people variable is iterated over. The C# conditional operator (also known as the Ternary operator) is used as a shorthand for if... else. It tests to see if the current item's index value, when divided by 2 leaves a remainder or not. If there is no remainder, the value "even" is applied to the div's class attribute. Otherwise the value "odd" is applied, resulting in alternating CSS styles:

The next examples take advantage of a feature which was added to Razor v 2.0. The feature is called Conditional Attributes, and it allows you to decide whether to render the attribute at all, let alone a value for it. The first example shows how this feature is can be used to determine whether to render an attribute or not. The scenario features a navigation, where the link to the current page has a CSS class applied to it to make it look different to the other links in the navigation. Here is the code for the Layout page:

@{
    
}

<!DOCTYPE html>

<html lang="en">
    <head>
        <meta charset="utf-8" />
        <title></title>
        <style>
            body { font-family: Arial; font-size: 0.8em; }
            nav ul { list-style-type: none; margin: 0; padding: 0; }
            nav li { float: left; }
            nav li a { display: block; padding-right: 15px; color: #0026ff; text-decoration:  underline; }
            nav li a.current { text-decoration:  none; color: black; }
            .clear { clear: both; }
            #content{ width:  300px; font-family: "Times New Roman"; }
            .cap { font-size: 4.2em; float: left; color: #a01820; padding-right: 7px; }
        </style>
    </head>
    <body>
        <nav>
            <ul>
                <li><a href="~/Page1" class="@IsCurrentPage("Page1")">Page 1</a></li>
                <li><a href="~/Page2" class="@IsCurrentPage("Page2")">Page 2</a></li>
                <li><a href="~/Page3" class="@IsCurrentPage("Page3")">Page 3</a></li>
            </ul>
        </nav>
        <div class="clear"></div>
        @RenderBody()
    </body>
</html>
@functions {
    public static string IsCurrentPage(string page){
        return HttpContext.Current.Request.Url.ToString().Contains(page) ? "current" : null;
    }
}

A default style has been applied to all hyperlinks in the nav element, which ensures that they appear in blue and underlined. Another style has been declared for a link with the class attribute of "current" which results in the link appearing in black font with no underline. There's a little bit of CSS floaty stuff there too, so that the list of links appear horizontally instead of vertically. The value for the class attribute of each link is derived from a function called IsCurrentPage. The function takes a string representing the name of the page and returns "current" if that page name is found within the current URL. If it is not found, the function returns null, which is important as you will see.

Here is an image showing Page1.cshtml:

And here is the source for the <nav> element:

Notice that "current" has been applied to the class attribute for the first link. The class attribute is not rendered at all for the other two links where the value returned from the method was null. If the value had been (almost) anything else, including an empty string, the class attributes would have been rendered with that value. In the case of an empty string, it would have appeared as class="". Did you spot the "almost" there? There is another pair of values that Razor's Conditional Attributes feature responds to in a special way, and that is true and false.

This example features a dropdown list, which is used to display the selected product's category:

@{
    var db = Database.Open("Northwind");
    var id = UrlData[0].IsEmpty() ? 1 : UrlData[0].AsInt();
    var product = db.QuerySingle("SELECT ProductName, CategoryId from Products WHERE ProductId = @0", id);
    var categories = db.Query("SELECT CategoryId, CategoryName FROM Categories");
}

<!DOCTYPE html>

<html lang="en">
    <head>
        <meta charset="utf-8" />
        <title></title>
    </head>
    <body>
        <div>
            @product.ProductName
            <select name="CategoryId">
                @foreach(var category in categories){
                    <option value="@category.CategoryId" selected="@Selected(product.CategoryId, category.CategoryId)">
                        @category.CategoryName
                     </option>
                }
            </select>
        </div>
    </body>
</html>
@functions {
    public static bool Selected(int a, int b){
        return a == b;
    }
}
In this case, the value of the selected attribute in each option element has been set to the output of a function which returns a bool. The function simply compares the current category's ID with that of the selected product. If they match, the function returns true. Otherwise it returns false. If the value applied to the attribute is true, the result is that the attribute is repeated, in this case resulting in selected="selected". And that is perfect for options in a dropdown list. If the value equates to false, nothing is rendered just as in the previous example when null was applied to the value of an attribute. Here is the source for the rendered dropdown list when a product in the Beverages category is displayed.

 

There are other attributes, which when the attribute name is repeated have special meaning. Two of them are checked, and disabled. Here is an example where both are applied through the same variable:

@{
    var disabled = Request["toggle"] == "on";
}

<!DOCTYPE html>

<html lang="en">
    <head>
        <meta charset="utf-8" />
        <title></title>
    </head>
    <body>
        <form method="post">
            <input type="checkbox" name="toggle" checked="@disabled" />
            <input type="text" name="text" disabled="@disabled" />
            <input type="submit" />
        </form>
    </body>
</html>
This very simple example renders a checkbox and a textbox. If the checkbox is ticked, the default value for checkboxes "on" is passed in the Request.Form collection. The single line of code at the top of the page applies the value true to the disabled variable in that case. And here is the source of the form when that happens. As you can see, both attributes have been repeated as desired:

 

Finally, class attributes can have multiple values separated by spaces.

@{
    Layout = "~/_Layout.cshtml";
    string red = null;
    string black = "red";
}
<div id="content" class="@red @black">
    <span class="cap">A</span>liquam mi turpis, rutrum vitae sagittis ut, fermentum at 
    massa. Mauris a elit libero, non tincidunt nunc. Donec ac sem eros. Suspendisse 
    tincidunt sollicitudin adipiscing. Nullam cursus iaculis purus, vel iaculis urna 
    porttitor id. Aenean et lorem vel est tristique luctus. Morbi pharetra justo non 
    urna aliquet ut sollicitudin dolor consectetur. Curabitur tincidunt eleifend erat, 
    et pharetra nisi sagittis vel. Vivamus quis fermentum purus. Proin tellus diam, 
    accumsan sit amet feugiat vel, blandit sed turpis.
</div>

If the variable used to set any of them resolves to null, not only is it not rendered, but any remaining whitespace is collapsed:

Conditional attributes provide a nice addition to Razor allowing the Web Pages developer to manage scenarios that can result in messy spaghetti code in a much more succinct manner. In addition to this, you can use the conditional operator to keep if... else tests to one clean inline expression, reducing code smell.

All of the scenarios and sample code are available in the GitHub Repo that accompanies this article.

 

You might also like...

Date Posted:
Last Updated:
Posted by:
Total Views to date: 87440

7 Comments

- knox

Great introduction to conditional attributes. Can't wait to use them.

- Tonman

Nice article
Thanks.

- lymber

These conditional attributes do not work in a partial view with custom attributes such as hello-test. So following does not work and the attribute is generated as hello-test="" instead of being omitted.

string path = null;

if (Model.TitleViewModel.OrderViewModel.IsPdf)
{
path = "hello";
}

<div hello-test="@path">test</div>

- John Avis

Thanks for the article. I didn't know of this feature before.

- Joe M.

Nice article - thanks! Unfortunately the attributes change is a breaking change for existing code. Now it appears that any Boolean value rendered as razor into an attribute will no longer render a string representation of the Boolean, but will re-render the attribute name.

So if I have code that was previously:
<div MyCustomAttr="@Model.MyBoolValue"></div>
the rendered attr value was "True"

Now, the rendered attr value is "MyCustomAttr". This breaks any javascript that attempts to use that attr's value.

A bit yucky... not sure its Razor's business to assume that is the correct value for my custom attribute just because I used a Boolean type to populate it. I can fix it by casting to string... if I can find all the places.

- Ignas

Any suggestions for Html Helper elements with HtmlAttributes, when you need to conditionally set disabled/readonly/checked it doesn't help, as readonly="" will still set element as read only.

- Mike

@Ignas

Those attributes are applied to the element simply by being present - regardless of the value you assign to them. If you don't want a field to be set to readonly, you need to ensure that the attribute is not rendered at all. So you need to make sure that the expression you apply to it resolves to 'false'.

Recent Comments

Pam 30/08/2017 11:30
In response to Sending Email in Razor Pages
Mike, RazorPages sound like a nice choice for somebody still working in ASP classic who wants to to...

Robby Robson 15/08/2017 00:43
In response to Routing in Razor Pages
Mike: great stuff. Now that .Core Standard 2.0 is formally out, how soon will you rewrite your book...

Satyabrata Mohapatra 28/07/2017 08:59
In response to Sending Email in Razor Pages
Bit off topic, but congratulation sir for your MVP award. You deserve it !!!...

Satyabrata Mohapatra 23/07/2017 16:43
In response to Razor Pages - The Elevator Pitch
@Dale Severin You can continue to build apps using asp.net web pages....

Satyabrata Mohapatra 23/07/2017 16:40
In response to Sending Email in Razor Pages
Thanks for sharing...learned a lot...

Gfw 22/07/2017 11:53
In response to Sending Email in Razor Pages
Question... Does System.Net.Mail support SSL?...

Dale Severin 20/07/2017 03:38
In response to Razor Pages - The Elevator Pitch
I work with razor web pages extensively. I appreciate the rapid development it permits me to I am as...

Obinna Okafor 14/07/2017 01:19
In response to Routing in Razor Pages
Thank you, Mike. Good post....

Satyabrata Mohapatra 11/07/2017 16:02
In response to Routing in Razor Pages
Very powerful routing system!!...

Cyrus 05/07/2017 03:41
In response to Razor Pages - Getting Started With The Preview
How can I trim packages and services as much as possible to use just razor pages? I don’t want to to...