část 3 Razor : stránky s EF Core v ASP.NET Core řazení, filtrování, stránkování

Tím, že Dykstra, Jeremy Liknessa Jan P Smith

Webová aplikace Contoso University vám ukáže, jak vytvářet Razor stránky webových aplikací pomocí EF Core a Visual Studio. Informace o řadě kurzů najdete v prvním kurzu.

Pokud narazíte na problémy, které nemůžete vyřešit, stáhněte dokončenou aplikaci a porovnejte tento kód s tím, co jste vytvořili, pomocí tohoto kurzu.

Tento kurz přináší na stránky studentů funkce řazení, filtrování a stránkování.

Na následujícím obrázku je znázorněna Dokončená stránka. Záhlaví sloupců jsou kliknutí na odkazy pro řazení sloupce. Chcete-li přepínat mezi vzestupném a sestupným řazením, klikněte na záhlaví sloupce opakovaně.

Stránka indexu studentů

Přidat řazení

Nahraďte kód na stránkách/Students/index. cshtml. cs následujícím kódem pro přidání řazení.

public class IndexModel : PageModel
{
    private readonly SchoolContext _context;
    public IndexModel(SchoolContext context)
    {
        _context = context;
    }

    public string NameSort { get; set; }
    public string DateSort { get; set; }
    public string CurrentFilter { get; set; }
    public string CurrentSort { get; set; }

    public IList<Student> Students { get; set; }

    public async Task OnGetAsync(string sortOrder)
    {
        // using System;
        NameSort = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
        DateSort = sortOrder == "Date" ? "date_desc" : "Date";

        IQueryable<Student> studentsIQ = from s in _context.Students
                                        select s;

        switch (sortOrder)
        {
            case "name_desc":
                studentsIQ = studentsIQ.OrderByDescending(s => s.LastName);
                break;
            case "Date":
                studentsIQ = studentsIQ.OrderBy(s => s.EnrollmentDate);
                break;
            case "date_desc":
                studentsIQ = studentsIQ.OrderByDescending(s => s.EnrollmentDate);
                break;
            default:
                studentsIQ = studentsIQ.OrderBy(s => s.LastName);
                break;
        }

        Students = await studentsIQ.AsNoTracking().ToListAsync();
    }
}

Předchozí kód:

  • Vyžaduje přidání using System; .
  • Přidá vlastnosti, které obsahují parametry řazení.
  • Změní název Student vlastnosti na Students .
  • Nahradí kód v OnGetAsync metodě.

OnGetAsyncMetoda přijímá sortOrder parametr z řetězce dotazu v adrese URL. Adresa URL a řetězec dotazu jsou vygenerovány pomocníkem značek ukotvení.

sortOrderParametr je buď Name nebo Date . sortOrderParametr je volitelně následován _desc k určení sestupného pořadí. Výchozí pořadí řazení je vzestupné.

Když se na stránku indexu požaduje odkaz na studenty , neexistuje žádný řetězec dotazu. Studenti se zobrazí ve vzestupném pořadí podle příjmení. Vzestupné pořadí podle příjmení je default v switch příkazu. Když uživatel klikne na odkaz záhlaví sloupce, sortOrder je v hodnotě řetězce dotazu uvedena příslušná hodnota.

NameSort a DateSort jsou používány Razor stránkou ke konfiguraci hypertextových odkazů záhlaví sloupce s příslušnými hodnotami řetězce dotazu:

NameSort = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
DateSort = sortOrder == "Date" ? "date_desc" : "Date";

Kód používá podmíněný operátor jazyka C#?:. ?:Operátor je Ternární operátor, používá tři operandy. První řádek určuje, že pokud sortOrder je hodnota null nebo prázdná, NameSort je nastavena na name_desc . Pokud sortOrder hodnota není null nebo prázdná, NameSort je nastavena na prázdný řetězec.

Tyto dva příkazy umožňují, aby stránka nastavila hypertextové odkazy záhlaví sloupce následujícím způsobem:

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

metoda používá LINQ to Entities k určení sloupce, podle kterého se má řadit. Kód inicializuje IQueryable<Student> před příkazem Switch a upraví ho v příkazu switch:

IQueryable<Student> studentsIQ = from s in _context.Students
                                select s;

switch (sortOrder)
{
    case "name_desc":
        studentsIQ = studentsIQ.OrderByDescending(s => s.LastName);
        break;
    case "Date":
        studentsIQ = studentsIQ.OrderBy(s => s.EnrollmentDate);
        break;
    case "date_desc":
        studentsIQ = studentsIQ.OrderByDescending(s => s.EnrollmentDate);
        break;
    default:
        studentsIQ = studentsIQ.OrderBy(s => s.LastName);
        break;
}

Students = await studentsIQ.AsNoTracking().ToListAsync();

Při IQueryable Vytvoření nebo úpravě se do databáze neodesílají žádné dotazy. Dotaz není proveden, dokud nebude IQueryable objekt převeden do kolekce. IQueryable jsou převedeny na kolekci voláním metody, jako je ToListAsync . Proto IQueryable kód vytvoří jeden dotaz, který není proveden do následujícího příkazu:

Students = await studentsIQ.AsNoTracking().ToListAsync();

OnGetAsync může získat podrobné zobrazení s velkým počtem sloupců, které lze seřadit. Informace o alternativním způsobu, jak tuto funkci zakódovat, najdete v tématu použití dynamického LINQ ke zjednodušení kódu ve verzi MVC této série kurzů.

Nahraďte kód v Students/index. cshtml s následujícím kódem. Změny jsou zvýrazněny.

@page
@model ContosoUniversity.Pages.Students.IndexModel

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

<h2>Students</h2>
<p>
    <a asp-page="Create">Create New</a>
</p>

<table class="table">
    <thead>
        <tr>
            <th>
                <a asp-page="./Index" asp-route-sortOrder="@Model.NameSort">
                    @Html.DisplayNameFor(model => model.Students[0].LastName)
                </a>
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Students[0].FirstMidName)
            </th>
            <th>
                <a asp-page="./Index" asp-route-sortOrder="@Model.DateSort">
                    @Html.DisplayNameFor(model => model.Students[0].EnrollmentDate)
                </a>
            </th>
            <th></th>
        </tr>
    </thead>
    <tbody>
        @foreach (var item in Model.Students)
        {
            <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-page="./Edit" asp-route-id="@item.ID">Edit</a> |
                    <a asp-page="./Details" asp-route-id="@item.ID">Details</a> |
                    <a asp-page="./Delete" asp-route-id="@item.ID">Delete</a>
                </td>
            </tr>
        }
    </tbody>
</table>

Předchozí kód:

  • Přidá hypertextové odkazy LastName do EnrollmentDate záhlaví sloupců a.
  • Používá informace v NameSort a DateSort k nastavení hypertextových odkazů s aktuálními hodnotami pořadí řazení.
  • Změní záhlaví stránky z indexu na studenty.
  • Změny Model.Student v Model.Students .

Ověření, že řazení funguje:

  • Spusťte aplikaci a vyberte kartu Students .
  • Klikněte na záhlaví sloupců.

Přidat filtrování

Postup přidání filtrování na stránku indexu studentů:

  • Na stránku je přidáno textové pole a tlačítko Odeslat Razor . Textové pole poskytuje hledaný řetězec pro první nebo poslední název.
  • Model stránky je aktualizován tak, aby používal hodnotu textového pole.

Aktualizace metody OnGetAsync

Nahraďte kód v Students/index. cshtml. cs následujícím kódem pro přidání filtrování:

public class IndexModel : PageModel
{
    private readonly SchoolContext _context;

    public IndexModel(SchoolContext context)
    {
        _context = context;
    }

    public string NameSort { get; set; }
    public string DateSort { get; set; }
    public string CurrentFilter { get; set; }
    public string CurrentSort { get; set; }

    public IList<Student> Students { get; set; }

    public async Task OnGetAsync(string sortOrder, string searchString)
    {
        NameSort = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
        DateSort = sortOrder == "Date" ? "date_desc" : "Date";

        CurrentFilter = searchString;
        
        IQueryable<Student> studentsIQ = from s in _context.Students
                                        select s;
        if (!String.IsNullOrEmpty(searchString))
        {
            studentsIQ = studentsIQ.Where(s => s.LastName.Contains(searchString)
                                   || s.FirstMidName.Contains(searchString));
        }

        switch (sortOrder)
        {
            case "name_desc":
                studentsIQ = studentsIQ.OrderByDescending(s => s.LastName);
                break;
            case "Date":
                studentsIQ = studentsIQ.OrderBy(s => s.EnrollmentDate);
                break;
            case "date_desc":
                studentsIQ = studentsIQ.OrderByDescending(s => s.EnrollmentDate);
                break;
            default:
                studentsIQ = studentsIQ.OrderBy(s => s.LastName);
                break;
        }

        Students = await studentsIQ.AsNoTracking().ToListAsync();
    }
}

Předchozí kód:

  • Přidá searchString parametr do OnGetAsync metody a uloží hodnotu parametru do CurrentFilter Vlastnosti. Hodnota hledaného řetězce se přijímá z textového pole, které se přidalo v další části.
  • Přidá do příkazu LINQ Where klauzuli. WhereKlauzule vybere pouze studenty, jejichž křestní jméno nebo příjmení obsahuje hledaný řetězec. Příkaz LINQ se spustí pouze v případě, že existuje hodnota, která se má vyhledat.

IQueryable vs. IEnumerable

Kód volá Where metodu na IQueryable objekt a filtr je zpracován na serveru. V některých scénářích může aplikace zavolat Where metodu jako metodu rozšíření v kolekci v paměti. Předpokládejme například, že se _context.Students změní z EF Core DbSet na metodu úložiště, která vrací IEnumerable kolekci. Výsledek by byl normálně stejný, ale v některých případech se může lišit.

například implementace .NET Framework ve Contains výchozím nastavení provádí porovnání rozlišující malá a velká písmena. v SQL Server Contains je rozlišování velkých a malých písmen určeno nastavením kolace instance SQL Server. výchozí hodnota SQL Server nerozlišuje malá a velká písmena. Výchozí hodnota SQLite rozlišuje velká a malá písmena. ToUpper může být volána, aby test explicitně nerozlišuje velikost písmen:

Where(s => s.LastName.ToUpper().Contains(searchString.ToUpper())`

Předchozí kód zajistí, že filtr rozlišuje velká a malá písmena, i když je Where metoda volána na IEnumerable nebo spuštěná na sqlite.

Když Contains je volána v IEnumerable kolekci, je použita implementace .NET Core. Když Contains je volána u IQueryable objektu, je použita implementace databáze.

Volání Contains na IQueryable je obvykle vhodnější z důvodů výkonu. Pomocí nástroje IQueryable je filtrování provedeno databázovým serverem. Pokud IEnumerable je vytvořen jako první, všechny řádky musí být vráceny z databázového serveru.

Došlo ke snížení výkonu pro volání ToUpper . ToUpperKód přidá funkci v klauzuli WHERE příkazu TSQL SELECT. Přidaná funkce brání Optimalizátoru v používání indexu. Vzhledem k tomu, že SQL se nainstalují jako nerozlišuje velká a malá písmena, je nejlepší se vyhnout ToUpper volání, když není potřeba.

Další informace najdete v tématu jak použít dotaz nerozlišující malá a velká písmena se zprostředkovatelem SQLite.

Aktualizovat Razor stránku

Nahraďte kód v Pages/Students/Index.cshtml přidáním tlačítka Prohledat.

@page
@model ContosoUniversity.Pages.Students.IndexModel

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

<h2>Students</h2>

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

<form asp-page="./Index" method="get">
    <div class="form-actions no-color">
        <p>
            Find by name:
            <input type="text" name="SearchString" value="@Model.CurrentFilter" />
            <input type="submit" value="Search" class="btn btn-primary" /> |
            <a asp-page="./Index">Back to full List</a>
        </p>
    </div>
</form>

<table class="table">
    <thead>
        <tr>
            <th>
                <a asp-page="./Index" asp-route-sortOrder="@Model.NameSort">
                    @Html.DisplayNameFor(model => model.Students[0].LastName)
                </a>
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Students[0].FirstMidName)
            </th>
            <th>
                <a asp-page="./Index" asp-route-sortOrder="@Model.DateSort">
                    @Html.DisplayNameFor(model => model.Students[0].EnrollmentDate)
                </a>
            </th>
            <th></th>
        </tr>
    </thead>
    <tbody>
        @foreach (var item in Model.Students)
        {
            <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-page="./Edit" asp-route-id="@item.ID">Edit</a> |
                    <a asp-page="./Details" asp-route-id="@item.ID">Details</a> |
                    <a asp-page="./Delete" asp-route-id="@item.ID">Delete</a>
                </td>
            </tr>
        }
    </tbody>
</table>

Předchozí kód používá pomocný <form> prvek značky k přidání vyhledávacího textového pole a tlačítka. Ve výchozím nastavení pomocník <form> značky odešle data formuláře pomocí post. U metody POST se parametry předá v textu zprávy HTTP, a ne v adrese URL. Při použití HTTP GET se data formuláře předá v adrese URL jako řetězce dotazu. Předání dat pomocí řetězců dotazů umožňuje uživatelům vytvořit záložku adresy URL. Pokyny W3C doporučují, aby se v případě, že akce nesníží aktualizaci, použila get.

Otestujte aplikaci:

  • Vyberte kartu Students (Studenti) a zadejte hledaný řetězec. Pokud používáte SQLite, filtr se bez rozlišení velkých a malých písmen odlišuje pouze v případě, že jste implementovali volitelný ToUpper kód zobrazený dříve.

  • Vyberte Hledat.

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

https://localhost:5001/Students?SearchString=an

Pokud je stránka záložka, záložka obsahuje adresu URL stránky a SearchString řetězec dotazu. Ve method="get" form značce je to, co způsobilo vygenerování řetězce dotazu.

Když je aktuálně vybraný odkaz pro řazení záhlaví sloupce, hodnota filtru z vyhledávacího pole se ztratí. Hodnota ztraceného filtru je v další části opravená.

Přidání stránkování

V této části je PaginatedList vytvořena třída pro podporu stránkování. Třída používá příkazy a k filtrování dat na serveru namísto načítání všech PaginatedList Skip řádků Take tabulky. Následující obrázek znázorňuje stránkovací tlačítka.

Stránka indexu Students s odkazy na stránkování

Vytvoření třídy PaginatedList

Ve složce projektu vytvořte PaginatedList.cs pomocí následujícího kódu:

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 v předchozím kódu přebírá velikost stránky a číslo stránky a aplikuje příslušné příkazy a CreateAsync Skip na Take IQueryable . Když ToListAsync se volá na , vrátí seznam obsahující pouze IQueryable požadovanou stránku. Vlastnosti a HasPreviousPage slouží k povolení nebo zakázání tlačítek HasNextPage předchozího a dalšího stránkování.

Metoda CreateAsync se používá k vytvoření PaginatedList<T> . Konstruktor nemůže vytvořit objekt ; konstruktory PaginatedList<T> nespouštěly asynchronní kód.

Přidání velikosti stránky do konfigurace

Přidejte PageSize do appsettings.json konfiguračního souboru:

{
  "PageSize": 3,
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "SchoolContext": "Server=(localdb)\\mssqllocaldb;Database=CU-1;Trusted_Connection=True;MultipleActiveResultSets=true"
  }
}

Přidání stránkování do modelu IndexModel

Nahraďte kód v souboru Students/Index.cshtml.cs přidáním stránkování.

using ContosoUniversity.Data;
using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using System;
using System.Linq;
using System.Threading.Tasks;

namespace ContosoUniversity.Pages.Students
{
    public class IndexModel : PageModel
    {
        private readonly SchoolContext _context;
        private readonly IConfiguration Configuration;

        public IndexModel(SchoolContext context, IConfiguration configuration)
        {
            _context = context;
            Configuration = configuration;
        }

        public string NameSort { get; set; }
        public string DateSort { get; set; }
        public string CurrentFilter { get; set; }
        public string CurrentSort { get; set; }

        public PaginatedList<Student> Students { get; set; }

        public async Task OnGetAsync(string sortOrder,
            string currentFilter, string searchString, int? pageIndex)
        {
            CurrentSort = sortOrder;
            NameSort = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
            DateSort = sortOrder == "Date" ? "date_desc" : "Date";
            if (searchString != null)
            {
                pageIndex = 1;
            }
            else
            {
                searchString = currentFilter;
            }

            CurrentFilter = searchString;

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

            var pageSize = Configuration.GetValue("PageSize", 4);
            Students = await PaginatedList<Student>.CreateAsync(
                studentsIQ.AsNoTracking(), pageIndex ?? 1, pageSize);
        }
    }
}

Předchozí kód:

  • Změní typ vlastnosti Students z IList<Student> na PaginatedList<Student> .
  • Přidá index stránky, aktuální sortOrder a do currentFilter OnGetAsync signatury metody .
  • Uloží pořadí řazení do CurrentSort vlastnosti .
  • Pokud existuje nový hledaný řetězec, obnoví index stránky na hodnotu 1.
  • Používá třídu PaginatedList k získání studentských entit.
  • V pageSize konfiguraci nastaví hodnotu na 3, pokud konfigurace selže, na hodnotu 4.

Všechny parametry, které OnGetAsync obdrží, mají hodnotu null, když:

  • Stránka se volá z odkazu Studenti.
  • Uživatel neklikl na odkaz pro stránkování nebo řazení.

Když kliknete na stránkovací odkaz, proměnná indexu stránky obsahuje číslo stránky, které se má zobrazit.

Vlastnost CurrentSort poskytuje stránce aktuální pořadí Razor řazení. Aktuální pořadí řazení musí být součástí stránkovacího propojení, aby se při stránkování uchová pořadí řazení.

Vlastnost CurrentFilter poskytuje stránce aktuální řetězec Razor filtru. CurrentFilterHodnota:

  • Aby bylo možné zachovat nastavení filtru během stránkování, musí být součástí stránkovacího propojení.
  • 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, stránka se resetuje na hodnotu 1. Stránku je třeba resetovat na 1, protože nový filtr může vést k různým datům, která se mají zobrazit. Po zadání hledané hodnoty a výběru možnosti Odeslat:

  • Hledaný řetězec se změní.
  • Parametr searchString není null.

Metoda převede dotaz studenta na jednu stránku studentů v typu PaginatedList.CreateAsync kolekce, která podporuje stránkování. Tato jedna stránka studentů se předá Razor této stránce.

Dvě otazníky za ve pageIndex PaginatedList.CreateAsync volání představují operátor nulového salescingu. Operátor nulového salescing definuje výchozí hodnotu pro typ s možnou hodnotou null. Výraz vrátí hodnotu , pokud má hodnotu , v opačném případě pageIndex ?? 1 pageIndex vrátí hodnotu 1.

Nahraďte kód v souboru Students/Index.cshtml následujícím kódem. Změny jsou zvýrazněné:

@page
@model ContosoUniversity.Pages.Students.IndexModel

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

<h2>Students</h2>

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

<form asp-page="./Index" method="get">
    <div class="form-actions no-color">
        <p>
            Find by name: 
            <input type="text" name="SearchString" value="@Model.CurrentFilter" />
            <input type="submit" value="Search" class="btn btn-primary" /> |
            <a asp-page="./Index">Back to full List</a>
        </p>
    </div>
</form>

<table class="table">
    <thead>
        <tr>
            <th>
                <a asp-page="./Index" asp-route-sortOrder="@Model.NameSort"
                   asp-route-currentFilter="@Model.CurrentFilter">
                    @Html.DisplayNameFor(model => model.Students[0].LastName)
                </a>
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Students[0].FirstMidName)
            </th>
            <th>
                <a asp-page="./Index" asp-route-sortOrder="@Model.DateSort"
                   asp-route-currentFilter="@Model.CurrentFilter">
                    @Html.DisplayNameFor(model => model.Students[0].EnrollmentDate)
                </a>
            </th>
            <th></th>
        </tr>
    </thead>
    <tbody>
        @foreach (var item in Model.Students)
        {
            <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-page="./Edit" asp-route-id="@item.ID">Edit</a> |
                    <a asp-page="./Details" asp-route-id="@item.ID">Details</a> |
                    <a asp-page="./Delete" asp-route-id="@item.ID">Delete</a>
                </td>
            </tr>
        }
    </tbody>
</table>

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

<a asp-page="./Index"
   asp-route-sortOrder="@Model.CurrentSort"
   asp-route-pageIndex="@(Model.Students.PageIndex - 1)"
   asp-route-currentFilter="@Model.CurrentFilter"
   class="btn btn-primary @prevDisabled">
    Previous
</a>
<a asp-page="./Index"
   asp-route-sortOrder="@Model.CurrentSort"
   asp-route-pageIndex="@(Model.Students.PageIndex + 1)"
   asp-route-currentFilter="@Model.CurrentFilter"
   class="btn btn-primary @nextDisabled">
    Next
</a>

Odkazy záhlaví sloupce používají řetězec dotazu k předání aktuálního hledaového řetězce do OnGetAsync metody :

<a asp-page="./Index" asp-route-sortOrder="@Model.NameSort"
   asp-route-currentFilter="@Model.CurrentFilter">
    @Html.DisplayNameFor(model => model.Students[0].LastName)
</a>

Tlačítka stránkování se zobrazují pomocí pomocníků značek:


<a asp-page="./Index"
   asp-route-sortOrder="@Model.CurrentSort"
   asp-route-pageIndex="@(Model.Students.PageIndex - 1)"
   asp-route-currentFilter="@Model.CurrentFilter"
   class="btn btn-primary @prevDisabled">
    Previous
</a>
<a asp-page="./Index"
   asp-route-sortOrder="@Model.CurrentSort"
   asp-route-pageIndex="@(Model.Students.PageIndex + 1)"
   asp-route-currentFilter="@Model.CurrentFilter"
   class="btn btn-primary @nextDisabled">
    Next
</a>

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

  • Pokud chcete zajistit, aby stránkování funguje, klikněte na stránkovací odkazy v různých pořadích řazení.
  • Pokud chcete ověřit, že stránkování funguje správně s řazením a filtrováním, zadejte hledaný řetězec a zkuste stránkování.

Stránka indexu students s odkazy na stránkování

Seskupování

V této části se About vytvoří stránka, která zobrazuje, kolik studentů se zaregistroval pro každé datum registrace. Aktualizace používá seskupení a zahrnuje následující kroky:

  • Vytvořte model zobrazení pro data používaná About stránkou.
  • Aktualizujte About stránku tak, aby se používá model zobrazení.

Vytvoření modelu zobrazení

Vytvořte složku Models/SchoolViewModels.

Vytvořte SchoolViewModels/EnrollmentDateGroup.cs s 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; }
    }
}

Vytvoření Razor stránky

Vytvořte soubor Pages/About.cshtml s následujícím kódem:

@page
@model ContosoUniversity.Pages.AboutModel

@{
    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.Students)
    {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.EnrollmentDate)
            </td>
            <td>
                @item.StudentCount
            </td>
        </tr>
    }
</table>

Vytvoření modelu stránky

Aktualizujte soubor Pages/About.cshtml.cs následujícím kódem:

using ContosoUniversity.Models.SchoolViewModels;
using ContosoUniversity.Data;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using ContosoUniversity.Models;

namespace ContosoUniversity.Pages
{
    public class AboutModel : PageModel
    {
        private readonly SchoolContext _context;

        public AboutModel(SchoolContext context)
        {
            _context = context;
        }

        public IList<EnrollmentDateGroup> Students { get; set; }

        public async Task OnGetAsync()
        {
            IQueryable<EnrollmentDateGroup> data =
                from student in _context.Students
                group student by student.EnrollmentDate into dateGroup
                select new EnrollmentDateGroup()
                {
                    EnrollmentDate = dateGroup.Key,
                    StudentCount = dateGroup.Count()
                };

            Students = await data.AsNoTracking().ToListAsync();
        }
    }
}

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

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

Stránka O službě

Další kroky

V dalším kurzu aplikace používá k aktualizaci datového modelu migrace.

V tomto kurzu se přidávají funkce řazení, filtrování, seskupování a stránkování.

Na následujícím obrázku je zobrazena dokončená stránka. Záhlaví sloupců jsou odkazy, na které můžete kliknout, abyste sloupec seřadí. Kliknutím na záhlaví sloupce se opakovaně přepíná mezi vzestupným a sestupným pořadím řazení.

Stránka indexu Students

Pokud na problémy naštou problémy, které nemůžete vyřešit, stáhněte si dokončenou aplikaci.

Přidání řazení na stránku Index

Přidejte řetězce do souboru Students/Index.cshtml.cs, PageModel aby obsahovaly parametry řazení:

public class IndexModel : PageModel
{
    private readonly SchoolContext _context;

    public IndexModel(SchoolContext context)
    {
        _context = context;
    }

    public string NameSort { get; set; }
    public string DateSort { get; set; }
    public string CurrentFilter { get; set; }
    public string CurrentSort { get; set; }

Aktualizujte soubor Students/Index.cshtml.cs OnGetAsync následujícím kódem:

public async Task OnGetAsync(string sortOrder)
{
    NameSort = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
    DateSort = sortOrder == "Date" ? "date_desc" : "Date";

    IQueryable<Student> studentIQ = from s in _context.Student
                                    select s;

    switch (sortOrder)
    {
        case "name_desc":
            studentIQ = studentIQ.OrderByDescending(s => s.LastName);
            break;
        case "Date":
            studentIQ = studentIQ.OrderBy(s => s.EnrollmentDate);
            break;
        case "date_desc":
            studentIQ = studentIQ.OrderByDescending(s => s.EnrollmentDate);
            break;
        default:
            studentIQ = studentIQ.OrderBy(s => s.LastName);
            break;
    }

    Student = await studentIQ.AsNoTracking().ToListAsync();
}

Předchozí kód obdrží parametr sortOrder z řetězce dotazu v adrese URL. Adresu URL (včetně řetězce dotazu) vygeneruje pomocná aplikace značky ukotvení.

Parametr sortOrder je "Name" nebo "Date". Parametr sortOrder je volitelně následován textem "_desc" pro určení sestupného pořadí. Výchozí pořadí řazení je vzestupné.

Pokud se stránka Index požaduje z odkazu Students, neexistuje žádný řetězec dotazu. Studenti se zobrazují vzestupně podle příjmení. Vzestupné pořadí podle příjmení je výchozí (propadající případ) v switch příkazu . Když uživatel klikne na odkaz záhlaví sloupce, v hodnotě řetězce dotazu se zobrazí odpovídající sortOrder hodnota.

NameSort a DateSort používá stránka ke konfiguraci hypertextových odkazů záhlaví sloupce s Razor příslušnými hodnotami řetězce dotazu:

public async Task OnGetAsync(string sortOrder)
{
    NameSort = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
    DateSort = sortOrder == "Date" ? "date_desc" : "Date";

    IQueryable<Student> studentIQ = from s in _context.Student
                                    select s;

    switch (sortOrder)
    {
        case "name_desc":
            studentIQ = studentIQ.OrderByDescending(s => s.LastName);
            break;
        case "Date":
            studentIQ = studentIQ.OrderBy(s => s.EnrollmentDate);
            break;
        case "date_desc":
            studentIQ = studentIQ.OrderByDescending(s => s.EnrollmentDate);
            break;
        default:
            studentIQ = studentIQ.OrderBy(s => s.LastName);
            break;
    }

    Student = await studentIQ.AsNoTracking().ToListAsync();
}

Následující kód obsahuje podmíněný operátor C# ?::

NameSort = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
DateSort = sortOrder == "Date" ? "date_desc" : "Date";

První řádek určuje, že pokud sortOrder je hodnota null nebo prázdná, je nastavena na NameSort "name_desc". Pokud sortOrder nemá hodnotu null nebo je prázdná, NameSort je nastavena na prázdný řetězec.

Označuje ?: operator se také jako ternární operátor.

Tyto dva příkazy umožňují stránce 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
Sestupné datum ascending ascending

Metoda používá LINQ to Entities k určení sloupce, podle kterých se má řadit. Kód inicializuje před příkazem switch a IQueryable<Student> upraví ho v příkazu switch:

public async Task OnGetAsync(string sortOrder)
{
    NameSort = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
    DateSort = sortOrder == "Date" ? "date_desc" : "Date";

    IQueryable<Student> studentIQ = from s in _context.Student
                                    select s;

    switch (sortOrder)
    {
        case "name_desc":
            studentIQ = studentIQ.OrderByDescending(s => s.LastName);
            break;
        case "Date":
            studentIQ = studentIQ.OrderBy(s => s.EnrollmentDate);
            break;
        case "date_desc":
            studentIQ = studentIQ.OrderByDescending(s => s.EnrollmentDate);
            break;
        default:
            studentIQ = studentIQ.OrderBy(s => s.LastName);
            break;
    }

    Student = await studentIQ.AsNoTracking().ToListAsync();
}

Když se IQueryable vytvoří nebo upraví objekt , do databáze se nesídá žádný dotaz. Dotaz se neprovádí, dokud IQueryable není objekt převeden na kolekci. IQueryable jsou převedeny na kolekci voláním metody, například ToListAsync . Výsledkem tohoto kódu je jediný dotaz, který se nepro IQueryable spustí až do následujícího příkazu:

Student = await studentIQ.AsNoTracking().ToListAsync();

OnGetAsync může získat podrobné informace o velkém počtu seřazených sloupců.

Nahraďte kód v souboru Students/Index.cshtml následujícím zvýrazněným kódem:

@page
@model ContosoUniversity.Pages.Students.IndexModel

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

<h2>Index</h2>
<p>
    <a asp-page="Create">Create New</a>
</p>

<table class="table">
    <thead>
        <tr>
            <th>
                <a asp-page="./Index" asp-route-sortOrder="@Model.NameSort">
                    @Html.DisplayNameFor(model => model.Student[0].LastName)
                </a>
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Student[0].FirstMidName)
            </th>
            <th>
                <a asp-page="./Index" asp-route-sortOrder="@Model.DateSort">
                    @Html.DisplayNameFor(model => model.Student[0].EnrollmentDate)
                </a>
            </th>
            <th></th>
        </tr>
    </thead>
    <tbody>
        @foreach (var item in Model.Student)
        {
            <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-page="./Edit" asp-route-id="@item.ID">Edit</a> |
                    <a asp-page="./Details" asp-route-id="@item.ID">Details</a> |
                    <a asp-page="./Delete" asp-route-id="@item.ID">Delete</a>
                </td>
            </tr>
        }
    </tbody>
</table>

Předchozí kód:

  • Přidá hypertextové odkazy do záhlaví LastName EnrollmentDate sloupců a .
  • Používá informace v a NameSort DateSort k nastavení hypertextových odkazů s aktuálními hodnotami pořadí řazení.

Ověření, že řazení funguje:

  • Spusťte aplikaci a vyberte kartu Studenti.
  • Klikněte na Příjmení.
  • Klikněte na Datum registrace.

Pokud chcete lépe porozumět kódu:

  • V souboru Students/Index.cshtml.cs nastavte zarážku na switch (sortOrder) .
  • Přidejte hodinku pro a NameSort DateSort .
  • V souboru Students/Index.cshtml nastavte zarážku na @Html.DisplayNameFor(model => model.Student[0].LastName) .

Projdete si ladicí program.

Přidání vyhledávacího pole na stránku Students Index

Přidání filtrování na stránku Students Index:

  • Na stránku se přidá textové pole a tlačítko Razor pro odeslání. Do textového pole zadejte hledaný řetězec k prvnímu nebo příjmení.
  • Model stránky se aktualizuje a použije hodnotu textového pole.

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

Aktualizujte soubor Students/Index.cshtml.cs OnGetAsync následujícím kódem:

public async Task OnGetAsync(string sortOrder, string searchString)
{
    NameSort = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
    DateSort = sortOrder == "Date" ? "date_desc" : "Date";
    CurrentFilter = searchString;

    IQueryable<Student> studentIQ = from s in _context.Student
                                    select s;
    if (!String.IsNullOrEmpty(searchString))
    {
        studentIQ = studentIQ.Where(s => s.LastName.Contains(searchString)
                               || s.FirstMidName.Contains(searchString));
    }

    switch (sortOrder)
    {
        case "name_desc":
            studentIQ = studentIQ.OrderByDescending(s => s.LastName);
            break;
        case "Date":
            studentIQ = studentIQ.OrderBy(s => s.EnrollmentDate);
            break;
        case "date_desc":
            studentIQ = studentIQ.OrderByDescending(s => s.EnrollmentDate);
            break;
        default:
            studentIQ = studentIQ.OrderBy(s => s.LastName);
            break;
    }

    Student = await studentIQ.AsNoTracking().ToListAsync();
}

Předchozí kód:

  • Přidá searchString parametr do OnGetAsync metody . Hledaný řetězec se přijímá z textového pole přidaného v další části.
  • Přidání klauzule do příkazu Where LINQ Klauzule vybere pouze studenty, jejichž jméno nebo příjmení Where obsahuje hledaný řetězec. Příkaz LINQ se spustí pouze v případě, že existuje hodnota, která se má vyhledat.

Poznámka: Předchozí kód volá metodu u objektu Where a filtr se IQueryable zpracuje na serveru. V některých scénářích může aplikace volat metodu jako rozšiřující metodu v Where kolekci v paměti. Předpokládejme například, _context.Students že se EF Core na DbSet metodu úložiště, která vrací IEnumerable kolekci. Výsledek by normálně byl stejný, ale v některých případech se může lišit.

Například při .NET Framework třídy se ve výchozím nastavení rozlišují malá Contains a velká písmena. Kromě SQL Server se rozlišování velkých a malých písmen určuje podle nastavení kolace Contains SQL Server instance. SQL Server se ve výchozím nastavení bez rozlišení velkých a malých písmen. ToUpper lze volat, aby test explicitně bez rozlišení velkých a malých písmen:

Where(s => s.LastName.ToUpper().Contains(searchString.ToUpper())

Předchozí kód zajistí, aby se ve výsledcích v případě, že se změní kód na použití , nerozlišovat velká a malá IEnumerable písmena. Při Contains volání v kolekci se použije implementace IEnumerable .NET Core. Při Contains volání objektu se používá implementace IQueryable databáze. Vrácení z IEnumerable úložiště může mít výraznou penalizaci výkonu:

  1. Všechny řádky se vrátí z databázového serveru.
  2. Filtr se použije na všechny vrácené řádky v aplikaci.

Při volání se může zvýšit ToUpper výkon. Kód ToUpper přidá funkci do klauzule WHERE příkazu TSQL SELECT. Přidaná funkce brání optimalizátoru v použití indexu. Vzhledem k SQL, že se funkce instaluje jako bez rozlišení velkých a malých písmen, je nejlepší se volání vyhnout, když ToUpper není potřeba.

Přidání vyhledávacího pole na stránku Indexu studentů

V souboru Pages/Students/Index.cshtml přidejte následující zvýrazněný kód, který vytvoří tlačítko Hledat a různé chrome.

@page
@model ContosoUniversity.Pages.Students.IndexModel

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

<h2>Index</h2>

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

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

<table class="table">

Předchozí kód používá pomocný <form> prvek značky k přidání vyhledávacího textového pole a tlačítka. Ve výchozím nastavení pomocník <form> značky odešle data formuláře pomocí post. U metody POST se parametry předá v textu zprávy HTTP, a ne v adrese URL. Při použití HTTP GET se data formuláře předá v adrese URL jako řetězce dotazu. Předání dat pomocí řetězců dotazů umožňuje uživatelům vytvořit záložku adresy URL. Pokyny W3C doporučují, aby se v případě, že akce nesníží aktualizaci, použila get.

Otestujte aplikaci:

  • Vyberte kartu Students (Studenti) a zadejte hledaný řetězec.
  • Vyberte Hledat.

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

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

Pokud je stránka záložka, záložka obsahuje adresu URL stránky a SearchString řetězec dotazu. Ve method="get" form značce je to, co způsobilo vygenerování řetězce dotazu.

Když je aktuálně vybraný odkaz pro řazení záhlaví sloupce, hodnota filtru z vyhledávacího pole se ztratí. Hodnota ztraceného filtru je v další části opravená.

Přidání funkce stránkování na stránku Students Index

V této části je PaginatedList vytvořena třída pro podporu stránkování. Třída používá příkazy a k filtrování dat na serveru namísto načítání všech PaginatedList Skip řádků Take tabulky. Následující obrázek znázorňuje stránkovací tlačítka.

Stránka indexu Students s odkazy na stránkování

Ve složce projektu vytvořte PaginatedList.cs pomocí následujícího kódu:

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
        {
            get
            {
                return (PageIndex > 1);
            }
        }

        public bool HasNextPage
        {
            get
            {
                return (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);
        }
    }
}

CreateAsyncMetoda v předchozím kódu má velikost stránky a číslo stránky a aplikuje příslušné Skip Take příkazy a na IQueryable . Když ToListAsync je volána na IQueryable , vrátí seznam obsahující pouze požadovanou stránku. Vlastnosti HasPreviousPage a HasNextPage slouží k povolení nebo zakázání tlačítek předchozí a Další stránkování.

CreateAsyncMetoda slouží k vytvoření PaginatedList<T> . Konstruktor nemůže vytvořit PaginatedList<T> objekt, konstruktory nemůžou spustit asynchronní kód.

Přidání funkce stránkování do metody index

V části studenti/index. cshtml. cs aktualizujte typ Student z IList<Student> na PaginatedList<Student> :

public PaginatedList<Student> Student { get; set; }

Aktualizujte studenty/index. cshtml. cs OnGetAsync pomocí následujícího kódu:

public async Task OnGetAsync(string sortOrder,
    string currentFilter, string searchString, int? pageIndex)
{
    CurrentSort = sortOrder;
    NameSort = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
    DateSort = sortOrder == "Date" ? "date_desc" : "Date";
    if (searchString != null)
    {
        pageIndex = 1;
    }
    else
    {
        searchString = currentFilter;
    }

    CurrentFilter = searchString;

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

    int pageSize = 3;
    Student = await PaginatedList<Student>.CreateAsync(
        studentIQ.AsNoTracking(), pageIndex ?? 1, pageSize);
}

Předchozí kód přidá index stránky, aktuální sortOrder a currentFilter do signatury metody.

public async Task OnGetAsync(string sortOrder,
    string currentFilter, string searchString, int? pageIndex)

Všechny parametry mají hodnotu null, pokud:

  • Stránka je volána z odkazu Students .
  • Uživatel nekliknul na odkaz na stránkování nebo řazení.

Po kliknutí na odkaz na stránkování obsahuje proměnná index stránky číslo stránky, která se má zobrazit.

CurrentSort poskytuje Razor stránku s aktuálním pořadím řazení. Aktuální pořadí řazení musí být ve stránkovacích odkazech zahrnuto, aby při stránkování zůstalo pořadí řazení.

CurrentFilter poskytuje Razor stránku s aktuálním řetězcem filtru. CurrentFilterHodnota:

  • Musí být součástí odkazů stránkování, aby bylo možné zachovat nastavení filtru během stránkování.
  • Po zobrazení stránky se musí obnovit do textového pole.

Pokud se hledaný řetězec změní během stránkování, stránka je resetována na 1. Stránka musí být obnovena na 1, protože nový filtr může mít za následek zobrazení různých dat. Když je zadána hodnota vyhledávání a je vybrána možnost Odeslat :

  • Hledaný řetězec se změnil.
  • searchStringParametr není null.
if (searchString != null)
{
    pageIndex = 1;
}
else
{
    searchString = currentFilter;
}

PaginatedList.CreateAsyncMetoda převede dotaz studenta na jednu stránku studentů v typu kolekce, který podporuje stránkování. Tato jediná stránka studentů se předává na Razor stránku.

Student = await PaginatedList<Student>.CreateAsync(
    studentIQ.AsNoTracking(), pageIndex ?? 1, pageSize);

Dvě otazníky v PaginatedList.CreateAsync reprezentují operátor slučování s hodnotou null. Operátor slučování null definuje výchozí hodnotu pro typ s možnou hodnotou null. Výraz (pageIndex ?? 1) znamená, že vrátí hodnotu, pageIndex Pokud má hodnotu. Pokud pageIndex hodnota nemá, vrátí hodnotu 1.

Aktualizujte značky v Students/index. cshtml. Změny jsou zvýrazněny:

@page
@model ContosoUniversity.Pages.Students.IndexModel

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

<h2>Index</h2>

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

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

<table class="table">
    <thead>
        <tr>
            <th>
                <a asp-page="./Index" asp-route-sortOrder="@Model.NameSort"
                   asp-route-currentFilter="@Model.CurrentFilter">
                    @Html.DisplayNameFor(model => model.Student[0].LastName)
                </a>
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Student[0].FirstMidName)
            </th>
            <th>
                <a asp-page="./Index" asp-route-sortOrder="@Model.DateSort"
                   asp-route-currentFilter="@Model.CurrentFilter">
                    @Html.DisplayNameFor(model => model.Student[0].EnrollmentDate)
                </a>
            </th>
            <th></th>
        </tr>
    </thead>
    <tbody>
        @foreach (var item in Model.Student)
        {
            <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-page="./Edit" asp-route-id="@item.ID">Edit</a> |
                    <a asp-page="./Details" asp-route-id="@item.ID">Details</a> |
                    <a asp-page="./Delete" asp-route-id="@item.ID">Delete</a>
                </td>
            </tr>
        }
    </tbody>
</table>

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

<a asp-page="./Index"
   asp-route-sortOrder="@Model.CurrentSort"
   asp-route-pageIndex="@(Model.Student.PageIndex - 1)"
   asp-route-currentFilter="@Model.CurrentFilter"
   class="btn btn-default @prevDisabled">
    Previous
</a>
<a asp-page="./Index"
   asp-route-sortOrder="@Model.CurrentSort"
   asp-route-pageIndex="@(Model.Student.PageIndex + 1)"
   asp-route-currentFilter="@Model.CurrentFilter"
   class="btn btn-default @nextDisabled">
    Next
</a>

Záhlaví sloupce odkazuje pomocí řetězce dotazu k předání aktuálního vyhledávacího řetězce OnGetAsync metodě, aby uživatel mohl seřadit výsledky filtru:

<a asp-page="./Index" asp-route-sortOrder="@Model.NameSort"
   asp-route-currentFilter="@Model.CurrentFilter">
    @Html.DisplayNameFor(model => model.Student[0].LastName)
</a>

Tlačítka pro stránkování se zobrazují v pomocníkech značek:


<a asp-page="./Index"
   asp-route-sortOrder="@Model.CurrentSort"
   asp-route-pageIndex="@(Model.Student.PageIndex - 1)"
   asp-route-currentFilter="@Model.CurrentFilter"
   class="btn btn-default @prevDisabled">
    Previous
</a>
<a asp-page="./Index"
   asp-route-sortOrder="@Model.CurrentSort"
   asp-route-pageIndex="@(Model.Student.PageIndex + 1)"
   asp-route-currentFilter="@Model.CurrentFilter"
   class="btn btn-default @nextDisabled">
    Next
</a>

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

  • Chcete-li zajistit, aby stránkování fungovalo, klikněte na odkazy na stránkování v různých objednávkách řazení.
  • Pokud chcete ověřit, že stránkování funguje správně s řazením a filtrováním, zadejte hledaný řetězec a zkuste použít stránkování.

stránka indexu studentů s odkazy na stránkování

Chcete-li získat lepší informace o kódu:

  • V nabídce Students/index. cshtml. cs nastavte zarážku na switch (sortOrder) .
  • Přidejte kukátko pro NameSort , DateSort , a CurrentSort Model.Student.PageIndex .
  • V nabídce Students/index. cshtml nastavte zarážku na @Html.DisplayNameFor(model => model.Student[0].LastName) .

Projděte si ladicí program.

Aktualizace stránky o produktu pro zobrazení statistik studenta

V tomto kroku se aktualizují stránky/o. cshtml , aby se zobrazilo, kolik studentů bylo zaregistrované pro každé datum registrace. Tato aktualizace používá seskupení a obsahuje následující kroky:

  • Vytvořte model zobrazení pro data používaná na stránce About .
  • Aktualizujte stránku o, aby používala model zobrazení.

Vytvoření modelu zobrazení

Vytvořte složku SchoolViewModels ve složce modely .

Ve složce SchoolViewModels přidejte EnrollmentDateGroup. cs s 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; }
    }
}

Aktualizace modelu stránky

webové šablony v ASP.NET Core 2,2 neobsahují stránku About. pokud používáte ASP.NET Core 2,2, vytvořte Razor stránku About.

Aktualizujte soubor stránky/o. cshtml. cs následujícím kódem:

using ContosoUniversity.Models.SchoolViewModels;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using ContosoUniversity.Models;

namespace ContosoUniversity.Pages
{
    public class AboutModel : PageModel
    {
        private readonly SchoolContext _context;

        public AboutModel(SchoolContext context)
        {
            _context = context;
        }

        public IList<EnrollmentDateGroup> Student { get; set; }

        public async Task OnGetAsync()
        {
            IQueryable<EnrollmentDateGroup> data =
                from student in _context.Student
                group student by student.EnrollmentDate into dateGroup
                select new EnrollmentDateGroup()
                {
                    EnrollmentDate = dateGroup.Key,
                    StudentCount = dateGroup.Count()
                };

            Student = await data.AsNoTracking().ToListAsync();
        }
    }
}

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

Úprava stránky o produktu Razor

Nahraďte kód v souboru Pages/About. cshtml následujícím kódem:

@page
@model ContosoUniversity.Pages.AboutModel

@{
    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.Student)
    {
        <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 produktu. V tabulce se zobrazí počet studentů pro každé datum zápisu.

Pokud narazíte na problémy, které nemůžete vyřešit, Stáhněte si dokončenou aplikaci pro tuto fázi.

O stránce

Další zdroje informací

V dalším kurzu aplikace používá migrace k aktualizaci datového modelu.