Bagikan melalui


Bagian 3, Razor Halaman dengan EF Core inti ASP.NET - Urutkan, Filter, Halaman

Oleh Tom Dykstra, Jeremy Likness, dan Jon P Smith

Aplikasi web Contoso University menunjukkan cara membuat Razor aplikasi web Pages menggunakan EF Core dan Visual Studio. Untuk informasi tentang seri tutorial, lihat tutorial pertama.

Jika Anda mengalami masalah yang tidak dapat Anda selesaikan, unduh aplikasi yang telah selesai dan bandingkan kode tersebut dengan apa yang Anda buat dengan mengikuti tutorial.

Tutorial ini menambahkan fungsi pengurutan, pemfilteran, dan penomoran halaman ke halaman Siswa.

Ilustrasi berikut ini memperlihatkan halaman yang telah selesai. Judul kolom adalah tautan yang dapat diklik untuk mengurutkan kolom. Klik judul kolom berulang kali untuk beralih antara urutan urutan naik dan turun.

Students index page

Menambahkan pengurutan

Ganti kode dengan Pages/Students/Index.cshtml.cs kode berikut untuk menambahkan pengurutan.

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

Kode sebelumnya:

  • Memerlukan penambahan using System;.
  • Menambahkan properti untuk memuat parameter pengurutan.
  • Mengubah nama properti menjadi StudentStudents.
  • Mengganti kode dalam OnGetAsync metode .

Metode OnGetAsync menerima sortOrder parameter dari string kueri di URL. URL dan string kueri dihasilkan oleh Pembantu Tag Jangkar.

Parameternya sortOrder adalah Name atau Date. Parameter sortOrder ini secara opsional diikuti oleh _desc untuk menentukan urutan menurut. Urutan sortir default adalah menaik.

Saat halaman Indeks diminta dari tautan Siswa , tidak ada string kueri. Siswa ditampilkan dalam urutan naik berdasarkan nama belakang. Urutan naik menurut nama belakang adalah default dalam switch pernyataan. Saat pengguna mengklik tautan judul kolom, nilai yang sesuai sortOrder disediakan dalam nilai string kueri.

NameSort dan DateSort digunakan oleh Razor Halaman untuk mengonfigurasi hyperlink judul kolom dengan nilai string kueri yang sesuai:

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

Kode menggunakan operator kondisi C# ?:. Operator ?: adalah operator ternary, dibutuhkan tiga operand. Baris pertama menentukan bahwa ketika sortOrder null atau kosong, NameSort diatur ke name_desc. Jika sortOrder tidak null atau kosong, NameSort diatur ke string kosong.

Kedua pernyataan ini memungkinkan halaman untuk mengatur hyperlink judul kolom sebagai berikut:

Urutan pengurutan saat ini Hyperlink Nama Belakang Hyperlink Tanggal
Nama Belakang naik Urut turun urutan naik
Nama Belakang turun urutan naik urutan naik
Tanggal naik urutan naik Urut turun
Tanggal turun urutan naik urutan naik

Metode ini menggunakan LINQ ke Entitas untuk menentukan kolom yang akan diurutkan. Kode menginisialisasi IQueryable<Student> sebelum pernyataan pengalihan, dan memodifikasinya dalam pernyataan pengalihan:

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 Saat dibuat atau dimodifikasi, tidak ada kueri yang dikirim ke database. Kueri tidak dijalankan sampai objek dikonversi IQueryable menjadi koleksi. IQueryable dikonversi ke koleksi dengan memanggil metode seperti ToListAsync. Oleh karena itu, IQueryable kode menghasilkan satu kueri yang tidak dijalankan hingga pernyataan berikut:

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

OnGetAsync bisa mendapatkan verbose dengan sejumlah besar kolom yang dapat diurutkan. Untuk informasi tentang cara alternatif untuk mengodekan fungsionalitas ini, lihat Menggunakan LINQ dinamis untuk menyederhanakan kode dalam versi MVC dari seri tutorial ini.

Ganti kode di Students/Index.cshtml, dengan kode berikut. Perubahan disorot.

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

Kode sebelumnya:

  • Menambahkan hyperlink ke LastName judul kolom dan EnrollmentDate .
  • Menggunakan informasi dalam NameSort dan DateSort untuk menyiapkan hyperlink dengan nilai susunan urutan saat ini.
  • Mengubah judul halaman dari Indeks ke Siswa.
  • Perubahan Model.Student pada Model.Students.

Untuk memverifikasi bahwa pengurutan berfungsi:

  • Jalankan aplikasi dan pilih tab Siswa .
  • Klik judul kolom.

Menambahkan pemfilteran

Untuk menambahkan pemfilteran ke halaman Indeks Siswa:

  • Kotak teks dan tombol kirim ditambahkan ke Razor Halaman. Kotak teks menyediakan string pencarian pada nama depan atau belakang.
  • Model halaman diperbarui untuk menggunakan nilai kotak teks.

Memperbarui metode OnGetAsync

Ganti kode dengan Students/Index.cshtml.cs kode berikut untuk menambahkan pemfilteran:

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

Kode sebelumnya:

  • searchString Menambahkan parameter ke OnGetAsync metode , dan menyimpan nilai parameter dalam CurrentFilter properti . Nilai string pencarian diterima dari kotak teks yang ditambahkan di bagian berikutnya.
  • Menambahkan ke klausa pernyataan Where LINQ. Klausa Where hanya memilih siswa yang nama depan atau nama belakangnya berisi string pencarian. Pernyataan LINQ dijalankan hanya jika ada nilai untuk dicari.

IQueryable vs. IEnumerable

Kode memanggil metode pada IQueryable objek, dan filter diproses Where di server. Dalam beberapa skenario, aplikasi mungkin memanggil Where metode sebagai metode ekstensi pada koleksi dalam memori. Misalnya, misalkan _context.Students perubahan dari EF CoreDbSet ke metode repositori yang mengembalikan IEnumerable koleksi. Hasilnya biasanya akan sama tetapi dalam beberapa kasus mungkin berbeda.

Misalnya, implementasi .NET Framework melakukan perbandingan Contains peka huruf besar/kecil secara default. Di SQL Server, Contains sensitivitas huruf besar/kecil ditentukan oleh pengaturan kolase instans SQL Server. SQL Server default menjadi tidak peka huruf besar/kecil. SQLite default ke peka huruf besar/kecil. ToUpper dapat dipanggil untuk membuat pengujian secara eksplisit tidak peka huruf besar/kecil:

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

Kode sebelumnya akan memastikan bahwa filter tidak peka huruf besar/kecil meskipun Where metode dipanggil pada IEnumerable atau berjalan di SQLite.

Ketika Contains dipanggil pada IEnumerable koleksi, implementasi .NET Core digunakan. Ketika Contains dipanggil pada IQueryable objek, implementasi database digunakan.

Panggilan Contains pada IQueryable biasanya lebih disukai karena alasan performa. Dengan IQueryable, pemfilteran dilakukan oleh server database. IEnumerable Jika dibuat terlebih dahulu, semua baris harus dikembalikan dari server database.

Ada penalti performa untuk memanggil ToUpper. Kode ToUpper menambahkan fungsi dalam klausa WHERE dari pernyataan TSQL SELECT. Fungsi yang ditambahkan mencegah pengoptimal menggunakan indeks. Mengingat bahwa SQL diinstal sebagai tidak peka huruf besar/kecil, sebaiknya hindari ToUpper panggilan saat tidak diperlukan.

Untuk informasi selengkapnya, lihat Cara menggunakan kueri yang tidak peka huruf besar/kecil dengan penyedia Sqlite.

Razor Memperbarui halaman

Ganti kode di Pages/Students/Index.cshtml untuk menambahkan tombol Cari .

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

Kode sebelumnya menggunakan pembantu <form> tag untuk menambahkan kotak teks pencarian dan tombol. Secara default, pembantu <form> tag mengirimkan data formulir dengan POST. Dengan POST, parameter diteruskan dalam isi pesan HTTP dan bukan di URL. Saat HTTP GET digunakan, data formulir diteruskan di URL sebagai string kueri. Meneruskan data dengan string kueri memungkinkan pengguna untuk menandai URL. Panduan W3C merekomendasikan bahwa GET harus digunakan saat tindakan tidak menghasilkan pembaruan.

Uji aplikasi:

  • Pilih tab Siswa dan masukkan string pencarian. Jika Anda menggunakan SQLite, filter tidak peka huruf besar/kecil hanya jika Anda menerapkan kode opsional ToUpper yang ditampilkan sebelumnya.

  • Pilih Telusuri.

Perhatikan bahwa URL berisi string pencarian. Contohnya:

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

Jika halaman diberi marka buku, marka buku berisi URL ke halaman dan SearchString string kueri. Dalam method="get"form tag adalah apa yang menyebabkan string kueri dihasilkan.

Saat ini, saat tautan pengurutan judul kolom dipilih, nilai filter dari kotak Pencarian hilang. Nilai filter yang hilang diperbaiki di bagian berikutnya.

Tambahkan halaman

Di bagian ini, PaginatedList kelas dibuat untuk mendukung halaman. Kelas PaginatedList menggunakan Skip pernyataan dan Take untuk memfilter data di server alih-alih mengambil semua baris tabel. Ilustrasi berikut menunjukkan tombol halaman.

Students index page with paging links

Membuat kelas PaginatedList

Di folder proyek, buat PaginatedList.cs dengan kode berikut:

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

Metode CreateAsync dalam kode sebelumnya mengambil ukuran halaman dan nomor halaman dan menerapkan pernyataan dan Take yang sesuai Skip ke IQueryable. Ketika ToListAsync dipanggil pada IQueryable, itu mengembalikan Daftar yang hanya berisi halaman yang diminta. Properti HasPreviousPage dan HasNextPage digunakan untuk mengaktifkan atau menonaktifkan tombol halaman Sebelumnya dan Berikutnya .

Metode CreateAsync ini digunakan untuk membuat PaginatedList<T>. Konstruktor tidak dapat membuat PaginatedList<T> objek; konstruktor tidak dapat menjalankan kode asinkron.

Menambahkan ukuran halaman ke konfigurasi

Tambahkan PageSize ke appsettings.jsonfile Konfigurasi :

{
  "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"
  }
}

Menambahkan halaman ke IndexModel

Ganti kode di Students/Index.cshtml.cs untuk menambahkan halaman.

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

Kode sebelumnya:

  • Mengubah jenis Students properti dari IList<Student> ke PaginatedList<Student>.
  • Menambahkan indeks halaman, , dan currentFilterOnGetAsync ke tanda tangan metode saat inisortOrder.
  • Menyimpan urutan pengurutan CurrentSort dalam properti .
  • Mereset indeks halaman ke 1 saat ada string pencarian baru.
  • PaginatedList Menggunakan kelas untuk mendapatkan entitas Siswa.
  • pageSize Atur ke 3 dari Konfigurasi, 4 jika konfigurasi gagal.

Semua parameter yang OnGetAsync diterima null ketika:

  • Halaman dipanggil dari tautan Siswa .
  • Pengguna belum mengklik tautan penomoran atau pengurutan.

Saat tautan halaman diklik, variabel indeks halaman berisi nomor halaman yang akan ditampilkan.

Properti CurrentSort menyediakan Razor Halaman dengan urutan pengurutan saat ini. Urutan pengurutan saat ini harus disertakan dalam tautan halaman untuk mempertahankan urutan pengurutan saat penomoran halaman.

Properti CurrentFilter menyediakan Razor Halaman dengan string filter saat ini. Nilai CurrentFilter :

  • Harus disertakan dalam tautan halaman untuk mempertahankan pengaturan filter selama halaman.
  • Harus dipulihkan ke kotak teks ketika halaman diputar ulang.

Jika string pencarian diubah saat halaman, halaman diatur ulang ke 1. Halaman harus direset ke 1 karena filter baru dapat menghasilkan data yang berbeda untuk ditampilkan. Saat nilai pencarian dimasukkan dan Kirim dipilih:

  • String pencarian diubah.
  • Parameter searchString tidak null.

Metode ini PaginatedList.CreateAsync mengonversi kueri siswa menjadi satu halaman siswa dalam jenis koleksi yang mendukung halaman. Halaman tunggal siswa tersebut diteruskan ke Razor Halaman.

Dua tanda tanya setelah pageIndex dalam PaginatedList.CreateAsync panggilan mewakili operator null-coalescing. Operator null-coalescing mendefinisikan nilai default untuk jenis nullable. Ekspresi pageIndex ?? 1 mengembalikan nilai pageIndex jika memiliki nilai, jika tidak, ekspresi mengembalikan 1.

Ganti kode dengan Students/Index.cshtml kode berikut. Perubahan disorot:

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

Tautan header kolom menggunakan string kueri untuk meneruskan string pencarian saat ini ke OnGetAsync metode :

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

Tombol halaman ditampilkan oleh pembantu tag:


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

Jalankan aplikasi dan navigasikan ke halaman siswa.

  • Untuk memastikan penomoran berfungsi, klik tautan halaman dalam urutan pengurutan yang berbeda.
  • Untuk memverifikasi bahwa penomoran berfungsi dengan benar dengan pengurutan dan pemfilteran, masukkan string pencarian dan coba penomoran halaman.

students index page with paging links

Pengelompokan

Bagian ini membuat About halaman yang menampilkan berapa banyak siswa yang telah mendaftar untuk setiap tanggal pendaftaran. Pembaruan menggunakan pengelompokan dan menyertakan langkah-langkah berikut:

  • Buat model tampilan untuk data yang digunakan oleh About halaman.
  • About Perbarui halaman untuk menggunakan model tampilan.

Membuat model tampilan

Buat folder Models/SchoolViewModels .

Buat SchoolViewModels/EnrollmentDateGroup.cs dengan kode berikut:

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 Membuat Halaman

Buat Pages/About.cshtml file dengan kode berikut:

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

Membuat model halaman

Pages/About.cshtml.cs Perbarui file dengan kode berikut:

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

Pernyataan LINQ mengelompokkan entitas siswa berdasarkan tanggal pendaftaran, menghitung jumlah entitas di setiap grup, dan menyimpan hasilnya dalam kumpulan EnrollmentDateGroup objek model tampilan.

Jalankan aplikasi dan navigasikan ke halaman Tentang. Jumlah siswa untuk setiap tanggal pendaftaran ditampilkan dalam tabel.

About page

Langkah berikutnya

Dalam tutorial berikutnya, aplikasi menggunakan migrasi untuk memperbarui model data.

Dalam tutorial ini, pengurutan, pemfilteran, pengelompokan, dan penomoran, fungsionalitas ditambahkan.

Ilustrasi berikut ini memperlihatkan halaman yang telah selesai. Judul kolom adalah tautan yang dapat diklik untuk mengurutkan kolom. Mengklik judul kolom berulang kali beralih antara urutan urutan naik dan turun.

Students index page

Jika Anda mengalami masalah yang tidak dapat Anda selesaikan, unduh aplikasi yang telah selesai.

Menambahkan pengurutan ke halaman Indeks

Tambahkan string ke Students/Index.cshtml.csPageModel untuk berisi parameter pengurutan:

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 Perbarui dengan kode berikut:

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

Kode sebelumnya menerima sortOrder parameter dari string kueri di URL. URL (termasuk string kueri) dihasilkan oleh Pembantu Tag Jangkar

Parameternya sortOrder adalah "Nama" atau "Tanggal." Parameter sortOrder secara opsional diikuti oleh "_desc" untuk menentukan urutan turun. Urutan sortir default adalah menaik.

Saat halaman Indeks diminta dari tautan Siswa , tidak ada string kueri. Siswa ditampilkan dalam urutan naik berdasarkan nama belakang. Urutan naik menurut nama belakang adalah default (kasus fall-through) dalam switch pernyataan. Saat pengguna mengklik tautan judul kolom, nilai yang sesuai sortOrder disediakan dalam nilai string kueri.

NameSort dan DateSort digunakan oleh Razor Halaman untuk mengonfigurasi hyperlink judul kolom dengan nilai string kueri yang sesuai:

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

Kode berikut berisi operator C# kondisi? :

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

Baris pertama menentukan bahwa kapan sortOrder null atau kosong, NameSort diatur ke "name_desc." Jika sortOrder tidak null atau kosong, NameSort diatur ke string kosong.

ini ?: operator juga dikenal sebagai operator ternary.

Kedua pernyataan ini memungkinkan halaman untuk mengatur hyperlink judul kolom sebagai berikut:

Urutan pengurutan saat ini Hyperlink Nama Belakang Hyperlink Tanggal
Nama Belakang naik Urut turun urutan naik
Nama Belakang turun urutan naik urutan naik
Tanggal naik urutan naik Urut turun
Tanggal turun urutan naik urutan naik

Metode ini menggunakan LINQ ke Entitas untuk menentukan kolom yang akan diurutkan. Kode menginisialisasi IQueryable<Student> sebelum pernyataan pengalihan, dan memodifikasinya dalam pernyataan pengalihan:

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 Saat dibuat atau dimodifikasi, tidak ada kueri yang dikirim ke database. Kueri tidak dijalankan sampai objek dikonversi IQueryable menjadi koleksi. IQueryable dikonversi ke koleksi dengan memanggil metode seperti ToListAsync. Oleh karena itu, IQueryable kode menghasilkan satu kueri yang tidak dijalankan hingga pernyataan berikut:

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

OnGetAsync bisa mendapatkan verbose dengan sejumlah besar kolom yang dapat diurutkan.

Ganti kode di Students/Index.cshtml, dengan kode yang disorot berikut:

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

Kode sebelumnya:

  • Menambahkan hyperlink ke LastName judul kolom dan EnrollmentDate .
  • Menggunakan informasi dalam NameSort dan DateSort untuk menyiapkan hyperlink dengan nilai susunan urutan saat ini.

Untuk memverifikasi bahwa pengurutan berfungsi:

  • Jalankan aplikasi dan pilih tab Siswa .
  • Klik Nama Belakang.
  • Klik Tanggal Pendaftaran.

Untuk mendapatkan pemahaman yang lebih baik tentang kode:

  • Di Students/Index.cshtml.cs, atur titik henti pada switch (sortOrder).
  • Tambahkan jam tangan untuk NameSort dan DateSort.
  • Di Students/Index.cshtml, atur titik henti pada @Html.DisplayNameFor(model => model.Student[0].LastName).

Menelusuri debugger.

Menambahkan Kotak Pencarian ke halaman Indeks Siswa

Untuk menambahkan pemfilteran ke halaman Indeks Siswa:

  • Kotak teks dan tombol kirim ditambahkan ke Razor Halaman. Kotak teks menyediakan string pencarian pada nama depan atau belakang.
  • Model halaman diperbarui untuk menggunakan nilai kotak teks.

Menambahkan fungsionalitas pemfilteran ke metode Indeks

Students/Index.cshtml.csOnGetAsync Perbarui dengan kode berikut:

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

Kode sebelumnya:

  • searchString Menambahkan parameter ke OnGetAsync metode . Nilai string pencarian diterima dari kotak teks yang ditambahkan di bagian berikutnya.
  • Ditambahkan ke klausa pernyataan Where LINQ. Klausa Where hanya memilih siswa yang nama depan atau nama belakangnya berisi string pencarian. Pernyataan LINQ dijalankan hanya jika ada nilai untuk dicari.

Catatan: Kode sebelumnya memanggil metode pada IQueryable objek, dan filter diproses Where di server. Dalam beberapa skenario, aplikasi mungkin memanggil Where metode sebagai metode ekstensi pada koleksi dalam memori. Misalnya, misalkan _context.Students perubahan dari EF CoreDbSet ke metode repositori yang mengembalikan IEnumerable koleksi. Hasilnya biasanya akan sama tetapi dalam beberapa kasus mungkin berbeda.

Misalnya, implementasi .NET Framework melakukan perbandingan Contains peka huruf besar/kecil secara default. Di SQL Server, Contains sensitivitas huruf besar/kecil ditentukan oleh pengaturan kolase instans SQL Server. SQL Server default menjadi tidak peka huruf besar/kecil. ToUpper dapat dipanggil untuk membuat pengujian secara eksplisit tidak peka huruf besar/kecil:

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

Kode sebelumnya akan memastikan bahwa hasilnya tidak peka huruf besar/kecil jika kode berubah untuk menggunakan IEnumerable. Ketika Contains dipanggil pada IEnumerable koleksi, implementasi .NET Core digunakan. Ketika Contains dipanggil pada IQueryable objek, implementasi database digunakan. Mengembalikan IEnumerable dari repositori dapat memiliki penalti performa yang signifikan:

  1. Semua baris dikembalikan dari server DB.
  2. Filter diterapkan ke semua baris yang dikembalikan dalam aplikasi.

Ada penalti performa untuk memanggil ToUpper. Kode ToUpper menambahkan fungsi dalam klausa WHERE dari pernyataan TSQL SELECT. Fungsi yang ditambahkan mencegah pengoptimal menggunakan indeks. Mengingat bahwa SQL diinstal sebagai tidak peka huruf besar/kecil, sebaiknya hindari ToUpper panggilan saat tidak diperlukan.

Menambahkan Kotak Pencarian ke halaman Indeks Siswa

Di Pages/Students/Index.cshtml, tambahkan kode yang disorot berikut untuk membuat tombol Pencarian dan krom bermakna.

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

Kode sebelumnya menggunakan pembantu <form> tag untuk menambahkan kotak teks pencarian dan tombol. Secara default, pembantu <form> tag mengirimkan data formulir dengan POST. Dengan POST, parameter diteruskan dalam isi pesan HTTP dan bukan di URL. Saat HTTP GET digunakan, data formulir diteruskan di URL sebagai string kueri. Meneruskan data dengan string kueri memungkinkan pengguna untuk menandai URL. Panduan W3C merekomendasikan bahwa GET harus digunakan saat tindakan tidak menghasilkan pembaruan.

Uji aplikasi:

  • Pilih tab Siswa dan masukkan string pencarian.
  • Pilih Telusuri.

Perhatikan bahwa URL berisi string pencarian.

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

Jika halaman diberi marka buku, marka buku berisi URL ke halaman dan SearchString string kueri. Dalam method="get"form tag adalah apa yang menyebabkan string kueri dihasilkan.

Saat ini, saat tautan pengurutan judul kolom dipilih, nilai filter dari kotak Pencarian hilang. Nilai filter yang hilang diperbaiki di bagian berikutnya.

Menambahkan fungsionalitas halaman ke halaman Indeks Siswa

Di bagian ini, PaginatedList kelas dibuat untuk mendukung halaman. Kelas PaginatedList menggunakan Skip pernyataan dan Take untuk memfilter data di server alih-alih mengambil semua baris tabel. Ilustrasi berikut menunjukkan tombol halaman.

Students index page with paging links

Di folder proyek, buat PaginatedList.cs dengan kode berikut:

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

Metode CreateAsync dalam kode sebelumnya mengambil ukuran halaman dan nomor halaman dan menerapkan pernyataan dan Take yang sesuai Skip ke IQueryable. Ketika ToListAsync dipanggil pada IQueryable, itu mengembalikan Daftar yang hanya berisi halaman yang diminta. Properti HasPreviousPage dan HasNextPage digunakan untuk mengaktifkan atau menonaktifkan tombol halaman Sebelumnya dan Berikutnya .

Metode CreateAsync ini digunakan untuk membuat PaginatedList<T>. Konstruktor tidak dapat membuat PaginatedList<T> objek, konstruktor tidak dapat menjalankan kode asinkron.

Menambahkan fungsionalitas halaman ke metode Indeks

Di Students/Index.cshtml.cs, perbarui jenis dari StudentIList<Student> ke PaginatedList<Student>:

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

Students/Index.cshtml.csOnGetAsync Perbarui dengan kode berikut:

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

Kode sebelumnya menambahkan indeks halaman, saat ini sortOrder, dan ke currentFilter tanda tangan metode.

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

Semua parameter null ketika:

  • Halaman dipanggil dari tautan Siswa .
  • Pengguna belum mengklik tautan penomoran atau pengurutan.

Saat tautan halaman diklik, variabel indeks halaman berisi nomor halaman yang akan ditampilkan.

CurrentSortRazor menyediakan Halaman dengan urutan pengurutan saat ini. Urutan pengurutan saat ini harus disertakan dalam tautan halaman untuk mempertahankan urutan pengurutan saat penomoran halaman.

CurrentFilterRazor menyediakan Halaman dengan string filter saat ini. Nilai CurrentFilter :

  • Harus disertakan dalam tautan halaman untuk mempertahankan pengaturan filter selama halaman.
  • Harus dipulihkan ke kotak teks ketika halaman diputar ulang.

Jika string pencarian diubah saat halaman, halaman diatur ulang ke 1. Halaman harus direset ke 1 karena filter baru dapat menghasilkan data yang berbeda untuk ditampilkan. Saat nilai pencarian dimasukkan dan Kirim dipilih:

  • String pencarian diubah.
  • Parameter searchString tidak null.
if (searchString != null)
{
    pageIndex = 1;
}
else
{
    searchString = currentFilter;
}

Metode ini PaginatedList.CreateAsync mengonversi kueri siswa menjadi satu halaman siswa dalam jenis koleksi yang mendukung halaman. Halaman tunggal siswa tersebut diteruskan ke Razor Halaman.

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

Dua tanda tanya mewakili PaginatedList.CreateAsyncoperator null-coalescing. Operator null-coalescing mendefinisikan nilai default untuk jenis nullable. Ekspresi (pageIndex ?? 1) berarti mengembalikan nilai pageIndex jika memiliki nilai. Jika pageIndex tidak memiliki nilai, kembalikan 1.

Perbarui markup di Students/Index.cshtml. Perubahan disorot:

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

Tautan header kolom menggunakan string kueri untuk meneruskan string pencarian saat ini ke OnGetAsync metode sehingga pengguna dapat mengurutkan dalam hasil filter:

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

Tombol halaman ditampilkan oleh pembantu tag:


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

Jalankan aplikasi dan navigasikan ke halaman siswa.

  • Untuk memastikan penomoran berfungsi, klik tautan halaman dalam urutan pengurutan yang berbeda.
  • Untuk memverifikasi bahwa penomoran berfungsi dengan benar dengan pengurutan dan pemfilteran, masukkan string pencarian dan coba penomoran halaman.

students index page with paging links

Untuk mendapatkan pemahaman yang lebih baik tentang kode:

  • Di Students/Index.cshtml.cs, atur titik henti pada switch (sortOrder).
  • Tambahkan jam tangan untuk NameSort, DateSort, CurrentSort, dan Model.Student.PageIndex.
  • Di Students/Index.cshtml, atur titik henti pada @Html.DisplayNameFor(model => model.Student[0].LastName).

Menelusuri debugger.

Perbarui halaman Tentang untuk menampilkan statistik siswa

Dalam langkah ini, Pages/About.cshtml diperbarui untuk menampilkan berapa banyak siswa yang telah mendaftar untuk setiap tanggal pendaftaran. Pembaruan menggunakan pengelompokan dan menyertakan langkah-langkah berikut:

  • Buat model tampilan untuk data yang digunakan oleh Halaman Tentang .
  • Perbarui halaman Tentang untuk menggunakan model tampilan.

Membuat model tampilan

Buat folder SchoolViewModels di folder Model .

Di folder SchoolViewModels, tambahkan EnrollmentDateGroup.cs dengan kode berikut:

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

Memperbarui model halaman Tentang

Templat web di ASP.NET Core 2.2 tidak menyertakan halaman Tentang. Jika Anda menggunakan ASP.NET Core 2.2, buat Halaman Tentang Razor .

Pages/About.cshtml.cs Perbarui file dengan kode berikut:

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

Pernyataan LINQ mengelompokkan entitas siswa berdasarkan tanggal pendaftaran, menghitung jumlah entitas di setiap grup, dan menyimpan hasilnya dalam kumpulan EnrollmentDateGroup objek model tampilan.

Ubah Halaman Tentang Razor

Ganti kode dalam Pages/About.cshtml file dengan kode berikut:

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

Jalankan aplikasi dan navigasikan ke halaman Tentang. Jumlah siswa untuk setiap tanggal pendaftaran ditampilkan dalam tabel.

Jika Anda mengalami masalah yang tidak dapat Anda selesaikan, unduh aplikasi yang telah selesai untuk tahap ini.

About page

Sumber Daya Tambahan:

Dalam tutorial berikutnya, aplikasi menggunakan migrasi untuk memperbarui model data.