Sdílet prostřednictvím


Část 3, Razor Stránky s EF Core ASP.NET jádrem – řazení, filtrování, stránkování

Tom Dykstra, Jeremy Likness a Jon P Smith

Webová aplikace Contoso University ukazuje, jak vytvářet Razor webové aplikace Pages pomocí EF Core sady Visual Studio. Informace o sérii kurzů najdete v prvním kurzu.

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

V tomto kurzu přidáte funkce řazení, filtrování a stránkování na stránky Studenti.

Následující obrázek znázorňuje dokončenou stránku. Záhlaví sloupců se dají seřadit kliknutím na odkazy. Opakovaným kliknutím na záhlaví sloupce můžete přepínat mezi vzestupným a sestupným pořadím řazení.

Students index page

Přidání řazení

Nahraďte kód Pages/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é budou obsahovat parametry řazení.
  • Změní název Student vlastnosti na Students.
  • Nahradí kód v OnGetAsync metodě.

Metoda OnGetAsync obdrží sortOrder parametr z řetězce dotazu v adrese URL. Adresa URL a řetězec dotazu je generována pomocným rutinou značky ukotvení.

Parametr sortOrder je buď Name nebo Date. Za sortOrder parametrem _desc můžete volitelně zadat sestupné pořadí. Výchozí pořadí řazení je vzestupné.

Když je stránka Index požadována z odkazu Studenti , neexistuje žádný řetězec dotazu. Studenti se zobrazují vzestupně podle příjmení. Vzestupné pořadí podle příjmení je default v switch příkazu. Když uživatel klikne na odkaz záhlaví sloupce, zobrazí se v řetězcové hodnotě dotazu příslušná sortOrder hodnota.

NameSort a DateSort stránka slouží Razor ke konfiguraci hypertextových odkazů záhlaví sloupců 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, má tři operandy. První řádek určuje, že pokud sortOrder je hodnota null nebo prázdná, NameSort je nastavena na name_deschodnotu . Pokud sortOrder hodnota null nebo není prázdná, NameSort nastaví se na prázdný řetězec.

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
Datum sestupně ascending ascending

Metoda používá LINQ to Entities k určení sloupce, podle který se má seřadit. Kód inicializuje IQueryable<Student> před příkaz 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();

IQueryable Při vytvoření nebo úpravě se do databáze neposílají žádné dotazy. Dotaz se nespustí, dokud IQueryable se objekt nepřevedí do kolekce. IQueryable jsou převedeny na kolekci voláním metody, například ToListAsync. IQueryable Výsledkem kódu je tedy jeden dotaz, který se nespustí, dokud nebude následující příkaz:

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

OnGetAsync mohlo by se dostat do podrobného formátu s velkým počtem seřaditelných sloupců. Informace o alternativním způsobu kódování této funkce 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.cshtmlnásledujícím kódu. 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>

<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 do LastName záhlaví sloupců a EnrollmentDate sloupců.
  • Používá informace v NameSort a DateSort k nastavení hypertextových odkazů s aktuálními hodnotami pořadí řazení.
  • Změní nadpis stránky z indexu na Studenty.
  • Změny Model.Student v souboru Model.Students.

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

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

Přidání filtrování

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

  • Na stránku se přidá Razor textové pole a tlačítko odeslat. Textové pole poskytuje hledaný řetězec na křestní jméno nebo příjmení.
  • Model stránky se aktualizuje tak, aby používal hodnotu textového pole.

Aktualizace metody OnGetAsync

Nahraďte kód Students/Index.cshtml.cs následujícím kódem a přidejte 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:

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

IQueryable vs. IEnumerable

Kód volá metodu Where objektu IQueryable a filtr je zpracován na serveru. V některých scénářích může aplikace volat metodu Where jako metodu rozšíření v kolekci v paměti. Předpokládejme například, že _context.Students se změny z EF CoreDbSet metody ú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 ve výchozím nastavení provádí porovnání s rozlišováním velkých a malých písmen. V SYSTÉMU SQL Server Contains je citlivost na velká a malá písmena určena nastavením kolace instance SYSTÉMU SQL Server. SQL Server ve výchozím nastavení nerozlišuje malá a velká písmena. Funkce SQLite ve výchozím nastavení rozlišují malá a velká písmena. ToUpper lze volat, aby test explicitně nerozlišil malá a velká písmena:

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

Předchozí kód zajistí, že filtr nerozlišuje malá a velká písmena, i když Where je metoda volána na IEnumerable rozhraní SQLite nebo se spustí na rozhraní SQLite.

Při Contains zavolání kolekce IEnumerable se použije implementace .NET Core. Při Contains zavolání objektu IQueryable se použije implementace databáze.

Volání Contains na službu IQueryable je obvykle vhodnější z důvodů výkonu. Při IQueryablepoužití se filtrování provádí databázovým serverem. IEnumerable Pokud se vytvoří nejprve, musí se všechny řádky vrátit z databázového serveru.

Je tu trest za výkon za volání ToUpper. Kód ToUpper 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 je nainstalovaný jako nerozlišující velká a malá písmena, je nejlepší se vyhnout ToUpper volání, když není potřeba.

Další informace naleznete v tématu Použití dotazů nerozlišující velká a malá písmena u zprostředkovatele Sqlite.

Razor Aktualizace stránky

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

@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 pomocí pomocné rutiny <form> značky přidá vyhledávací textové pole a tlačítko. Ve výchozím nastavení <form> pomocník značky odesílá data formuláře pomocí post. U post se parametry předávají v textu zprávy HTTP, a ne v adrese URL. Při použití http GET se data formuláře předávají v adrese URL jako řetězce dotazu. Předání dat pomocí řetězců dotazu umožňuje uživatelům vytvořit záložku adresy URL. Pokyny W3C doporučují, aby se funkce GET používala, když akce nemá za následek aktualizaci.

Otestujte aplikaci:

  • Vyberte kartu Studenti a zadejte hledaný řetězec. Pokud používáte SQLite, filtr nerozlišuje malá a velká písmena jenom 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. Pří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. V method="get" značce form je to, co způsobilo vygenerování řetězce dotazu.

Když je v současné době vybrán odkaz pro řazení záhlaví sloupce, hodnota filtru z vyhledávacího pole se ztratí. Ztracená hodnota filtru je opravena v další části.

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

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

Students index page with paging links

Vytvoření třídy PaginatedList

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

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 předchozím 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 IQueryablestránce vrátí seznam obsahující pouze požadovanou stránku. Vlastnosti HasPreviousPage a HasNextPage slouží k povolení nebo zakázání předchozí a další stránkovací tlačítka.

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

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

Přidejte PageSize do konfiguračníhoappsettings.jsonsouboru:

{
  "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 Students/Index.cshtml.cs pro přidání 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 Students vlastnosti z IList<Student> na PaginatedList<Student>.
  • Přidá index stránky, aktuální sortOrdera currentFilter do OnGetAsync podpisu metody.
  • Uloží pořadí řazení ve CurrentSort vlastnosti.
  • Obnoví index stránky na hodnotu 1, pokud je nový hledaný řetězec.
  • PaginatedList Používá třídu k získání entit studenta.
  • Nastaví pageSize 3 z konfigurace, 4, pokud se konfigurace nezdaří.

Všechny parametry, které OnGetAsync obdrží, mají hodnotu null v následujících případech:

  • Stránka se volá z odkazu Studenti .
  • Uživatel neklikli na stránkovací nebo seřazovací odkaz.

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

Vlastnost CurrentSort poskytuje Razor Page s aktuálním pořadím řazení. Aktuální pořadí řazení musí být zahrnuto ve stránkovacích odkazech, aby bylo možné zachovat pořadí řazení při stránkování.

Vlastnost CurrentFilter poskytuje Razor Page s aktuálním řetězcem filtru. Hodnota CurrentFilter :

  • Aby bylo možné zachovat nastavení filtru během stránkování, musí být součástí stránkovacího odkazu.
  • Po opětovném zobrazení stránky je nutné obnovit textové pole do textového pole.

Pokud se při stránkování změní hledaný řetězec, stránka se resetuje na 1. Stránka se musí resetovat na 1, protože nový filtr může vést k zobrazení různých dat. Když je zadána hodnota hledání a je vybrána možnost Odeslat :

  • Vyhledávací řetězec se změní.
  • Parametr searchString nemá hodnotu null.

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

Dvě otazníky za pageIndexPaginatedList.CreateAsync voláním 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 pageIndex ?? 1 vrátí hodnotu pageIndex , pokud má hodnotu, jinak vrátí hodnotu 1.

Nahraďte kód v 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 vyhledávacího řetězce metodě OnGetAsync :

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

Stránkovací tlačítka se zobrazují pomocnými rutiny 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 studentů.

  • Pokud chcete zajistit, aby stránkování fungovalo, 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í.

students index page with paging links

Seskupení

Tato část vytvoří About stránku, která zobrazí, kolik studentů se zaregistrovalo pro každé datum registrace. Aktualizace používá seskupení a zahrnuje následující kroky:

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

Vytvoření modelu zobrazení

Vytvořte složku Models/SchoolViewModels .

Vytvořte SchoolViewModels/EnrollmentDateGroup.cs pomocí následujícího kódu:

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; }
    }
}

Razor Vytvoření stránky

Vytvořte Pages/About.cshtml soubor 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

Pages/About.cshtml.cs Aktualizujte soubor 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 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í.

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

About page

Další kroky

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

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

Následující obrázek znázorňuje dokončenou stránku. Záhlaví sloupců se dají seřadit kliknutím na odkazy. Kliknutím na záhlaví sloupce se opakovaně přepne mezi vzestupným a sestupným pořadím řazení.

Students index page

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

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

Přidejte do Students/Index.cshtml.csPageModel řetězce, které mají obsahovat 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; }

Students/Index.cshtml.csOnGetAsync Aktualizujte následující kód:

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ží sortOrder parametr z řetězce dotazu v adrese URL. Adresa URL (včetně řetězce dotazu) je generována pomocným pomocníkem značky ukotvení.

Parametr sortOrder je "Name" nebo "Date". Parametr sortOrder je volitelně následovaný "_desc" a určí sestupné pořadí. Výchozí pořadí řazení je vzestupné.

Když je stránka Index požadována z odkazu Studenti , neexistuje žádný řetězec dotazu. Studenti se zobrazují vzestupně podle příjmení. Vzestupné pořadí podle příjmení je výchozí (velká písmena) v switch příkazu. Když uživatel klikne na odkaz záhlaví sloupce, zobrazí se v řetězcové hodnotě dotazu příslušná sortOrder hodnota.

NameSort a DateSort stránka slouží Razor ke konfiguraci hypertextových odkazů záhlaví sloupců s 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 jazyka 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á, NameSort je nastavena na hodnotu "name_desc". Pokud sortOrder hodnota null nebo není prázdná, NameSort nastaví se na prázdný řetězec.

Označuje se ?: operator 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
Datum sestupně ascending ascending

Metoda používá LINQ to Entities k určení sloupce, podle který se má seřadit. Kód inicializuje IQueryable<Student> před příkaz switch a 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();
}

IQueryable Při vytvoření nebo úpravě se do databáze neposílají žádné dotazy. Dotaz se nespustí, dokud IQueryable se objekt nepřevedí do kolekce. IQueryable jsou převedeny na kolekci voláním metody, například ToListAsync. IQueryable Výsledkem kódu je tedy jeden dotaz, který se nespustí, dokud nebude následující příkaz:

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

OnGetAsync mohlo by se dostat do podrobného formátu s velkým počtem seřaditelných sloupců.

Nahraďte kód v 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 LastName záhlaví sloupců a EnrollmentDate sloupců.
  • Používá informace v NameSort a 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.

Lepší porozumění kódu:

  • Nastavte Students/Index.cshtml.cszarážku na switch (sortOrder).
  • Přidejte kukátku NameSort a DateSort.
  • Nastavte Students/Index.cshtmlzarážku na @Html.DisplayNameFor(model => model.Student[0].LastName).

Projděte ladicí program.

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

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

  • Na stránku se přidá Razor textové pole a tlačítko odeslat. Textové pole poskytuje hledaný řetězec na křestní jméno nebo příjmení.
  • Model stránky se aktualizuje tak, aby používal hodnotu textového pole.

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

Students/Index.cshtml.csOnGetAsync Aktualizujte následující kód:

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:

  • searchString Přidá parametr do OnGetAsync metody. Hodnota vyhledávacího řetězce se přijímá z textového pole přidaného v další části.
  • Přidáno do příkazu LINQ Where klauzule. Klauzule Where vybere jenom studenty, jejichž křestní jméno nebo příjmení obsahuje hledaný řetězec. Příkaz LINQ se spustí pouze v případě, že je k dispozici hodnota, kterou chcete vyhledat.

Poznámka: Předchozí kód volá metodu Where objektu IQueryable a filtr je zpracován na serveru. V některých scénářích může aplikace volat metodu Where jako metodu rozšíření v kolekci v paměti. Předpokládejme například, že _context.Students se změny z EF CoreDbSet metody ú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 ve výchozím nastavení provádí porovnání s rozlišováním velkých a malých písmen. V SYSTÉMU SQL Server Contains je citlivost na velká a malá písmena určena nastavením kolace instance SYSTÉMU SQL Server. SQL Server ve výchozím nastavení nerozlišuje malá a velká písmena. ToUpper lze volat, aby test explicitně nerozlišil malá a velká písmena:

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

Předchozí kód zajistí, aby výsledky nerozlišovaly malá a velká písmena, pokud se kód změní na použití IEnumerable. Při Contains zavolání kolekce IEnumerable se použije implementace .NET Core. Při Contains zavolání objektu IQueryable se použije implementace databáze. Vrácení IEnumerable z úložiště může mít významné snížení výkonu:

  1. Všechny řádky se vrátí ze serveru DATABÁZE.
  2. Filtr se použije na všechny vrácené řádky v aplikaci.

Je tu trest za výkon za volání ToUpper. Kód ToUpper 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 je nainstalovaný jako nerozlišující velká a malá písmena, je nejlepší se vyhnout ToUpper volání, když není potřeba.

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

Do Pages/Students/Index.cshtmlpole přidejte následující zvýrazněný kód pro vytvoření tlačítka Hledat a seřazeného chromu.

@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 pomocí pomocné rutiny <form> značky přidá vyhledávací textové pole a tlačítko. Ve výchozím nastavení <form> pomocník značky odesílá data formuláře pomocí post. U post se parametry předávají v textu zprávy HTTP, a ne v adrese URL. Při použití http GET se data formuláře předávají v adrese URL jako řetězce dotazu. Předání dat pomocí řetězců dotazu umožňuje uživatelům vytvořit záložku adresy URL. Pokyny W3C doporučují, aby se funkce GET používala, když akce nemá za následek aktualizaci.

Otestujte aplikaci:

  • Vyberte kartu 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. V method="get" značce form je to, co způsobilo vygenerování řetězce dotazu.

Když je v současné době vybrán odkaz pro řazení záhlaví sloupce, hodnota filtru z vyhledávacího pole se ztratí. Ztracená hodnota filtru je opravena v další části.

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

V této části se vytvoří třída, PaginatedList která podporuje stránkování. Třída PaginatedList používá Skip a Take příkazy k filtrování dat na serveru místo načtení všech řádků tabulky. 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.cs následující kód:

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 předchozím 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 IQueryablestránce vrátí seznam obsahující pouze požadovanou stránku. Vlastnosti HasPreviousPage a HasNextPage slouží k povolení nebo zakázání předchozí a další stránkovací tlačítka.

Metoda CreateAsync se používá 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 Students/Index.cshtml.csaplikaci aktualizujte typ Student z IList<Student> na PaginatedList<Student>:

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

Students/Index.cshtml.csOnGetAsync Aktualizujte následující kód:

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í sortOrdera do currentFilter podpisu metody.

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

Všechny parametry mají hodnotu null v následujících případech:

  • Stránka se volá z odkazu Studenti .
  • Uživatel neklikli na stránkovací nebo seřazovací odkaz.

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

CurrentSortRazor poskytuje stránku s aktuálním pořadím řazení. Aktuální pořadí řazení musí být zahrnuto ve stránkovacích odkazech, aby bylo možné zachovat pořadí řazení při stránkování.

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

  • Aby bylo možné zachovat nastavení filtru během stránkování, musí být součástí stránkovacího odkazu.
  • Po opětovném zobrazení stránky je nutné obnovit textové pole do textového pole.

Pokud se při stránkování změní hledaný řetězec, stránka se resetuje na 1. Stránka se musí resetovat na 1, protože nový filtr může vést k zobrazení různých dat. Když je zadána hodnota hledání a je vybrána možnost Odeslat :

  • Vyhledávací řetězec se změní.
  • Parametr searchString nemá hodnotu null.
if (searchString != null)
{
    pageIndex = 1;
}
else
{
    searchString = currentFilter;
}

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

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

Dvě otazníky představují PaginatedList.CreateAsyncoperátor nulového sjednocení. Operátor nulového sjednocení 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 nemá hodnotu, vrátí hodnotu 1.

Aktualizujte kód v Students/Index.cshtmlsouboru . Změny jsou zvýrazněné:

@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>

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

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

Stránkovací tlačítka se zobrazují pomocnými rutiny 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 studentů.

  • Pokud chcete zajistit, aby stránkování fungovalo, 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í.

students index page with paging links

Lepší porozumění kódu:

  • Nastavte Students/Index.cshtml.cszarážku na switch (sortOrder).
  • Přidejte kukátku , NameSortDateSort, CurrentSorta Model.Student.PageIndex.
  • Nastavte Students/Index.cshtmlzarážku na @Html.DisplayNameFor(model => model.Student[0].LastName).

Projděte ladicí program.

Aktualizace stránky O aplikaci tak, aby zobrazovala statistiky studentů

V tomto kroku se aktualizuje, aby se zobrazilo, Pages/About.cshtml kolik studentů se zaregistrovalo pro každé datum registrace. Aktualizace používá seskupení a zahrnuje následující kroky:

  • Vytvořte model zobrazení pro data používaná stránkou O aplikaci.
  • Aktualizujte stránku O aplikaci tak, aby používala model zobrazení.

Vytvoření modelu zobrazení

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

Do složky SchoolViewModels přidejte následující EnrollmentDateGroup.cs kód:

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 O produktu

Webové šablony v ASP.NET Core 2.2 nezahrnují stránku O produktu. Pokud používáte ASP.NET Core 2.2, vytvořte stránku O Razor aplikaci.

Pages/About.cshtml.cs Aktualizujte soubor 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 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í.

Úprava stránky O Razor aplikaci

Nahraďte kód v Pages/About.cshtml souboru 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 aplikaci. Počet studentů pro každé datum registrace se zobrazí v tabulce.

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

About page

Další prostředky

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