DEV Community

Manohari Jayachandran
Manohari Jayachandran

Posted on

Razor Syntax Fundamentals: Rendering the Task Tracker

Parts 1 and 2 covered WPF and MVVM, a desktop application built and then properly structured. Part 3 pivots to the web, genuinely new territory. Razor is the syntax Blazor, covered in Part 4, is built directly on top of, so this post covers Razor on its own first, deliberately, before any framework gets layered in, the same Task Tracker data from the previous two posts, now rendered as a simple, read-only web page.

What Razor Actually Is

Razor is a syntax for mixing C# directly into HTML, letting a single file describe both the visual structure of a page and the logic that decides what actually gets rendered, no separate templating language to learn, since the logic portions are just genuine C#.

Think of a mail-merge letter template. Most of the letter is fixed, ordinary text, but certain spots contain a placeholder, "Dear [Name]", that gets filled in with real data before the letter is actually printed. Razor works the same way: most of a .cshtml file is plain HTML, but specific spots marked with @ get replaced with real, computed values, or entire blocks of logic, before the page is actually sent to a browser.

The @ Symbol: How Razor Knows Where C# Starts

Everything in a Razor file is treated as plain HTML by default. The @ symbol is the signal that switches into C# mode for whatever comes immediately after it.

<h2>Task Tracker</h2>
<p>You have @Model.Tasks.Count tasks.</p>

<!-- Everything here is plain HTML, rendered exactly
     as written, EXCEPT @Model.Tasks.Count - that
     specific expression is evaluated in C#, and the
     RESULT (a number) is what actually appears in
     the rendered page -->
Enter fullscreen mode Exit fullscreen mode

Razor is genuinely smart about where C# ends and HTML resumes again, it doesn't require an explicit closing symbol for a simple expression like this one, since it can tell from the syntax itself where the C# expression naturally ends.

Mixing C# and HTML Directly: A Small First Example

@{
    var completedCount = Model.Tasks.Count(t => t.IsCompleted);
}

<h2>Task Tracker</h2>
<p>@completedCount of @Model.Tasks.Count tasks completed</p>

<!-- The @{ } block above is a CODE BLOCK - pure C#,
     computing a value, but not directly rendering
     anything itself. The variable it creates
     (completedCount) is then USED further down,
     inside the HTML, with a simple @ expression -->
Enter fullscreen mode Exit fullscreen mode

@foreach: Rendering a List

Razor supports genuine C# control structures directly inside HTML, a @foreach loop generates one copy of whatever HTML sits inside it, once per item.

<ul>
    @foreach (var task in Model.Tasks)
    {
        <li>@task.Description</li>
    }
</ul>

<!-- This is a REAL C# foreach loop - the exact same
     syntax used anywhere else in C#. The <li> element
     inside the loop body gets rendered once per task
     in Model.Tasks, with @task.Description substituted
     in each time -->
Enter fullscreen mode Exit fullscreen mode

@if: Conditional Rendering

The same applies to @if and else, genuine C# conditional logic, deciding which HTML actually gets rendered.

<ul>
    @foreach (var task in Model.Tasks)
    {
        <li>
            @if (task.IsCompleted)
            {
                <s>@task.Description</s>
            }
            else
            {
                <span>@task.Description</span>
            }
        </li>
    }
</ul>

<!-- Completed tasks render inside a <s> tag
     (strikethrough) - matching visually what checking
     a box did in the WPF version from Parts 1 and 2.
     Incomplete tasks render in a plain <span> instead -->
Enter fullscreen mode Exit fullscreen mode

Rendering the Complete Task Tracker as a Read-Only Page

@model TaskTrackerViewModel

<h2>Task Tracker</h2>

@{
    var completedCount = Model.Tasks.Count(t => t.IsCompleted);
}

<p>@completedCount of @Model.Tasks.Count tasks completed</p>

<ul>
    @foreach (var task in Model.Tasks)
    {
        <li>
            @if (task.IsCompleted)
            {
                <s>@task.Description</s>
            }
            else
            {
                <span>@task.Description</span>
            }
        </li>
    }
</ul>

@if (!Model.Tasks.Any())
{
    <p><em>No tasks yet.</em></p>
}

<!-- @model at the very top declares WHAT TYPE of data
     this specific Razor file expects to receive - here,
     a TaskTrackerViewModel containing a list of tasks.
     This is what makes Model.Tasks, used throughout the
     rest of the file, actually valid and known at
     compile time -->
Enter fullscreen mode Exit fullscreen mode
// TaskTrackerViewModel.cs - the plain C# class this
// Razor file expects, supplied by whatever is rendering
// the page (a Razor Page or an MVC controller action)
public class TaskTrackerViewModel
{
    public List<TaskItem> Tasks { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

What's Deliberately Not Here Yet

This page is genuinely read-only, there's no way to add a task, check a box, or delete anything from it. That's intentional: Razor by itself describes how to render HTML from data, nothing more. The interactivity, a button that actually does something without a full page reload, a checkbox that actually updates state, is exactly what Blazor, covered in the next post, adds on top of this same Razor syntax.

Key Lessons

Razor mixes real C# directly into HTML using the @ symbol, there's no separate templating language, the logic portions are genuine, ordinary C#.

A code block computes values without directly rendering anything; a simple expression renders a value directly at that exact spot.

@foreach and @if are the exact same C# control structures used anywhere else, just usable directly inside HTML markup.

@model at the top of a file declares the exact type of data that file expects, giving compile-time knowledge of what Model actually contains throughout the rest of the file.

Razor on its own only describes how to render HTML from data, it has no interactivity by itself, which is exactly the gap Blazor fills next.

What's Next

Part 4 covers Blazor, taking this exact Razor syntax and adding real interactivity on top of it, turning the read-only Task Tracker page built here into one where adding, checking, and deleting tasks actually works, without a full page reload.

Summary

Razor lets C# and HTML live in the same file, using @ as the signal for where C# begins, a code block for computing values, a simple expression for rendering one directly, and genuine control structures like @foreach and @if for loops and conditionals. The Task Tracker rendered here is intentionally read-only, since Razor by itself only describes how to turn data into HTML, it has no concept of interactivity on its own. That gap is exactly what the next post's subject, Blazor, exists to fill, building directly on top of everything covered in this one.


More from TechStack Blog: C# / .NET: https://www.techstackblog.com/category.html?cat=csharp
CS Fundamentals: https://www.techstackblog.com/category.html?cat=cs-fundamentals

Top comments (0)