Razor Syntax and Tag Helpers

Mix C# into HTML with Razor's @ expressions, code blocks, if and foreach, and let tag helpers such as asp-action and asp-route-id write your links.

Journeyman · ASP.NET Core · Razor · C# · Updated

After this lesson you can

  • Mix C# into HTML with Razor's @ expressions, @{ } blocks, @if and @foreach
  • Build links with tag helpers such as asp-controller, asp-action and asp-route-id

Key takeaways

  • @ shows a value, and a calculation needs parentheses: @(Model.UnitPrice * 2).
  • @{ } runs C# without showing anything; @if and @foreach work like C#'s, with HTML inside their braces.
  • Razor HTML-encodes what @ shows, so HTML a user typed appears as text instead of running.
  • Build links with tag helpers such as asp-controller, asp-action and asp-route-id, not hand-typed addresses, so they follow the route.

Razor is the syntax of .cshtml files. A view is mostly HTML, and the @ sign switches to C# wherever you need it. Razor works out where the C# ends by itself, so there's little to type.

All the examples on this page are from one view whose model is Northwind product 8, Northwoods Cranberry Sauce: $40.00, with 6 in stock.

@ shows a value

@ followed by a name shows that value in the page:

<p>@Model.ProductName</p>
<p>Northwoods Cranberry Sauce</p>

Razor reads the C# up to the first character that can't be part of a name, such as a space or <. So a calculation needs parentheses, @( ), or Razor stops too soon:

<p>@Model.UnitPrice * 2</p>
<p>@(Model.UnitPrice * 2)</p>
<p>40.0000 * 2</p>
<p>80.0000</p>

The first line showed the price, then the text * 2. And 40.0000? Northwind's prices are SQL Server money, which keeps four decimal places. Format it as money with ToString("C"), as the product views do.

@{ } runs code

A code block, @{ ... }, runs C# without showing anything: declare variables, work things out, set ViewData. Every view you've seen starts with one:

@{
    ViewData["Title"] = Model.ProductName;
    decimal priceWithTax = Model.UnitPrice!.Value * 1.08m;
}

<p>With tax: @priceWithTax.ToString("C")</p>
<p>With tax: $43.20</p>

@if and @foreach

C#'s if, foreach, for and switch work with an @ in front. Inside their braces, you're back in HTML:

@if (Model.UnitsInStock < 20)
{
    <p>Only @Model.UnitsInStock left!</p>
}

@for (int i = 1; i <= 3; i++)
{
    <span>@i</span>
}
<p>Only 6 left!</p>
<span>1</span>
<span>2</span>
<span>3</span>

@foreach is the one you'll use most: one table row, list item or card per item in the model, as in the products table in Passing a Model to a View.

Razor encodes what it shows

Text shown with @ is HTML-encoded: characters that mean something in HTML, such as <, > and ', are turned into codes the browser shows as plain characters.

@{
    string warning = "<b>Low stock</b>";
}
<p>@warning</p>
<p>&lt;b&gt;Low stock&lt;/b&gt;</p>

The browser shows <b>Low stock</b> as text, not bold. That's a safety feature: if a user types HTML or JavaScript into a form, your pages show it as text instead of running it. (It's also why the page for product 7 has Uncle Bob&#x27;s in its source: &#x27; is the code for '.)

A few more

You write You get
@* a note *@ A Razor comment: it never reaches the browser
sales@northwind.com An email address stays text: Razor sees the letters before the @
@@northwind A literal @, as in @northwind

Tag helpers write your links

You could type links by hand: <a href="/Products/Details/8">. But if a route ever changes, every hand-written address breaks. Tag helpers are asp- attributes that ASP.NET Core turns into the right HTML when the page is built:

<a asp-controller="Products" asp-action="Details" asp-route-id="@Model.ProductId">Details</a>
<a asp-action="Index">All products</a>
<a asp-controller="Home" asp-action="Index">Home</a>
<a asp-action="Search" asp-route-name="sauce">Sauces</a>
<a href="/Products/Details/8">Details</a>
<a href="/Products">All products</a>
<a href="/">Home</a>
<a href="/Products/Search?name=sauce">Sauces</a>
Attribute Means
asp-controller Which controller. Leave it out for the current view's controller.
asp-action Which action
asp-route-id The route's id part
asp-route-anything else A query-string value: asp-route-name="sauce" adds ?name=sauce

Notice the tag helpers know the route: they leave out Index and Home where the defaults cover them.

Tag helpers work because of one line in Views/_ViewImports.cshtml:

@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

Now link the products list to the details pages. In Views/Products/Index.cshtml, change the name cell to:

<td><a asp-action="Details" asp-route-id="@product.ProductId">@product.ProductName</a></td>

Other tag helpers do other jobs. asp-append-version in the layout adds a version to CSS and JavaScript addresses so browsers fetch fresh copies, and the form tag helpers, asp-for above all, are the subject of MVC Forms.

Common problems

What you see What to do
The page shows * 2 or .ToString("C") as text Razor stopped reading C# too soon. Wrap the expression in @( ).
A link leads to a 404 page The tag helper writes an address for any name, even a misspelled one. Check the spelling in asp-action and asp-controller.
asp-action shows up in the page's HTML Tag helpers are off: _ViewImports.cshtml is missing its @addTagHelper line.

Check yourself

2 questions to try on the lesson page.

So what

@ for values, @{ } for code, @if and @foreach for logic, and asp- attributes for links: that's most of Razor. With it, one view turns any product, or a list of seventy-seven, into a page.

Key terms

  • Razor
  • Razor Expression
  • Code Block
  • HTML Encoding
  • Tag Helper
  • asp-action

Cheat sheets