Kurz: Přidání řazení, filtrování a stránkování – ASP.NET MVC pomocí EF Core

V předchozím kurzu jste implementovali sadu webových stránek pro základní operace CRUD pro entity studentů. V tomto kurzu přidáte funkce řazení, filtrování a stránkování na stránku Index studentů. Vytvoříte také stránku, která provede jednoduché seskupení.

Následující obrázek ukazuje, jak bude stránka vypadat po dokončení. Záhlaví sloupců jsou odkazy, na které uživatel může kliknout a seřadit podle tohoto sloupce. Opakovaným kliknutím na záhlaví sloupce se přepíná mezi vzestupným a sestupným pořadím řazení.

Students index page

V tomto kurzu se naučíte:

  • Přidání odkazů pro řazení sloupců
  • Přidání vyhledávacího pole
  • Přidání stránkování do indexu Studentů
  • Přidání stránkování do metody Index
  • Přidání stránkovacího odkazu
  • Vytvoření stránky O aplikaci

Požadavky

Pokud chcete přidat řazení na stránku Index studenta, změníte Index metodu kontroleru Students a přidáte kód do zobrazení Index studenta.

Přidání funkce řazení do metody indexu

V StudentsController.cspříkazu nahraďte metodu Index následujícím kódem:

public async Task<IActionResult> Index(string sortOrder)
{
    ViewData["NameSortParm"] = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
    ViewData["DateSortParm"] = sortOrder == "Date" ? "date_desc" : "Date";
    var students = from s in _context.Students
                   select s;
    switch (sortOrder)
    {
        case "name_desc":
            students = students.OrderByDescending(s => s.LastName);
            break;
        case "Date":
            students = students.OrderBy(s => s.EnrollmentDate);
            break;
        case "date_desc":
            students = students.OrderByDescending(s => s.EnrollmentDate);
            break;
        default:
            students = students.OrderBy(s => s.LastName);
            break;
    }
    return View(await students.AsNoTracking().ToListAsync());
}

Tento kód obdrží sortOrder parametr z řetězce dotazu v adrese URL. Hodnotu řetězce dotazu poskytuje ASP.NET Core MVC jako parametr metody akce. Parametr bude řetězec, který je buď "Name" nebo "Date", volitelně následuje podtržítko a řetězec "desc" k určení sestupného pořadí. Výchozí pořadí řazení je vzestupné.

Při prvním vyžádání indexové stránky neexistuje žádný řetězec dotazu. Studenti se zobrazují ve vzestupném pořadí podle příjmení, což je výchozí nastavení podle písmen v switch příkazu. Když uživatel klikne na hypertextový odkaz záhlaví sloupce, v řetězci dotazu se zobrazí příslušná sortOrder hodnota.

ViewData Dva prvky (NameSortParm a DateSortParm) se používají v zobrazení ke konfiguraci hypertextových odkazů záhlaví sloupců s příslušnými řetězcovými hodnotami dotazu.

public async Task<IActionResult> Index(string sortOrder)
{
    ViewData["NameSortParm"] = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
    ViewData["DateSortParm"] = sortOrder == "Date" ? "date_desc" : "Date";
    var students = from s in _context.Students
                   select s;
    switch (sortOrder)
    {
        case "name_desc":
            students = students.OrderByDescending(s => s.LastName);
            break;
        case "Date":
            students = students.OrderBy(s => s.EnrollmentDate);
            break;
        case "date_desc":
            students = students.OrderByDescending(s => s.EnrollmentDate);
            break;
        default:
            students = students.OrderBy(s => s.LastName);
            break;
    }
    return View(await students.AsNoTracking().ToListAsync());
}

Jedná se o ternární příkazy. První určuje, že pokud sortOrder je parametr null nebo prázdný, nameSortParm by měl být nastaven na "name_desc". Jinak by měl být nastaven na prázdný řetězec. Tyto dva příkazy umožňují zobrazení nastavit hypertextové odkazy záhlaví sloupců následujícím způsobem:

Aktuální pořadí řazení Hypertextový odkaz na příjmení Hypertextový odkaz na datum
Příjmení vzestupně descending ascending
Příjmení sestupně ascending ascending
Vzestupné datum ascending descending
Datum sestupně ascending ascending

Metoda používá LINQ to Entities k určení sloupce, podle který se má seřadit. Kód vytvoří proměnnou IQueryable před příkazem switch, upraví ho v příkazu switch a zavolá ToListAsync metodu za příkazem switch . Při vytváření a úpravě IQueryable proměnných se do databáze neposílají žádné dotazy. Dotaz se nespustí, dokud nepřeveďte IQueryable objekt do kolekce voláním metody, jako ToListAsyncje . Výsledkem tohoto kódu je proto jeden dotaz, který se nespustí, dokud se return View příkaz nespustí.

Tento kód by mohl získat podrobný počet sloupců. Poslední kurz v této sérii ukazuje, jak napsat kód, který umožňuje předat název OrderBy sloupce v řetězcové proměnné.

Nahraďte kód v Views/Students/Index.cshtml, následujícím kódem pro přidání hypertextových odkazů záhlaví sloupce. Změněné čáry jsou zvýrazněné.

@model IEnumerable<ContosoUniversity.Models.Student>

@{
    ViewData["Title"] = "Index";
}

<h2>Index</h2>

<p>
    <a asp-action="Create">Create New</a>
</p>
<table class="table">
    <thead>
        <tr>
                <th>
                    <a asp-action="Index" asp-route-sortOrder="@ViewData["NameSortParm"]">@Html.DisplayNameFor(model => model.LastName)</a>
                </th>
                <th>
                    @Html.DisplayNameFor(model => model.FirstMidName)
                </th>
                <th>
                    <a asp-action="Index" asp-route-sortOrder="@ViewData["DateSortParm"]">@Html.DisplayNameFor(model => model.EnrollmentDate)</a>
                </th>
            <th></th>
        </tr>
    </thead>
    <tbody>
@foreach (var item in Model) {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.LastName)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.FirstMidName)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.EnrollmentDate)
            </td>
            <td>
                <a asp-action="Edit" asp-route-id="@item.ID">Edit</a> |
                <a asp-action="Details" asp-route-id="@item.ID">Details</a> |
                <a asp-action="Delete" asp-route-id="@item.ID">Delete</a>
            </td>
        </tr>
}
    </tbody>
</table>

Tento kód používá informace ve ViewData vlastnostech k nastavení hypertextových odkazů s příslušnými hodnotami řetězce dotazu.

Spusťte aplikaci, vyberte kartu Studenti a kliknutím na záhlaví sloupců Příjmení a Datum registrace ověřte, že řazení funguje.

Students index page in name order

Pokud chcete přidat filtrování na stránku Index studentů, přidáte do zobrazení textové pole a tlačítko odeslat a provedete odpovídající změny v Index metodě. Textové pole vám umožní zadat řetězec, který se má vyhledat v polích jméno a příjmení.

Přidání funkce filtrování do metody Index

V StudentsController.cspříkazu nahraďte metodu Index následujícím kódem (změny jsou zvýrazněny).

public async Task<IActionResult> Index(string sortOrder, string searchString)
{
    ViewData["NameSortParm"] = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
    ViewData["DateSortParm"] = sortOrder == "Date" ? "date_desc" : "Date";
    ViewData["CurrentFilter"] = searchString;

    var students = from s in _context.Students
                   select s;
    if (!String.IsNullOrEmpty(searchString))
    {
        students = students.Where(s => s.LastName.Contains(searchString)
                               || s.FirstMidName.Contains(searchString));
    }
    switch (sortOrder)
    {
        case "name_desc":
            students = students.OrderByDescending(s => s.LastName);
            break;
        case "Date":
            students = students.OrderBy(s => s.EnrollmentDate);
            break;
        case "date_desc":
            students = students.OrderByDescending(s => s.EnrollmentDate);
            break;
        default:
            students = students.OrderBy(s => s.LastName);
            break;
    }
    return View(await students.AsNoTracking().ToListAsync());
}

Do metody jste přidali searchString parametr Index . Hodnota vyhledávacího řetězce se přijímá z textového pole, které přidáte do zobrazení indexu. Přidali jste také do příkazu LINQ klauzuli where, která vybere jenom studenty, jejichž křestní jméno nebo příjmení obsahuje hledaný řetězec. Příkaz, který přidá klauzuli where, se provede pouze v případě, že je hodnota, kterou chcete vyhledat.

Poznámka

Tady voláte metodu WhereIQueryable na objektu a filtr se zpracuje na serveru. V některých scénářích můžete metodu Where volat jako metodu rozšíření v kolekci v paměti. (Předpokládejme například, že změníte odkaz tak _context.Students , aby místo EF DbSet odkaz na metodu úložiště, která vrací kolekci IEnumerable .) Výsledek by obvykle byl stejný, ale v některých případech se může lišit.

Například implementace Contains rozhraní .NET Framework metody ve výchozím nastavení provádí porovnání s rozlišováním velkých a malých písmen, ale na SQL Serveru je to určeno nastavením kolace instance SYSTÉMU SQL Server. Toto nastavení ve výchozím nastavení nerozlišuje malá a velká písmena. Metodu ToUpper můžete volat tak, aby test explicitně nerozlišil malá a velká písmena: Where(s>=s.LastName.ToUpper(). Contains(searchString.Toupper()) To zajistí, aby výsledky zůstaly stejné, pokud později změníte kód tak, aby používal úložiště, které vrací IEnumerable kolekci místo objektu IQueryable . (Při volání Contains metody v IEnumerable kolekci získáte implementaci rozhraní .NET Framework; když ji zavoláte na IQueryable objektu, získáte implementaci zprostředkovatele databáze.) Pro toto řešení je ale snížení výkonu. Kód ToUpper by vložil funkci do klauzule WHERE příkazu TSQL SELECT. Tím by se zabránilo použití indexu optimalizátoru. Vzhledem k tomu, že SQL se většinou instaluje bez rozlišování velkých a malých písmen, je nejlepší se ToUpper vyhnout kódu, dokud nemigrujete do úložiště dat citlivých na malá a velká písmena.

Přidání vyhledávacího pole do zobrazení indexu studenta

Přidejte Views/Student/Index.cshtmlzvýrazněný kód bezprostředně před levou značku tabulky, abyste vytvořili popis, textové pole a tlačítko Hledat.

<p>
    <a asp-action="Create">Create New</a>
</p>

<form asp-action="Index" method="get">
    <div class="form-actions no-color">
        <p>
            Find by name: <input type="text" name="SearchString" value="@ViewData["CurrentFilter"]" />
            <input type="submit" value="Search" class="btn btn-default" /> |
            <a asp-action="Index">Back to Full List</a>
        </p>
    </div>
</form>

<table class="table">

Tento kód používá pomocnou rutinu <form> značky k přidání vyhledávacího textového pole a tlačítka. Ve výchozím nastavení <form> pomocník značky odesílá data formuláře pomocí post, což znamená, že parametry se předávají v textu zprávy HTTP, a ne v adrese URL jako řetězce dotazu. Když zadáte HTTP GET, předají se data formuláře v adrese URL jako řetězce dotazu, což uživatelům umožňuje vytvořit záložku adresy URL. Pokyny W3C doporučují, abyste měli použít get, když akce nemá za následek aktualizaci.

Spusťte aplikaci, vyberte kartu Studenti , zadejte hledaný řetězec a kliknutím na Hledat ověřte, že filtrování funguje.

Students index page with filtering

Všimněte si, že adresa URL obsahuje hledaný řetězec.

http://localhost:5813/Students?SearchString=an

Pokud si tuto stránku označíte záložkou, zobrazí se filtrovaný seznam při použití této záložky. Přidání method="get" ke form značce je to, co způsobilo vygenerování řetězce dotazu.

Pokud v této fázi kliknete na odkaz pro řazení záhlaví sloupce, ztratíte hodnotu filtru, kterou jste zadali do vyhledávacího pole. Opravíte to v další části.

Přidání stránkování do indexu Studentů

Pokud chcete přidat stránkování na stránku Index studentů, vytvoříte PaginatedList třídu, která používá Skip a Take uvádí příkazy k filtrování dat na serveru místo toho, abyste vždy načítá všechny řádky tabulky. Pak provedete další změny v Index metodě a přidáte do zobrazení stránkovací tlačítka Index . Následující obrázek znázorňuje stránkovací tlačítka.

Students index page with paging links

Ve složce projektu vytvořte PaginatedList.csa pak nahraďte kód šablony následujícím kódem.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;

namespace ContosoUniversity
{
    public class PaginatedList<T> : List<T>
    {
        public int PageIndex { get; private set; }
        public int TotalPages { get; private set; }

        public PaginatedList(List<T> items, int count, int pageIndex, int pageSize)
        {
            PageIndex = pageIndex;
            TotalPages = (int)Math.Ceiling(count / (double)pageSize);

            this.AddRange(items);
        }

        public bool HasPreviousPage => PageIndex > 1;

        public bool HasNextPage => PageIndex < TotalPages;

        public static async Task<PaginatedList<T>> CreateAsync(IQueryable<T> source, int pageIndex, int pageSize)
        {
            var count = await source.CountAsync();
            var items = await source.Skip((pageIndex - 1) * pageSize).Take(pageSize).ToListAsync();
            return new PaginatedList<T>(items, count, pageIndex, pageSize);
        }
    }
}

Metoda CreateAsync v tomto kódu má velikost stránky a číslo stránky a použije příslušné Skip příkazy a Take příkazy na IQueryable. Při ToListAsync zavolání na této stránce IQueryablevrátí seznam obsahující pouze požadovanou stránku. Vlastnosti HasPreviousPage a HasNextPage lze je použít k povolení nebo zakázání předchozí a další stránkovací tlačítka.

Metoda CreateAsync se používá místo konstruktoru k vytvoření objektu PaginatedList<T> , protože konstruktory nemohou spustit asynchronní kód.

Přidání stránkování do metody Index

V StudentsController.cs, nahraďte metodu Index následujícím kódem.

public async Task<IActionResult> Index(
    string sortOrder,
    string currentFilter,
    string searchString,
    int? pageNumber)
{
    ViewData["CurrentSort"] = sortOrder;
    ViewData["NameSortParm"] = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
    ViewData["DateSortParm"] = sortOrder == "Date" ? "date_desc" : "Date";

    if (searchString != null)
    {
        pageNumber = 1;
    }
    else
    {
        searchString = currentFilter;
    }

    ViewData["CurrentFilter"] = searchString;

    var students = from s in _context.Students
                   select s;
    if (!String.IsNullOrEmpty(searchString))
    {
        students = students.Where(s => s.LastName.Contains(searchString)
                               || s.FirstMidName.Contains(searchString));
    }
    switch (sortOrder)
    {
        case "name_desc":
            students = students.OrderByDescending(s => s.LastName);
            break;
        case "Date":
            students = students.OrderBy(s => s.EnrollmentDate);
            break;
        case "date_desc":
            students = students.OrderByDescending(s => s.EnrollmentDate);
            break;
        default:
            students = students.OrderBy(s => s.LastName);
            break;
    }

    int pageSize = 3;
    return View(await PaginatedList<Student>.CreateAsync(students.AsNoTracking(), pageNumber ?? 1, pageSize));
}

Tento kód přidá do podpisu metody parametr čísla stránky, aktuální parametr pořadí řazení a aktuální parametr filtru.

public async Task<IActionResult> Index(
    string sortOrder,
    string currentFilter,
    string searchString,
    int? pageNumber)

Při prvním zobrazení stránky nebo pokud uživatel neklikli na stránkovací nebo seřazovací odkaz, budou mít všechny parametry hodnotu null. Pokud kliknete na stránkovací odkaz, proměnná stránky bude obsahovat číslo stránky, které se má zobrazit.

Element ViewData s názvem CurrentSort poskytuje zobrazení s aktuálním pořadím řazení, protože to musí být součástí stránkovací odkazy, aby pořadí řazení bylo stejné při stránkování.

Element ViewData s názvem CurrentFilter poskytuje zobrazení s aktuálním řetězcem filtru. Tato hodnota musí být zahrnuta ve stránkovacích odkazech, aby bylo možné zachovat nastavení filtru během stránkování, a při opětovném zobrazení stránky se musí obnovit do textového pole.

Pokud se během stránkování změní hledaný řetězec, musí se stránka resetovat na hodnotu 1, protože nový filtr může vést k zobrazení různých dat. Hledaný řetězec se změní při zadání hodnoty do textového pole a stisknete tlačítko Odeslat. V takovém případě searchString parametr nemá hodnotu null.

if (searchString != null)
{
    pageNumber = 1;
}
else
{
    searchString = currentFilter;
}

Na konci Index metody PaginatedList.CreateAsync metoda převede dotaz studenta na jednu stránku studentů v typu kolekce, která podporuje stránkování. Tato jedna stránka studentů se pak předá do zobrazení.

return View(await PaginatedList<Student>.CreateAsync(students.AsNoTracking(), pageNumber ?? 1, pageSize));

Metoda PaginatedList.CreateAsync přebírá číslo stránky. Dvě otazníky představují operátor nulového sjednocení. Operátor nulového sjednocení definuje výchozí hodnotu pro typ s možnou hodnotou null; výraz (pageNumber ?? 1) znamená, že vrátí hodnotu pageNumber , pokud má hodnotu, nebo vrátí hodnotu 1, pokud pageNumber má hodnotu null.

Nahraďte Views/Students/Index.cshtmlstávající kód následujícím kódem. Změny jsou zvýrazněné.

@model PaginatedList<ContosoUniversity.Models.Student>

@{
    ViewData["Title"] = "Index";
}

<h2>Index</h2>

<p>
    <a asp-action="Create">Create New</a>
</p>

<form asp-action="Index" method="get">
    <div class="form-actions no-color">
        <p>
            Find by name: <input type="text" name="SearchString" value="@ViewData["CurrentFilter"]" />
            <input type="submit" value="Search" class="btn btn-default" /> |
            <a asp-action="Index">Back to Full List</a>
        </p>
    </div>
</form>

<table class="table">
    <thead>
        <tr>
            <th>
                <a asp-action="Index" asp-route-sortOrder="@ViewData["NameSortParm"]" asp-route-currentFilter="@ViewData["CurrentFilter"]">Last Name</a>
            </th>
            <th>
                First Name
            </th>
            <th>
                <a asp-action="Index" asp-route-sortOrder="@ViewData["DateSortParm"]" asp-route-currentFilter="@ViewData["CurrentFilter"]">Enrollment Date</a>
            </th>
            <th></th>
        </tr>
    </thead>
    <tbody>
        @foreach (var item in Model)
        {
            <tr>
                <td>
                    @Html.DisplayFor(modelItem => item.LastName)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.FirstMidName)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.EnrollmentDate)
                </td>
                <td>
                    <a asp-action="Edit" asp-route-id="@item.ID">Edit</a> |
                    <a asp-action="Details" asp-route-id="@item.ID">Details</a> |
                    <a asp-action="Delete" asp-route-id="@item.ID">Delete</a>
                </td>
            </tr>
        }
    </tbody>
</table>

@{
    var prevDisabled = !Model.HasPreviousPage ? "disabled" : "";
    var nextDisabled = !Model.HasNextPage ? "disabled" : "";
}

<a asp-action="Index"
   asp-route-sortOrder="@ViewData["CurrentSort"]"
   asp-route-pageNumber="@(Model.PageIndex - 1)"
   asp-route-currentFilter="@ViewData["CurrentFilter"]"
   class="btn btn-default @prevDisabled">
    Previous
</a>
<a asp-action="Index"
   asp-route-sortOrder="@ViewData["CurrentSort"]"
   asp-route-pageNumber="@(Model.PageIndex + 1)"
   asp-route-currentFilter="@ViewData["CurrentFilter"]"
   class="btn btn-default @nextDisabled">
    Next
</a>

Příkaz @model v horní části stránky určuje, že zobrazení nyní získá PaginatedList<T> objekt místo objektu List<T> .

Odkazy záhlaví sloupce používají řetězec dotazu k předání aktuálního vyhledávacího řetězce kontroleru, aby uživatel mohl řadit ve výsledcích filtru:

<a asp-action="Index" asp-route-sortOrder="@ViewData["DateSortParm"]" asp-route-currentFilter ="@ViewData["CurrentFilter"]">Enrollment Date</a>

Stránkovací tlačítka se zobrazují pomocnými rutiny značek:

<a asp-action="Index"
   asp-route-sortOrder="@ViewData["CurrentSort"]"
   asp-route-pageNumber="@(Model.PageIndex - 1)"
   asp-route-currentFilter="@ViewData["CurrentFilter"]"
   class="btn btn-default @prevDisabled">
   Previous
</a>

Spusťte aplikaci a přejděte na stránku Studenti.

Students index page with paging links

Klikněte na stránkovací odkazy v různých pořadích řazení a ujistěte se, že stránkování funguje. Potom zadejte hledaný řetězec a zkuste stránkování zopakovat, abyste ověřili, že stránkování funguje s řazením a filtrováním správně.

Vytvoření stránky O aplikaci

Na stránce Informace o webu Contoso University se zobrazí počet studentů, kteří se zaregistrovali ke každému datu registrace. To vyžaduje seskupování a jednoduché výpočty skupin. Uděláte to takto:

  • Vytvořte třídu modelu zobrazení pro data, která potřebujete předat do zobrazení.
  • Vytvořte v kontroleru metodu Home About.
  • Vytvořte zobrazení O aplikaci.

Vytvoření modelu zobrazení

Ve složce Models vytvořte složku SchoolViewModels.

Do nové složky přidejte soubor EnrollmentDateGroup.cs třídy a nahraďte kód šablony následujícím kódem:

using System;
using System.ComponentModel.DataAnnotations;

namespace ContosoUniversity.Models.SchoolViewModels
{
    public class EnrollmentDateGroup
    {
        [DataType(DataType.Date)]
        public DateTime? EnrollmentDate { get; set; }

        public int StudentCount { get; set; }
    }
}

Home Úprava kontroleru

Do HomeController.cshorní části souboru přidejte následující příkazy using:

using Microsoft.EntityFrameworkCore;
using ContosoUniversity.Data;
using ContosoUniversity.Models.SchoolViewModels;
using Microsoft.Extensions.Logging;

Přidejte proměnnou třídy pro kontext databáze bezprostředně za levou složenou závorku třídy a získejte instanci kontextu z ASP.NET Core DI:

public class HomeController : Controller
{
    private readonly ILogger<HomeController> _logger;
    private readonly SchoolContext _context;

    public HomeController(ILogger<HomeController> logger, SchoolContext context)
    {
        _logger = logger;
        _context = context;
    }

Přidejte metodu About s následujícím kódem:

public async Task<ActionResult> About()
{
    IQueryable<EnrollmentDateGroup> data = 
        from student in _context.Students
        group student by student.EnrollmentDate into dateGroup
        select new EnrollmentDateGroup()
        {
            EnrollmentDate = dateGroup.Key,
            StudentCount = dateGroup.Count()
        };
    return View(await data.AsNoTracking().ToListAsync());
}

Příkaz LINQ seskupí entity studentů podle data registrace, vypočítá počet entit v každé skupině a uloží výsledky do kolekce EnrollmentDateGroup objektů modelu zobrazení.

Vytvoření zobrazení o aplikaci

Views/Home/About.cshtml Přidejte soubor s následujícím kódem:

@model IEnumerable<ContosoUniversity.Models.SchoolViewModels.EnrollmentDateGroup>

@{
    ViewData["Title"] = "Student Body Statistics";
}

<h2>Student Body Statistics</h2>

<table>
    <tr>
        <th>
            Enrollment Date
        </th>
        <th>
            Students
        </th>
    </tr>

    @foreach (var item in Model)
    {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.EnrollmentDate)
            </td>
            <td>
                @item.StudentCount
            </td>
        </tr>
    }
</table>

Spusťte aplikaci a přejděte na stránku O aplikaci. Počet studentů pro každé datum registrace se zobrazí v tabulce.

Získání kódu

Stáhněte nebo zobrazte dokončenou aplikaci.

Další kroky

V tomto kurzu se naučíte:

  • Přidání odkazů pro řazení sloupců
  • Přidání vyhledávacího pole
  • Přidání stránkování do indexu Studentů
  • Přidání stránkování do metody Index
  • Přidání stránkovacího odkazu
  • Vytvoření stránky O aplikaci

V dalším kurzu se dozvíte, jak zpracovávat změny datového modelu pomocí migrací.