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;@ifand@foreachwork 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-actionandasp-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><b>Low stock</b></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's in its source: ' 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