Jak działa silnik widoków Razor w ASP.NET Core

Modele, silnie typowane widoki, ViewBag, ViewData i TempData oraz pętle i warunki w Razor — wszystko, co musisz wiedzieć, żeby wygodnie generować HTML w .NET.

JAJarosław Armatys 9 min czytania
Wersja wideo tego wpisu — obejrzyj na YouTube

Razor to silnik widoków w ASP.NET Core, który pozwala mieszać kod C# z HTML-em w jednym pliku .cshtml. W trzeciej lekcji kursu ASP.NET Core zajmiemy się tym, jak faktycznie przepychać dane z kontrolera do widoku, jak te widoki silnie typować modelami oraz jak obsłużyć pętle i warunki bez zbędnego rozsypywania logiki po całym projekcie. W tym wpisie znajdziesz wszystko, co pokazuję na filmie — od @model przez ViewBag po TempData.

Kod z lekcji leży na GitHubie: asp-net-core-podstawy/tree/Lekcja-3.

Modele — podstawa każdego widoku

Modele w ASP.NET Core MVC trzymamy w folderze Models. W poprzedniej lekcji zaprojektowaliśmy prostą klasę Product, teraz rozbudujemy ją o kilka właściwości i dodamy drugą klasę — Category.

namespace WebApp.Models;

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    public string Category { get; set; }
}

public class Category
{
    public int Id { get; set; }
    public string Name { get; set; }
}

Teraz w kontrolerze ProductController deklarujemy nową akcję, która zwróci pojedynczy produkt do widoku:

public IActionResult Product()
{
    var product = new Product
    {
        Id = 1,
        Name = "BMW M5",
        Description = "To jest super samochód",
        Category = "samochód"
    };

    return View(product);
}

Jak kompilator nie widzi klasy Product, użyj skrótu Ctrl + . — IntelliSense sam podpowie import odpowiedniego using. To najszybsza droga do czystych referencji.

Do kontrolera przekazujemy obiekt przez View(product) i tyle — reszta dzieje się w widoku.

Silne typowanie widoku, czyli @model z dużej litery

Klikasz prawym na akcji, wybierasz „Add View”, zaznaczasz „Empty” (bez scaffoldingu) i dostajesz czysty Product.cshtml. Pierwsza linijka widoku to deklaracja, jakim modelem chcesz typować ten plik:

@model WebApp.Models.Product

<h1>@Model.Name</h1>
<p>Kategoria: @Model.Category</p>
<p>ID: @Model.Id</p>
<p>@Model.Description</p>

Zwróć uwagę na różnicę między @model (małe „m” — deklaracja typu) i @Model (duże „M” — odwołanie do konkretnej instancji). To klasyczny tripwire dla początkujących. Po pełnej ścieżce Namespace.Models.Product Razor wie, czym jest Model, i daje pełne podpowiadanie właściwości.

Jak odpalisz aplikację i wejdziesz pod /Product/Product, zobaczysz dane z kontrolera wyrenderowane w HTML. Na tym właśnie polega cały myk — kontroler przygotowuje dane, a widok je tylko wyświetla.

Kolekcje, pętle i warunki w Razor

Pojedynczy produkt to nuda — realnie zawsze ląduje się z listą. Dorzućmy akcję List, która zwróci List<Product>:

public IActionResult List()
{
    var products = new List<Product>
    {
        new Product { Id = 1, Name = "BMW M5", Category = "samochód" },
        new Product { Id = 2, Name = "Audi RS6", Category = "samochód" },
        new Product { Id = 3, Name = "Monstera", Category = "rośliny" },
        new Product { Id = 4, Name = "Pepsi", Category = "napoje" }
    };

    return View(products);
}

Widok List.cshtml typujemy jako IEnumerable<Product> (albo List<Product>) i lecimy z pętlą:

@model IEnumerable<WebApp.Models.Product>

<h1>Lista produktów</h1>

<ul>
    @foreach (var product in Model)
    {
        <li>@product.Category - @product.Name (ID: @product.Id)</li>
    }
</ul>

Składnia @ otwiera blok Razora. Wewnątrz @foreach Razor rozumie HTML i C# naprzemiennie — wystarczy, że zagnieżdżasz tagi, a silnik sam wie, co renderować, a co wykonać. Dla pojedynczych wartości używasz @product.Name, dla bloków — klamer { ... }.

Chcesz pokazać tylko samochody? Wrzuć if:

<h2>Tylko samochody</h2>
<ul>
    @foreach (var product in Model)
    {
        @if (product.Category == "samochód")
        {
            <li>@product.Name</li>
        }
    }
</ul>

A jakbyś chciał odwrotnie — wszystko, co nie jest samochodem — wystarczy !=:

@if (product.Category != "samochód")
{
    <li>@product.Name</li>
}

Jak piszesz kod C# bez żadnego taga HTML w środku, nie musisz otwierać kolejnego @. Razor zgaduje kontekst po tagach. To mały szczegół, ale oszczędza kilkanaście sekund przy każdym widoku.

Menu w layoutcie, czyli _Layout.cshtml

Wpisywanie URL-i ręcznie w pasku przeglądarki szybko się nudzi, więc dorzucimy linki do menu. W ASP.NET Core layout siedzi w Views/Shared/_Layout.cshtml. Zamiast klasycznego <a href="..."> masz tam pomocniki tagów (tag helpers):

<li class="nav-item">
    <a class="nav-link text-dark"
       asp-area=""
       asp-controller="Product"
       asp-action="List">Lista produktów</a>
</li>
<li class="nav-item">
    <a class="nav-link text-dark"
       asp-area=""
       asp-controller="Product"
       asp-action="Product">Pojedynczy produkt</a>
</li>

Tag helpery (asp-controller, asp-action) są czytelne i Visual Studio podświetla je na zielono, dzięki czemu od razu widzisz, że to nie zwykły HTML. asp-area zostawiasz puste, dopóki nie dzielisz aplikacji na Areas — to temat na później. Linki renderują się do pełnych ścieżek bez hardkodowania w menu.

ViewBag, ViewData, TempData — trzy sposoby na dodatkowe dane

Nie zawsze chcesz przerzucać cały model. Czasem wystarczy pojedyncza informacja — np. tekst powitania, komunikat po zapisie formularza, flaga dla UI. ASP.NET Core daje trzy mechanizmy.

ViewBag — dynamiczny worek, najwygodniejszy w użyciu:

public IActionResult Info()
{
    ViewBag.Greeting = "Cześć z kontrolera";
    return View();
}
<p>@ViewBag.Greeting</p>

ViewData — to samo, tylko w formie słownika po stringach:

ViewData["Greeting"] = "Cześć z ViewData";
<p>@ViewData["Greeting"]</p>

TempData — też słownik, ale z kluczowym twistem: przeżywa jeden dodatkowy request:

TempData["Greeting"] = "Cześć z TempData";
<p>@TempData["Greeting"]</p>

Różnica w sumie sprowadza się do jednego: ViewBag i ViewData żyją tylko w bieżącym cyklu request → response. TempData jest utrzymywany w sesji przez jeszcze jeden kolejny request — dlatego nadaje się do flash messages po redirectach (np. „Zapisano pomyślnie” po przekierowaniu z akcji POST).

Przy formularzach zobaczysz to w praktyce — ale z grubsza: jak robisz RedirectToAction, to ViewBag po drodze zginie, a TempData przetrwa. Do tematu wrócimy w kolejnej lekcji.

Co dalej?

Razor przestaje być magią, jak tylko załapiesz trzy rzeczy: @model silnie typuje widok, @Model to konkretna instancja, a @foreach i @if dają ci pełną kontrolę nad renderowaniem. Do tego ViewBag/ViewData/TempData, gdy nie chcesz ciągnąć całego modelu dla jednej zmiennej — i masz wszystko, co potrzebne, żeby generować sensowny HTML z danych kontrolera.

W następnym wpisie bierzemy się za formularze — jak je walidować, co to są ModelState i atrybuty walidacyjne oraz czym różnią się [HttpGet] i [HttpPost]. Wtedy też pokażę konkretny case, w którym TempData ratuje dane przed utratą po redirectcie. Do zobaczenia w lekcji czwartej.

Tagi:#course-aspnet#dotnet#razor#mvc#views

Masz pytanie do tego wpisu?

Napisz do mnie — chętnie porozmawiam o szczegółach technicznych, albo o tym jak zastosować to u Ciebie w projekcie.