Pengantar Razor Pages di ASP.NET Core

Oleh Rick Anderson, Dave Brock, dan Kirk Larkin

Catatan

Ini bukan versi terbaru dari artikel ini. Untuk rilis saat ini, lihat versi .NET 8 dari artikel ini.

Penting

Informasi ini berkaitan dengan produk pra-rilis yang mungkin dimodifikasi secara substansial sebelum dirilis secara komersial. Microsoft tidak memberikan jaminan, tersirat maupun tersurat, sehubungan dengan informasi yang diberikan di sini.

Untuk rilis saat ini, lihat versi .NET 8 dari artikel ini.

Razor Pages dapat membuat pengodean skenario yang berfokus pada halaman lebih mudah dan lebih produktif daripada menggunakan pengontrol dan tampilan.

Jika Anda mencari tutorial yang menggunakan pendekatan Model-View-Controller, lihat Mulai menggunakan MVC ASP.NET Core.

Dokumen ini memberikan pengantar untuk Razor Pages. Ini bukan tutorial langkah demi langkah. Jika Anda menganggap bahwa beberapa bagian sulit untuk dipahami, lihat Mulai menggunakan Razor Pages. Untuk gambaran umum ASP.NET Core, lihat Pengantar ASP.NET Core.

Prasyarat

Buat proyek Razor Pages

Lihat Mulai menggunakan Razor Pages untuk petunjuk terperinci tentang cara membuat proyek Razor Pages.

Razor Pages

Razor Pages diaktifkan di Program.cs:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

Dalam kode sebelumnya:

Pertimbangkan halaman dasar:

@page

<h1>Hello, world!</h1>
<h2>The time on the server is @DateTime.Now</h2>

Kode sebelumnya sangat mirip dengan file tampilan Razor yang digunakan dalam aplikasi ASP.NET Core dengan pengontrol dan tampilan. Yang membuatnya berbeda adalah arahan @page. @page membuat file menjadi tindakan MVC, yang berarti ini menangani permintaan secara langsung, tanpa melalui pengontrol. @page harus menjadi arahan Razor pertama di halaman. @page memengaruhi perilaku konstruksi Razor lainnya. Nama file Razor Pages memiliki akhiran .cshtml.

Halaman serupa, menggunakan kelas PageModel, ditampilkan dalam dua file berikut. File Pages/Index2.cshtml:

@page
@using RazorPagesIntro.Pages
@model Index2Model

<h2>Separate page model</h2>
<p>
    @Model.Message
</p>

Model halaman Pages/Index2.cshtml.cs:

using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
using System;

namespace RazorPagesIntro.Pages
{
    public class Index2Model : PageModel
    {
        public string Message { get; private set; } = "PageModel in C#";

        public void OnGet()
        {
            Message += $" Server time is { DateTime.Now }";
        }
    }
}

Menurut konvensi, file kelas PageModel memiliki nama yang sama dengan file Razor Pages dengan .cs yang ditambahkan. Misalnya, Razor Pages sebelumnya adalah Pages/Index2.cshtml. File yang berisi kelas PageModel diberi nama Pages/Index2.cshtml.cs.

Asosiasi jalur URL ke halaman ditentukan oleh lokasi halaman di sistem file. Tabel berikut menunjukkan jalur Razor Pages dan URL yang cocok:

Nama file dan jalur URL yang cocok
/Pages/Index.cshtml / atau /Index
/Pages/Contact.cshtml /Contact
/Pages/Store/Contact.cshtml /Store/Contact
/Pages/Store/Index.cshtml /Store atau /Store/Index

Catatan:

  • Runtime bahasa umum mencari file Razor Pages dalam folder Pages secara default.
  • Index adalah halaman bawaan jika URL tidak menyertakan halaman.

Menulis formulir dasar

Razor Pages dirancang untuk menjadikan pola umum yang digunakan dengan browser web mudah diimplementasikan saat membuat aplikasi. Pengikatan model, Pembantu Tag, dan pembantu HTML bekerja dengan properti yang ditentukan dalam kelas Razor Page. Pertimbangkan halaman yang mengimplementasikan formulir "hubungi kami" dasar untuk model Contact:

Untuk sampel dalam dokumen ini, DbContext diinisialisasi dalam file Program.cs .

Database dalam memori memerlukan paket NuGet Microsoft.EntityFrameworkCore.InMemory.

using Microsoft.EntityFrameworkCore;
using RazorPagesContacts.Data;
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddDbContext<CustomerDbContext>(options =>
    options.UseInMemoryDatabase("name"));

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

Model data:

using System.ComponentModel.DataAnnotations;

namespace RazorPagesContacts.Models
{
    public class Customer
    {
        public int Id { get; set; }

        [Required, StringLength(10)]
        public string? Name { get; set; }
    }
}

Konteks db:

using Microsoft.EntityFrameworkCore;

namespace RazorPagesContacts.Data
{
    public class CustomerDbContext : DbContext
    {
        public CustomerDbContext (DbContextOptions<CustomerDbContext> options)
            : base(options)
        {
        }

        public DbSet<RazorPagesContacts.Models.Customer> Customer => Set<RazorPagesContacts.Models.Customer>();
    }
}

File tampilan Pages/Customers/Create.cshtml:

@page
@model RazorPagesContacts.Pages.Customers.CreateModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<p>Enter a customer name:</p>

<form method="post">
    Name:
    <input asp-for="Customer!.Name" />
    <input type="submit" />
</form>

Model halaman Pages/Customers/Create.cshtml.cs:

public class CreateModel : PageModel
{
    private readonly Data.CustomerDbContext _context;

    public CreateModel(Data.CustomerDbContext context)
    {
        _context = context;
    }

    public IActionResult OnGet()
    {
        return Page();
    }

    [BindProperty]
    public Customer? Customer { get; set; }

    public async Task<IActionResult> OnPostAsync()
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }

        if (Customer != null) _context.Customer.Add(Customer);
        await _context.SaveChangesAsync();

        return RedirectToPage("./Index");
    }
}

Berdasarkan konvensi, kelas PageModel disebut <PageName>Model dan berada dalam namespace layanan yang sama dengan halaman.

Kelas PageModel memungkinkan pemisahan logika halaman dari presentasinya. Ini mendefinisikan penanganan halaman untuk permintaan yang dikirim ke halaman dan data yang digunakan untuk merender halaman. Pemisahan ini memungkinkan:

Halaman memiliki OnPostAsynchandler method, yang berjalan pada permintaan POST (ketika pengguna memposting formulir). Metode penanganan untuk kata kerja HTTP apa pun dapat ditambahkan. Penanganan yang paling umum adalah:

  • OnGet untuk menginisialisasi status yang diperlukan halaman. Pada kode sebelumnya, metode OnGet menampilkan CreateModel.cshtmlRazor Page.
  • OnPost untuk menangani pengiriman formulir.

Akhiran penamaan Async sifatnya opsional tetapi sering digunakan oleh konvensi untuk fungsi asinkron. Kode sebelumnya umum untuk Razor Pages.

Jika Anda terbiasa dengan aplikasi ASP.NET yang menggunakan pengontrol dan tampilan:

  • Kode OnPostAsync dalam contoh sebelumnya terlihat mirip dengan kode pengontrol biasa.
  • Sebagian besar primitif MVC seperti pengikatan model, validasi, dan hasil tindakan bekerja dengan cara yang sama seperti Pengontrol dan Razor Pages.

Metode OnPostAsync sebelumnya:

[BindProperty]
public Customer? Customer { get; set; }

public async Task<IActionResult> OnPostAsync()
{
    if (!ModelState.IsValid)
    {
        return Page();
    }

    if (Customer != null) _context.Customer.Add(Customer);
    await _context.SaveChangesAsync();

    return RedirectToPage("./Index");
}

Alur dasar OnPostAsync:

Periksa kesalahan validasi.

  • Jika tidak ada kesalahan, simpan data dan alihkan.
  • Jika ada kesalahan, tampilkan kembali halaman dengan pesan validasi. Dalam banyak kasus, kesalahan validasi akan terdeteksi pada klien, dan tidak pernah dikirimkan ke server.

File tampilan Pages/Customers/Create.cshtml:

@page
@model RazorPagesContacts.Pages.Customers.CreateModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<p>Enter a customer name:</p>

<form method="post">
    Name:
    <input asp-for="Customer!.Name" />
    <input type="submit" />
</form>

HTML yang dirender dari Pages/Customers/Create.cshtml:

<p>Enter a customer name:</p>

<form method="post">
    Name:
    <input type="text" data-val="true"
           data-val-length="The field Name must be a string with a maximum length of 10."
           data-val-length-max="10" data-val-required="The Name field is required."
           id="Customer_Name" maxlength="10" name="Customer.Name" value="" />
    <input type="submit" />
    <input name="__RequestVerificationToken" type="hidden"
           value="<Antiforgery token here>" />
</form>

Dalam kode sebelumnya, posting formulir:

  • Dengan data yang valid:

    • Metode penanganan OnPostAsync memanggil metode pembantu RedirectToPage. RedirectToPage mengembalikan instans dari RedirectToPageResult. RedirectToPage:

      • Merupakan hasil tindakan.
      • Mirip dengan RedirectToAction atau RedirectToRoute (digunakan dalam pengontrol dan tampilan).
      • Dikustomisasi untuk halaman. Dalam sampel sebelumnya, ini dialihkan ke halaman Indeks akar (/Index). RedirectToPage dijelaskan lebih lanjut di bagian pembuatan URL untuk Pages.
  • Dengan kesalahan validasi yang diteruskan ke server:

    • Metode penanganan OnPostAsync memanggil metode pembantu Page. Page mengembalikan instans dari PageResult. Pengembalian Page mirip dengan bagaimana tindakan di pengontrol mengembalikan View. PageResult adalah jenis pengembalian default untuk metode penanganan. Metode penanganan yang mengembalikan void merender halaman.
    • Pada contoh sebelumnya, memposting formulir tanpa nilai akan menyebabkan ModelState.IsValid mengembalikan false. Dalam sampel ini, tidak ada kesalahan validasi yang ditampilkan pada klien. Penanganan kesalahan validasi dibahas nanti dalam dokumen ini.
    [BindProperty]
    public Customer? Customer { get; set; }
    
    public async Task<IActionResult> OnPostAsync()
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }
    
        if (Customer != null) _context.Customer.Add(Customer);
        await _context.SaveChangesAsync();
    
        return RedirectToPage("./Index");
    }
    
  • Dengan kesalahan validasi yang terdeteksi oleh validasi sisi klien:

    • Data tidak diposting ke server.
    • Validasi sisi klien dijelaskan nanti dalam dokumen ini.

Properti Customer menggunakan atribut [BindProperty] untuk ikut serta dalam pengikatan model:

[BindProperty]
public Customer? Customer { get; set; }

public async Task<IActionResult> OnPostAsync()
{
    if (!ModelState.IsValid)
    {
        return Page();
    }

    if (Customer != null) _context.Customer.Add(Customer);
    await _context.SaveChangesAsync();

    return RedirectToPage("./Index");
}

[BindProperty]tidak boleh digunakan pada model yang berisi properti yang tidak boleh diubah oleh klien. Untuk informasi lebih lanjut, lihat Overposting.

Razor Pages, secara default, hanya mengikat properti dengan kata kerja non-GET. Pengikatan ke properti menghilangkan kebutuhan untuk menulis kode guna mengonversi data HTTP ke jenis model. Pengikatan mengurangi kode dengan menggunakan properti yang sama untuk merender bidang isian borang (<input asp-for="Customer.Name">) dan menerima input.

Peringatan

Karena alasan keamanan, Anda harus memilih untuk mengikat GET data permintaan ke properti model halaman. Verifikasi input pengguna sebelum memetakannya ke properti. Memilih pengikatan GET berguna saat menangani skenario yang mengandalkan string kueri atau nilai rute.

Untuk mengikat properti pada permintaan GET, atur properti SupportsGet atribut [BindProperty] ke true:

[BindProperty(SupportsGet = true)]

Untuk informasi lebih lanjut, lihat ASP.NET Core Community Standup: Bind on GET discussion (YouTube).

Meninjau file tampilan Pages/Customers/Create.cshtml:

@page
@model RazorPagesContacts.Pages.Customers.CreateModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<p>Enter a customer name:</p>

<form method="post">
    Name:
    <input asp-for="Customer!.Name" />
    <input type="submit" />
</form>
  • Dalam kode sebelumnya, pembantu tag input<input asp-for="Customer.Name" /> mengikat elemen <input> HTML ke ekspresi model Customer.Name.
  • @addTagHelper membuat Pembantu Tag tersedia.

Beranda

Index.cshtml adalah beranda:

@page
@model RazorPagesContacts.Pages.Customers.IndexModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<h1>Contacts home page</h1>
<form method="post">
    <table class="table">
        <thead>
            <tr>
                <th>ID</th>
                <th>Name</th>
                <th></th>
            </tr>
        </thead>
        <tbody>
        @if (Model.Customers != null)
        {
            foreach (var contact in Model.Customers)
            {
                <tr>
                    <td> @contact.Id </td>
                    <td>@contact.Name</td>
                    <td>
                        <!-- <snippet_Edit> -->
                        <a asp-page="./Edit" asp-route-id="@contact.Id">Edit</a> |
                        <!-- </snippet_Edit> -->
                        <!-- <snippet_Delete> -->
                        <button type="submit" asp-page-handler="delete" asp-route-id="@contact.Id">delete</button>
                        <!-- </snippet_Delete> -->
                    </td>
                </tr>
            }
        }
        </tbody>
    </table>
    <a asp-page="Create">Create New</a>
</form>

Kelas PageModel terkait (Index.cshtml.cs):

public class IndexModel : PageModel
{
    private readonly Data.CustomerDbContext _context;
    public IndexModel(Data.CustomerDbContext context)
    {
        _context = context;
    }

    public IList<Customer>? Customers { get; set; }

    public async Task OnGetAsync()
    {
        Customers = await _context.Customer.ToListAsync();
    }

    public async Task<IActionResult> OnPostDeleteAsync(int id)
    {
        var contact = await _context.Customer.FindAsync(id);

        if (contact != null)
        {
            _context.Customer.Remove(contact);
            await _context.SaveChangesAsync();
        }

        return RedirectToPage();
    }
}

File Index.cshtml berisi markup berikut:

<a asp-page="./Edit" asp-route-id="@contact.Id">Edit</a> |

<a /a>Pembantu Tag Jangkar menggunakan atribut asp-route-{value} untuk membuat tautan ke halaman Edit. Tautan berisi data rute dengan ID kontak. Contohnya,https://localhost:5001/Edit/1. Pembantu Tag mengaktifkan kode sisi server untuk berpartisipasi dalam membuat dan merender elemen HTML dalam file Razor.

File Index.cshtml berisi markup untuk membuat tombol hapus untuk setiap kontak pelanggan:

<button type="submit" asp-page-handler="delete" asp-route-id="@contact.Id">delete</button>

HTML yang dirender:

<button type="submit" formaction="/Customers?id=1&amp;handler=delete">delete</button>

Saat tombol hapus ditampilkan dalam HTML, formaction-nya menyertakan parameter untuk:

  • ID kontak pelanggan, ditentukan oleh atribut asp-route-id.
  • handler, ditentukan oleh atribut asp-page-handler.

Saat tombol dipilih, permintaan POST formulir dikirim ke server. Berdasarkan konvensi, nama metode penanganan dipilih berdasarkan nilai parameter handler menurut skema OnPost[handler]Async.

Karena handler adalah delete dalam contoh ini, metode penanganan OnPostDeleteAsync digunakan untuk memproses permintaan POST. Jika asp-page-handler diatur ke nilai yang berbeda, seperti remove, metode penanganan dengan nama OnPostRemoveAsync dipilih.

public async Task<IActionResult> OnPostDeleteAsync(int id)
{
    var contact = await _context.Customer.FindAsync(id);

    if (contact != null)
    {
        _context.Customer.Remove(contact);
        await _context.SaveChangesAsync();
    }

    return RedirectToPage();
}

Metode OnPostDeleteAsync:

  • Mendapatkan id dari string kueri.
  • Kueri database untuk kontak pelanggan dengan FindAsync.
  • Jika kontak pelanggan ditemukan, ini akan dihapus dan database diperbarui.
  • Memanggil RedirectToPage untuk mengalihkan ke halaman Indeks akar (/Index).

File Edit.cshtml

@page "{id:int}"
@model RazorPagesContacts.Pages.Customers.EditModel

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

<h1>Edit</h1>

<h4>Customer</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form method="post">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <input type="hidden" asp-for="Customer!.Id" />
            <div class="form-group">
                <label asp-for="Customer!.Name" class="control-label"></label>
                <input asp-for="Customer!.Name" class="form-control" />
                <span asp-validation-for="Customer!.Name" class="text-danger"></span>
            </div>
            <div class="form-group">
                <input type="submit" value="Save" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-page="./Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

Baris pertama berisi arahan @page "{id:int}". Batasan perutean "{id:int}" memberi tahu halaman untuk menerima permintaan ke halaman yang berisi data rute int. Jika permintaan ke halaman tidak berisi data rute yang dapat dikonversi ke int, runtime bahasa umum mengembalikan kesalahan HTTP 404 (tidak ditemukan). Untuk menjadikan ID bersifat opsional, tambahkan ? ke batasan rute:

@page "{id:int?}"

File Edit.cshtml.cs:

public class EditModel : PageModel
{
    private readonly RazorPagesContacts.Data.CustomerDbContext _context;

    public EditModel(RazorPagesContacts.Data.CustomerDbContext context)
    {
        _context = context;
    }

    [BindProperty]
    public Customer? Customer { get; set; }

    public async Task<IActionResult> OnGetAsync(int? id)
    {
        if (id == null)
        {
            return NotFound();
        }

        Customer = await _context.Customer.FirstOrDefaultAsync(m => m.Id == id);
        
        if (Customer == null)
        {
            return NotFound();
        }
        return Page();
    }

    // To protect from overposting attacks, enable the specific properties you want to bind to.
    // For more details, see https://aka.ms/RazorPagesCRUD.
    public async Task<IActionResult> OnPostAsync()
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }

        if (Customer != null)
        {
            _context.Attach(Customer).State = EntityState.Modified;

            try
            {
                await _context.SaveChangesAsync();
            }
            catch (DbUpdateConcurrencyException)
            {
                if (!CustomerExists(Customer.Id))
                {
                    return NotFound();
                }
                else
                {
                    throw;
                }
            }
        }

        return RedirectToPage("./Index");
    }

    private bool CustomerExists(int id)
    {
        return _context.Customer.Any(e => e.Id == id);
    }
}

Validasi

Aturan validasi:

  • Secara deklaratif ditentukan dalam kelas model.
  • Diberlakukan di mana-mana di aplikasi.

Namespace layanan System.ComponentModel.DataAnnotations menyediakan satu set atribut validasi bawaan yang diterapkan secara deklaratif ke kelas atau properti. DataAnnotations juga berisi atribut pemformatan seperti [DataType] yang membantu pemformatan dan tidak memberikan validasi apa pun.

Pertimbangkan model Customer:

using System.ComponentModel.DataAnnotations;

namespace RazorPagesContacts.Models
{
    public class Customer
    {
        public int Id { get; set; }

        [Required, StringLength(10)]
        public string? Name { get; set; }
    }
}

Dengan file tampilan Create.cshtml berikut:

@page
@model RazorPagesContacts.Pages.Customers.CreateModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<p>Validation: customer name:</p>

<form method="post">
    <div asp-validation-summary="ModelOnly"></div>
    <span asp-validation-for="Customer!.Name"></span>
    Name:
    <input asp-for="Customer!.Name" />
    <input type="submit" />
</form>

<script src="~/lib/jquery/dist/jquery.js"></script>
<script src="~/lib/jquery-validation/dist/jquery.validate.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"></script>

Kode sebelumnya:

  • Termasuk skrip validasi jQuery dan jQuery.

  • Menggunakan <div /> dan <span />Pembantu Tag untuk mengaktifkan:

    • Validasi sisi klien.
    • Perenderan kesalahan validasi.
  • Menghasilkan HTML berikut:

    <p>Enter a customer name:</p>
    
    <form method="post">
        Name:
        <input type="text" data-val="true"
               data-val-length="The field Name must be a string with a maximum length of 10."
               data-val-length-max="10" data-val-required="The Name field is required."
               id="Customer_Name" maxlength="10" name="Customer.Name" value="" />
        <input type="submit" />
        <input name="__RequestVerificationToken" type="hidden"
               value="<Antiforgery token here>" />
    </form>
    
    <script src="/lib/jquery/dist/jquery.js"></script>
    <script src="/lib/jquery-validation/dist/jquery.validate.js"></script>
    <script src="/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"></script>
    

Memposting formulir Buat tanpa nilai nama akan menampilkan pesan kesalahan "Bidang Nama diperlukan." pada formulir. Jika JavaScript diaktifkan pada klien, browser menampilkan kesalahan tanpa memposting ke server.

Atribut [StringLength(10)] menghasilkan data-val-length-max="10" pada HTML yang dirender. data-val-length-max mencegah browser agar tidak memasukkan lebih dari panjang maksimum yang ditentukan. Jika alat seperti Fiddler digunakan untuk mengedit dan memutar kembali pos:

  • Dengan nama yang melebihi 10.
  • Pesan kesalahan "Nama bidang harus berupa string dengan panjang maksimum 10." dikembalikan.

Perhatikan model Movie berikut:

using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace RazorPagesMovie.Models
{
    public class Movie
    {
        public int ID { get; set; }

        [StringLength(60, MinimumLength = 3)]
        [Required]
        public string Title { get; set; }

        [Display(Name = "Release Date")]
        [DataType(DataType.Date)]
        public DateTime ReleaseDate { get; set; }

        [Range(1, 100)]
        [DataType(DataType.Currency)]
        [Column(TypeName = "decimal(18, 2)")]
        public decimal Price { get; set; }

        [RegularExpression(@"^[A-Z]+[a-zA-Z\s]*$")]
        [Required]
        [StringLength(30)]
        public string Genre { get; set; }

        [RegularExpression(@"^[A-Z]+[a-zA-Z0-9""'\s-]*$")]
        [StringLength(5)]
        [Required]
        public string Rating { get; set; }
    }
}

Atribut validasi menentukan perilaku yang akan diberlakukan pada properti model tempatnya diterapkan:

  • Atribut Required dan MinimumLength menunjukkan bahwa properti harus memiliki nilai, tetapi tidak ada yang mencegah pengguna memasukkan spasi kosong untuk memenuhi validasi ini.

  • Atribut RegularExpression digunakan untuk membatasi karakter apa yang dapat dimasukkan. Dalam kode sebelumnya, "Genre":

    • Hanya boleh menggunakan huruf.
    • Huruf pertama harus huruf besar. Spasi kosong, angka, dan karakter khusus tidak diperbolehkan.
  • RegularExpression "Rating":

    • Mengharuskan karakter pertama berupa huruf besar.
    • Mengizinkan karakter dan angka khusus di spasi berikutnya. "PG-13" valid untuk rating, tetapi gagal untuk "Genre".
  • Atribut Range membatasi nilai dalam rentang yang ditentukan.

  • Atribut StringLength mengatur panjang maksimum properti string, dan secara opsional, panjang minimumnya.

  • Jenis nilai (seperti decimal, int, float, DateTime) secara inheren diperlukan dan tidak memerlukan atribut [Required].

Halaman Buat untuk model Movie menunjukkan kesalahan tampilan dengan nilai yang tidak valid:

Formulir tampilan film dengan beberapa kesalahan validasi sisi klien jQuery

Untuk informasi selengkapnya, lihat:

Isolasi CSS

Isolasi gaya CSS ke halaman, tampilan, dan komponen individual untuk mengurangi atau menghindari:

  • Dependensi pada gaya global yang sulit dipertahankan.
  • Konflik gaya dalam konten yang disarangkan.

Guna menambahkan file CSS cakupan untuk halaman atau tampilan, tempatkan gaya CSS di file .cshtml.css pengiring yang cocok dengan nama file .cshtml. Dalam contoh berikut, file Index.cshtml.css menyediakan gaya CSS yang hanya diterapkan pada halaman atau tampilan Index.cshtml.

Pages/Index.cshtml.css (Razor Pages) atau Views/Index.cshtml.css (MVC):

h1 {
    color: red;
}

Isolasi CSS terjadi pada waktu pembuatan. Kerangka kerja menulis ulang pemilih CSS agar sesuai dengan markup yang dirender oleh halaman atau tampilan aplikasi. Gaya CSS yang ditulis kembali dibundel dan diproduksi sebagai aset statis, {APP ASSEMBLY}.styles.css. Tempat penampung {APP ASSEMBLY} adalah nama rakitan proyek. Tautan ke gaya CSS yang dibundel ditempatkan di tata letak aplikasi.

Di konten <head> dari Pages/Shared/_Layout.cshtml aplikasi (Razor Pages) atau Views/Shared/_Layout.cshtml (MVC), tambahkan atau konfirmasi keberadaan tautan ke gaya CSS yang dibundel:

<link rel="stylesheet" href="~/{APP ASSEMBLY}.styles.css" />

Dalam contoh berikut, nama rakitan aplikasi adalah WebApp:

<link rel="stylesheet" href="WebApp.styles.css" />

Gaya yang ditentukan dalam file CSS cakupan hanya diterapkan pada output yang dirender dari file yang cocok. Dalam contoh sebelumnya, semua deklarasi CSS h1 yang ditentukan di tempat lain dalam aplikasi tidak bertentangan dengan gaya judul Index. Aturan kaskading dan pewarisan gaya CSS tetap berlaku untuk file CSS cakupan. Misalnya, gaya yang diterapkan langsung ke elemen <h1> dalam file Index.cshtml menimpa gaya file CSS cakupan di Index.cshtml.css.

Catatan

Untuk menjamin isolasi gaya CSS saat terjadi bundling, mengimpor CSS dalam blok kode Razor tidak didukung.

Isolasi CSS hanya berlaku untuk elemen HTML. Isolasi CSS tidak didukung untuk Pembantu Tag.

Dalam file CSS yang dibundel, setiap halaman, tampilan, atau komponen Razor dikaitkan dengan pengidentifikasi cakupan dalam b-{STRING} format, di mana tempat penampung {STRING} adalah string dengan sepuluh karakter yang dihasilkan oleh kerangka kerja. Contoh berikut memberikan gaya untuk elemen <h1> sebelumnya di halaman Index aplikasi Razor Pages:

/* /Pages/Index.cshtml.rz.scp.css */
h1[b-3xxtam6d07] {
    color: red;
}

Di halaman Index tempat gaya CSS diterapkan dari file yang dibundel, pengidentifikasi cakupan ditambahkan sebagai atribut HTML:

<h1 b-3xxtam6d07>

Pengidentifikasi sifatnya unik untuk suatu aplikasi. Pada waktu pembuatan, bundel proyek dibuat dengan konvensi {STATIC WEB ASSETS BASE PATH}/Project.lib.scp.css, di mana tempat penampung {STATIC WEB ASSETS BASE PATH} adalah jalur dasar aset web statis.

Jika proyek lain digunakan, seperti paket NuGet atau pustaka kelas Razor, file yang dibundel:

  • Mereferensikan gaya menggunakan impor CSS.
  • Tidak diterbitkan sebagai aset web statis aplikasi yang menggunakan gaya.

Dukungan praprosesor CSS

Praprosesor CSS berguna untuk meningkatkan pengembangan CSS dengan memanfaatkan fitur seperti variabel, bersarang, modul, mixin, dan pewarisan. Meskipun isolasi CSS tidak secara native mendukung praprosesor CSS seperti Sass atau Less, integrasi praprosesor CSS berjalan mulus selama kompilasi praprosesor terjadi sebelum kerangka kerja menulis kembali pemilih CSS selama proses pembuatan. Menggunakan Visual Studio sebagai contoh, konfigurasikan kompilasi praprosesor yang ada sebagai tugas Sebelum Build di Visual Studio Task Runner Explorer.

Banyak paket NuGet pihak ketiga, seperti AspNetCore.SassCompiler, yang dapat mengompilasi file SASS/SCSS di awal proses pembuatan sebelum isolasi CSS terjadi, dan tidak memerlukan konfigurasi tambahan.

Konfigurasi isolasi CSS

Isolasi CSS memungkinkan konfigurasi untuk beberapa skenario tingkat lanjut, seperti ketika ada dependensi pada alat atau alur kerja yang ada.

Mengkustomisasi format pengidentifikasi cakupan

Di bagian ini, tempat penampung {Pages|Views} adalah Pages untuk aplikasi Razor Pages atau Views untuk aplikasi MVC.

Secara default, pengidentifikasi cakupan menggunakan format b-{STRING}, di mana tempat penampung {STRING} adalah string sepuluh karakter yang dihasilkan oleh kerangka kerja. Untuk mengkustomisasi format pengidentifikasi cakupan, perbarui file proyek menjadi pola yang diinginkan:

<ItemGroup>
  <None Update="{Pages|Views}/Index.cshtml.css" CssScope="custom-scope-identifier" />
</ItemGroup>

Pada contoh sebelumnya, CSS yang dihasilkan untuk Index.cshtml.css mengubah pengidentifikasi cakupannya dari b-{STRING} menjadi custom-scope-identifier.

Gunakan pengidentifikasi cakupan untuk mencapai warisan dengan file CSS cakupan. Dalam contoh file proyek berikut, file BaseView.cshtml.css berisi gaya umum di seluruh tampilan. File DerivedView.cshtml.css mewarisi gaya ini.

<ItemGroup>
  <None Update="{Pages|Views}/BaseView.cshtml.css" CssScope="custom-scope-identifier" />
  <None Update="{Pages|Views}/DerivedView.cshtml.css" CssScope="custom-scope-identifier" />
</ItemGroup>

Gunakan operator wildcard (*) untuk berbagi pengidentifikasi cakupan di beberapa file:

<ItemGroup>
  <None Update="{Pages|Views}/*.cshtml.css" CssScope="custom-scope-identifier" />
</ItemGroup>

Mengubah jalur dasar untuk aset web statis

File CSS cakupan dibuat di akar aplikasi. Dalam file proyek, gunakan properti StaticWebAssetBasePath untuk mengubah jalur default. Contoh berikut menempatkan file CSS cakupan, dan aset aplikasi lainnya, di jalur _content:

<PropertyGroup>
  <StaticWebAssetBasePath>_content/$(PackageId)</StaticWebAssetBasePath>
</PropertyGroup>

Menonaktifkan bundling otomatis

Untuk menolak cara kerangka menerbitkan dan memuat file cakupan saat runtime bahasa umum, gunakan properti DisableScopedCssBundling. Saat menggunakan properti ini, alat atau proses lain bertanggung jawab untuk mengambil file CSS yang terisolasi dari direktori obj dan menerbitkan serta memuatnya pada runtime bahasa umum:

<PropertyGroup>
  <DisableScopedCssBundling>true</DisableScopedCssBundling>
</PropertyGroup>

Dukungan pustaka kelas Razor (RCL)

Saat pustaka kelas Razor (RCL) menyediakan gaya terisolasi, atribut href tag <link> menunjuk ke {STATIC WEB ASSET BASE PATH}/{PACKAGE ID}.bundle.scp.css, dengan tempat penampung:

  • {STATIC WEB ASSET BASE PATH}: Jalur dasar aset web statis.
  • {PACKAGE ID}: pengidentifikasi paket pustaka. Pengidentifikasi paket diatur ke default, yakni nama rakitan proyek, jika pengidentifikasi paket tidak ditentukan dalam file proyek.

Dalam contoh berikut:

  • Jalur dasar aset web statis adalah _content/ClassLib.
  • Nama rakitan pustaka kelas adalah ClassLib.

Pages/Shared/_Layout.cshtml (Razor Pages) atau Views/Shared/_Layout.cshtml (MVC):

<link href="_content/ClassLib/ClassLib.bundle.scp.css" rel="stylesheet">

Untuk informasi lebih lanjut tentang RCL, lihat artikel berikut:

Untuk informasi tentang isolasi CSS Blazor, lihat Isolasi CSS Blazor ASP.NET Core.

Menangani permintaan HEAD dengan fallback penanganan OnGet

Permintaan HEAD memungkinkan pengambilan header untuk sumber daya tertentu. Tidak seperti permintaan GET, permintaan HEAD tidak mengembalikan isi respons.

Biasanya, penanganan OnHead dibuat dan dipanggil untuk permintaan HEAD:

public void OnHead()
{
    HttpContext.Response.Headers.Add("Head Test", "Handled by OnHead!");
}

Razor Pages kembali memanggil penanganan OnGet jika tidak ada penanganan OnHead yang ditentukan.

XSRF/CSRF dan Razor Pages

Razor Pages dilindungi oleh Validasi anti-pemalsuan. FormTagHelper menyuntikkan token anti-pemalsuan ke dalam elemen formulir HTML.

Menggunakan Tata Letak, parsial, template, dan Pembantu Tag dengan Razor Pages

Pages berfungsi dengan semua kemampuan mesin tampilan Razor. Tata letak, parsial, template, Pembantu Tag, _ViewStart.cshtml, dan _ViewImports.cshtml bekerja dengan cara yang sama seperti tampilan Razor konvensional.

Mari kita rapikan halaman ini dengan memanfaatkan beberapa kemampuan tersebut.

Tambahkan halaman tata letak ke Pages/Shared/_Layout.cshtml:

<!DOCTYPE html>
<html>
<head>
    <title>RP Sample</title>
    <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
</head>
<body>
    <a asp-page="/Index">Home</a>
    <a asp-page="/Customers/Create">Create</a>
    <a asp-page="/Customers/Index">Customers</a> <br />

    @RenderBody()
    <script src="~/lib/jquery/dist/jquery.js"></script>
    <script src="~/lib/jquery-validation/dist/jquery.validate.js"></script>
    <script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"></script>
</body>
</html>

Tata Letak:

  • Mengontrol tata letak setiap halaman (kecuali halaman menolak tata letak).
  • Mengimpor struktur HTML seperti JavaScript dan stylesheet.
  • Konten halaman Razor dirender di tempat @RenderBody() dipanggil.

Untuk informasi lebih lanjut, lihat halaman tata letak.

Properti Tata letak diatur di Pages/_ViewStart.cshtml:

@{
    Layout = "_Layout";
}

Tata letaknya ada di folder Pages/Shared. Halaman mencari tampilan lain (tata letak, template, parsial) secara hierarkis, dimulai dari folder yang sama dengan halaman saat ini. Tata letak dalam folder Pages/Shared dapat digunakan dari halaman Razor mana pun di pada folder Pages.

File tata letak harus masuk ke dalam folder Pages/Shared.

Sebaiknya jangan meletakkan file tata letak di folder Views/Shared. Views/Shared adalah pola tampilan MVC. Razor Pages dimaksudkan untuk mengandalkan hierarki folder, bukan konvensi jalur.

Lihat pencarian dari Razor Page yang menyertakan folder Page. Tata letak, template, dan parsial yang digunakan dengan pengontrol MVC dan tampilan Razor konvensional berfungsi.

Tambahkan file Pages/_ViewImports.cshtml:

@namespace RazorPagesContacts.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

@namespace dijelaskan nanti dalam tutorial. Arahan @addTagHelper membawa Pembantu Tag bawaan ke semua halaman dalam folder Pages.

Arahan @namespace diatur pada halaman:

@page
@namespace RazorPagesIntro.Pages.Customers

@model NameSpaceModel

<h2>Name space</h2>
<p>
    @Model.Message
</p>

Arahan @namespace mengatur namespace layanan untuk halaman. Arahan @model tidak perlu menyertakan namespace layanan.

Jika arahan @namespace ada dalam _ViewImports.cshtml, namespace layanan yang ditentukan akan menyediakan awalan untuk namespace layanan yang dihasilkan di Page yang mengimpor arahan @namespace. Sisa namespace layanan yang dihasilkan (bagian akhiran) adalah jalur relatif yang dipisahkan titik antara folder yang berisi _ViewImports.cshtml dan folder yang berisi halaman.

Misalnya, kelas PageModelPages/Customers/Edit.cshtml.cs secara eksplisit mengatur namespace layanan:

namespace RazorPagesContacts.Pages
{
    public class EditModel : PageModel
    {
        private readonly AppDbContext _db;

        public EditModel(AppDbContext db)
        {
            _db = db;
        }

        // Code removed for brevity.

File Pages/_ViewImports.cshtml mengatur namespace layanan berikut:

@namespace RazorPagesContacts.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

Namespace layanan yang dihasilkan untuk Pages/Customers/Edit.cshtmlRazorPage sama dengan kelas PageModel.

@namespacejuga berfungsi dengan tampilan Razor konvensional.

Pertimbangkan file tampilan Pages/Customers/Create.cshtml:

@page
@model RazorPagesContacts.Pages.Customers.CreateModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<p>Validation: customer name:</p>

<form method="post">
    <div asp-validation-summary="ModelOnly"></div>
    <span asp-validation-for="Customer!.Name"></span>
    Name:
    <input asp-for="Customer!.Name" />
    <input type="submit" />
</form>

<script src="~/lib/jquery/dist/jquery.js"></script>
<script src="~/lib/jquery-validation/dist/jquery.validate.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"></script>

File tampilan Pages/Customers/Create.cshtml yang diperbarui dengan _ViewImports.cshtml dan file tata letak sebelumnya:

@page
@model CreateModel

<p>Enter a customer name:</p>

<form method="post">
    Name:
    <input asp-for="Customer!.Name" />
    <input type="submit" />
</form>

Dalam kode sebelumnya, _ViewImports.cshtml mengimpor namespace layanan dan Pembantu Tag. File tata letak mengimpor file JavaScript.

Proyek starter Razor Pages berisi Pages/_ValidationScriptsPartial.cshtml, yang menghubungkan validasi sisi klien.

Untuk informasi lebih lanjut tentang tampilan parsial, lihat Tampilan parsial di ASP.NET Core.

Pembuatan URL untuk Pages

Halaman Create, yang ditampilkan sebelumnya, menggunakan RedirectToPage:

public class CreateModel : PageModel
{
    private readonly Data.CustomerDbContext _context;

    public CreateModel(Data.CustomerDbContext context)
    {
        _context = context;
    }

    public IActionResult OnGet()
    {
        return Page();
    }

    [BindProperty]
    public Customer? Customer { get; set; }

    public async Task<IActionResult> OnPostAsync()
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }

        if (Customer != null) _context.Customer.Add(Customer);
        await _context.SaveChangesAsync();

        return RedirectToPage("./Index");
    }
}

Aplikasi ini memiliki struktur file/folder berikut:

  • /Pages

    • Index.cshtml

    • Privacy.cshtml

    • /Customers

      • Create.cshtml
      • Edit.cshtml
      • Index.cshtml

Halaman Pages/Customers/Create.cshtml dan Pages/Customers/Edit.cshtml dialihkan ke Pages/Customers/Index.cshtml setelah berhasil. ./Index string adalah nama halaman relatif yang digunakan untuk mengakses halaman sebelumnya. Ini digunakan untuk membuat URL ke halaman Pages/Customers/Index.cshtml. Contohnya:

  • Url.Page("./Index", ...)
  • <a asp-page="./Index">Customers Index Page</a>
  • RedirectToPage("./Index")

/Index nama halaman absolut digunakan untuk membuat URL ke halaman Pages/Index.cshtml. Contohnya:

  • Url.Page("/Index", ...)
  • <a asp-page="/Index">Home Index Page</a>
  • RedirectToPage("/Index")

Nama halaman adalah jalur ke halaman dari folder /Pages akar termasuk awalan / (misalnya, /Index). Sampel pembuatan URL sebelumnya menawarkan opsi yang disempurnakan dan kemampuan fungsional jika dibandingkan dengan hard-coding URL. Pembuatan URL menggunakan perutean dan dapat membuat serta mengodekan parameter sesuai dengan cara rute ditentukan di jalur tujuan.

Pembuatan URL untuk halaman mendukung nama relatif. Tabel berikut menunjukkan halaman Indeks mana yang dipilih menggunakan parameter RedirectToPage yang berbeda di Pages/Customers/Create.cshtml.

RedirectToPage(x) Halaman
RedirectToPage("/Index") Pages/Index
RedirectToPage("./Index"); Pages/Customers/Index
RedirectToPage("../Index") Pages/Index
RedirectToPage("Index") Pages/Customers/Index

RedirectToPage("Index"), RedirectToPage("./Index"), dan RedirectToPage("../Index") adalah nama relatif. Parameter RedirectToPagedigabungkan dengan jalur halaman saat ini untuk menghitung nama halaman tujuan.

Tautan nama relatif berguna saat membangun situs dengan struktur yang kompleks. Saat nama relatif digunakan untuk menautkan antar-halaman dalam folder:

  • Mengganti nama folder tidak merusak tautan relatif.
  • Tautan tidak rusak karena tautan tidak menyertakan nama folder.

Untuk mengalihkan ke halaman di Area yang berbeda, tentukan area:

RedirectToPage("/Index", new { area = "Services" });

Untuk informasi lebih lanjut, lihat Area di ASP.NET Core serta Rute dan konvensi aplikasi Razor Pages di ASP.NET Core.

Atribut ViewData

Data dapat diteruskan ke halaman dengan ViewDataAttribute. Properti dengan atribut [ViewData] memiliki nilai yang disimpan dan dimuat dari ViewDataDictionary.

Dalam contoh berikut, AboutModel menerapkan atribut [ViewData] ke properti Title:

public class AboutModel : PageModel
{
    [ViewData]
    public string Title { get; } = "About";

    public void OnGet()
    {
    }
}

Di halaman Tentang, akses properti Title sebagai properti model:

<h1>@Model.Title</h1>

Dalam tata letak, judul dibaca dari kamus ViewData:

<!DOCTYPE html>
<html lang="en">
<head>
    <title>@ViewData["Title"] - WebApplication</title>
    ...

TempData

ASP.NET Core mengekspos TempData. Properti ini menyimpan data sampai data dibaca. Metode Keep dan Peek dapat digunakan untuk memeriksa data tanpa menghapusnya. TempData berguna untuk pengalihan, ketika data diperlukan untuk lebih dari satu permintaan.

Kode berikut menetapkan nilai Message menggunakan TempData:

public class CreateDotModel : PageModel
{
    private readonly AppDbContext _db;

    public CreateDotModel(AppDbContext db)
    {
        _db = db;
    }

    [TempData]
    public string Message { get; set; }

    [BindProperty]
    public Customer Customer { get; set; }

    public async Task<IActionResult> OnPostAsync()
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }

        _db.Customers.Add(Customer);
        await _db.SaveChangesAsync();
        Message = $"Customer {Customer.Name} added";
        return RedirectToPage("./Index");
    }
}

Markup berikut dalam file Pages/Customers/Index.cshtml menampilkan nilai Message menggunakan TempData.

<h3>Msg: @Model.Message</h3>

Model halaman Pages/Customers/Index.cshtml.cs menerapkan atribut [TempData] ke properti Message.

[TempData]
public string Message { get; set; }

Untuk informasi lebih lanjut, lihat TempData.

Beberapa penanganan per halaman

Halaman berikut menghasilkan markup untuk dua penanganan menggunakan Pembantu Tag asp-page-handler:

@page
@model CreateFATHModel

<html>
<body>
    <p>
        Enter your name.
    </p>
    <div asp-validation-summary="All"></div>
    <form method="POST">
        <div>Name: <input asp-for="Customer.Name" /></div>
        <!-- <snippet_Handlers> -->
        <input type="submit" asp-page-handler="JoinList" value="Join" />
        <input type="submit" asp-page-handler="JoinListUC" value="JOIN UC" />
        <!-- </snippet_Handlers> -->
    </form>
</body>
</html>

Formulir dalam contoh sebelumnya memiliki dua tombol kirim, masing-masing menggunakan FormActionTagHelper untuk mengirim ke URL yang berbeda. Atribut asp-page-handler adalah pendamping asp-page. asp-page-handler menghasilkan URL yang dikirimkan ke setiap metode penanganan yang ditentukan oleh halaman. asp-page tidak ditentukan karena sampel menautkan ke halaman saat ini.

Model halaman :

using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using RazorPagesContacts.Data;

namespace RazorPagesContacts.Pages.Customers
{
    public class CreateFATHModel : PageModel
    {
        private readonly AppDbContext _db;

        public CreateFATHModel(AppDbContext db)
        {
            _db = db;
        }

        [BindProperty]
        public Customer Customer { get; set; }

        public async Task<IActionResult> OnPostJoinListAsync()
        {
            if (!ModelState.IsValid)
            {
                return Page();
            }

            _db.Customers.Add(Customer);
            await _db.SaveChangesAsync();
            return RedirectToPage("/Index");
        }

        public async Task<IActionResult> OnPostJoinListUCAsync()
        {
            if (!ModelState.IsValid)
            {
                return Page();
            }
            Customer.Name = Customer.Name?.ToUpperInvariant();
            return await OnPostJoinListAsync();
        }
    }
}

Kode sebelumnya menggunakan metode penanganan bernama. Metode pengendali bernama dibuat dengan mengambil teks dalam nama setelah On<HTTP Verb> dan sebelum Async (jika ada). Dalam contoh sebelumnya, metode halaman adalah OnPostJoinListAsync dan OnPostJoinListUCAsync. Dengan OnPost dan Async dihapus, nama penangannya adalah JoinList dan JoinListUC.

<input type="submit" asp-page-handler="JoinList" value="Join" />
<input type="submit" asp-page-handler="JoinListUC" value="JOIN UC" />

Dengan menggunakan kode sebelumnya, jalur URL yang mengirimkan ke OnPostJoinListAsync adalah https://localhost:5001/Customers/CreateFATH?handler=JoinList. Jalur URL yang mengirimkan ke OnPostJoinListUCAsync adalah https://localhost:5001/Customers/CreateFATH?handler=JoinListUC.

Rute kustom

Gunakan arahan @page untuk:

  • Menentukan rute kustom ke halaman. Misalnya, rute ke halaman Tentang dapat diatur ke /Some/Other/Path dengan @page "/Some/Other/Path".
  • Menambahkan segmen ke rute default halaman. Misalnya, segmen "item" dapat ditambahkan ke rute default halaman dengan @page "item".
  • Menambahkan parameter ke rute default halaman. Misalnya, parameter ID, id, dapat diperlukan untuk halaman dengan @page "{id}".

Jalur relatif akar yang ditunjuk oleh tilde (~) di awal jalur didukung. Misalnya, @page "~/Some/Other/Path" sama dengan @page "/Some/Other/Path".

Jika Anda tidak menyukai ?handler=JoinList string kueri di URL, ubah rute untuk meletakkan nama penanganan di bagian jalur URL. Rute dapat dikustomisasi dengan menambahkan template rute yang diapit tanda kutip ganda setelah arahan @page.

@page "{handler?}"
@model CreateRouteModel

<html>
<body>
    <p>
        Enter your name.
    </p>
    <div asp-validation-summary="All"></div>
    <form method="POST">
        <div>Name: <input asp-for="Customer.Name" /></div>
        <input type="submit" asp-page-handler="JoinList" value="Join" />
        <input type="submit" asp-page-handler="JoinListUC" value="JOIN UC" />
    </form>
</body>
</html>

Dengan menggunakan kode sebelumnya, jalur URL yang mengirimkan ke OnPostJoinListAsync adalah https://localhost:5001/Customers/CreateFATH/JoinList. Jalur URL yang mengirimkan ke OnPostJoinListUCAsync adalah https://localhost:5001/Customers/CreateFATH/JoinListUC.

? setelah handler berarti parameter rute adalah opsional.

Kolokasi file JavaScript (JS)

Kolokasi file JavaScript (JS) untuk halaman dan tampilan adalah cara mudah untuk mengatur skrip di aplikasi.

Kolokasikan file JS menggunakan konvensi ekstensi nama file berikut:

  • Halaman dari Aplikasi halaman Razor dan tampilan aplikasi MVC: .cshtml.js. Contoh:
    • Pages/Index.cshtml.js untuk halaman Index dari aplikasi Halaman Razor di Pages/Index.cshtml.
    • Views/Home/Index.cshtml.js untuk tampilan Index aplikasi MVC di Views/Home/Index.cshtml.

File JS yang dikumpulkan dapat diatasi secara publik menggunakan jalur ke file dalam proyek:

  • Halaman dan tampilan dari file skrip yang dikolokasi di aplikasi:

    {PATH}/{PAGE, VIEW, OR COMPONENT}.{EXTENSION}.js

    • Tempat penampung {PATH} adalah jalur ke halaman, tampilan, atau komponen.
    • Tempat penampung {PAGE, VIEW, OR COMPONENT} adalah halaman, tampilan, atau komponen.
    • Tempat penampung {EXTENSION} cocok dengan ekstensi halaman, tampilan, atau komponen, baik razor atau cshtml.

    RazorContoh halaman:

    File JS untuk halaman Index ditempatkan di folder Pages (Pages/Index.cshtml.js) di sebelah halaman Index (Pages/Index.cshtml). Di halaman Index, skrip direferensikan pada jalur di folder Pages:

    @section Scripts {
      <script src="~/Pages/Index.cshtml.js"></script>
    }
    

    Saat aplikasi diterbitkan, kerangka kerja secara otomatis memindahkan skrip ke akar web. Pada contoh sebelumnya, skrip dipindahkan ke bin\Release\{TARGET FRAMEWORK MONIKER}\publish\wwwroot\Pages\Index.cshtml.js, dengan tempat penampung {TARGET FRAMEWORK MONIKER} adalah Target Framework Moniker (TFM). Tidak diperlukan perubahan pada URL relatif skrip di halaman Index.

    Saat aplikasi diterbitkan, kerangka kerja secara otomatis memindahkan skrip ke akar web. Pada contoh sebelumnya, skrip dipindahkan ke bin\Release\{TARGET FRAMEWORK MONIKER}\publish\wwwroot\Components\Pages\Index.razor.js, dengan tempat penampung {TARGET FRAMEWORK MONIKER} adalah Target Framework Moniker (TFM). Tidak diperlukan perubahan pada URL relatif skrip di komponen Index.

  • Untuk skrip yang disediakan oleh pustaka kelas (RCL) Razor:

    _content/{PACKAGE ID}/{PATH}/{PAGE, VIEW, OR COMPONENT}.{EXTENSION}.js

    • Tempat penampung {PACKAGE ID} adalah pengidentifikasi paket RCL (atau nama pustaka untuk pustaka kelas yang dirujuk oleh aplikasi).
    • Tempat penampung {PATH} adalah jalur ke halaman, tampilan, atau komponen. Jika komponen Razor terletak di akar RCL, segmen jalur tidak disertakan.
    • Tempat penampung {PAGE, VIEW, OR COMPONENT} adalah halaman, tampilan, atau komponen.
    • Tempat penampung {EXTENSION} cocok dengan ekstensi halaman, tampilan, atau komponen, baik razor atau cshtml.

Konfigurasi dan pengaturan tingkat lanjut

Konfigurasi dan pengaturan di bagian berikut tidak diperlukan oleh sebagian besar aplikasi.

Untuk mengonfigurasi opsi tingkat lanjut, gunakan overload AddRazorPages yang mengonfigurasi RazorPagesOptions:

using Microsoft.EntityFrameworkCore;
using RazorPagesContacts.Data;
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages(options =>
{
    options.RootDirectory = "/MyPages";
    options.Conventions.AuthorizeFolder("/MyPages/Admin");
});

builder.Services.AddDbContext<CustomerDbContext>(options =>
    options.UseInMemoryDatabase("name"));

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

Gunakan RazorPagesOptions untuk mengatur direktori akar untuk halaman, atau menambahkan konvensi model aplikasi untuk halaman. Untuk informasi lebih lanjut tentang konvensi, lihat Konvensi otorisasi Razor Pages.

Untuk mengompilasi tampilan sebelumnya, lihat kompilasi tampilan Razor.

Menentukan bahwa Razor Pages berada di akar konten

Secara default, Razor Pages berakar di direktori /Pages. Tambahkan WithRazorPagesAtContentRoot untuk menentukan bahwa Razor Pages Anda berada di akar konten (ContentRootPath) aplikasi:

using Microsoft.EntityFrameworkCore;
using RazorPagesContacts.Data;
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages(options =>
{
    options.Conventions.AuthorizeFolder("/MyPages/Admin");
})
  .WithRazorPagesAtContentRoot();

builder.Services.AddDbContext<CustomerDbContext>(options =>
    options.UseInMemoryDatabase("name"));

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

Menentukan bahwa Razor Pages berada di direktori akar kustom

Tambahkan WithRazorPagesRoot untuk menentukan bahwa Razor Pages berada di direktori akar kustom di aplikasi (berikan jalur relatif):

using Microsoft.EntityFrameworkCore;
using RazorPagesContacts.Data;
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages(options =>
{
    options.Conventions.AuthorizeFolder("/MyPages/Admin");
})
  .WithRazorPagesRoot("/path/to/razor/pages");

builder.Services.AddDbContext<CustomerDbContext>(options =>
    options.UseInMemoryDatabase("name"));

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

Sumber Daya Tambahan:

Buat proyek Razor Pages

Lihat Mulai menggunakan Razor Pages untuk petunjuk terperinci tentang cara membuat proyek Razor Pages.

Razor Pages

Razor Pages diaktifkan di Startup.cs:

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddRazorPages();
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseExceptionHandler("/Error");
            app.UseHsts();
        }

        app.UseHttpsRedirection();
        app.UseStaticFiles();

        app.UseRouting();

        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapRazorPages();
        });
    }
}

Pertimbangkan halaman dasar:

@page

<h1>Hello, world!</h1>
<h2>The time on the server is @DateTime.Now</h2>

Kode sebelumnya sangat mirip dengan file tampilan Razor yang digunakan dalam aplikasi ASP.NET Core dengan pengontrol dan tampilan. Yang membuatnya berbeda adalah arahan @page. @page membuat file menjadi tindakan MVC - yang berarti menangani permintaan secara langsung, tanpa melalui pengontrol. @page harus menjadi arahan Razor pertama di halaman. @page memengaruhi perilaku konstruksi Razor lainnya. Nama file Razor Pages memiliki akhiran .cshtml.

Halaman serupa, menggunakan kelas PageModel, ditampilkan dalam dua file berikut. File Pages/Index2.cshtml:

@page
@using RazorPagesIntro.Pages
@model Index2Model

<h2>Separate page model</h2>
<p>
    @Model.Message
</p>

Model halaman Pages/Index2.cshtml.cs:

using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
using System;

namespace RazorPagesIntro.Pages
{
    public class Index2Model : PageModel
    {
        public string Message { get; private set; } = "PageModel in C#";

        public void OnGet()
        {
            Message += $" Server time is { DateTime.Now }";
        }
    }
}

Menurut konvensi, file kelas PageModel memiliki nama yang sama dengan file Razor Pages dengan .cs yang ditambahkan. Misalnya, Razor Pages sebelumnya adalah Pages/Index2.cshtml. File yang berisi kelas PageModel diberi nama Pages/Index2.cshtml.cs.

Asosiasi jalur URL ke halaman ditentukan oleh lokasi halaman di sistem file. Tabel berikut menunjukkan jalur Razor Pages dan URL yang cocok:

Nama file dan jalur URL yang cocok
/Pages/Index.cshtml / atau /Index
/Pages/Contact.cshtml /Contact
/Pages/Store/Contact.cshtml /Store/Contact
/Pages/Store/Index.cshtml /Store atau /Store/Index

Catatan:

  • Runtime bahasa umum mencari file Razor Pages dalam folder Pages secara default.
  • Index adalah halaman bawaan jika URL tidak menyertakan halaman.

Menulis formulir dasar

Razor Pages dirancang untuk menjadikan pola umum yang digunakan dengan browser web mudah diimplementasikan saat membuat aplikasi. Pengikatan model, Pembantu Tag, dan pembantu HTML, semuanya berfungsi dengan properti yang ditentukan dalam kelas Razor Page. Pertimbangkan halaman yang mengimplementasikan formulir "hubungi kami" dasar untuk model Contact:

Untuk sampel dalam dokumen ini, DbContext diinisialisasi dalam file Startup.cs.

Database dalam memori memerlukan paket NuGet Microsoft.EntityFrameworkCore.InMemory.

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<CustomerDbContext>(options =>
                      options.UseInMemoryDatabase("name"));
    services.AddRazorPages();
}

Model data:

using System.ComponentModel.DataAnnotations;

namespace RazorPagesContacts.Models
{
    public class Customer
    {
        public int Id { get; set; }

        [Required, StringLength(10)]
        public string Name { get; set; }
    }
}

Konteks db:

using Microsoft.EntityFrameworkCore;
using RazorPagesContacts.Models;

namespace RazorPagesContacts.Data
{
    public class CustomerDbContext : DbContext
    {
        public CustomerDbContext(DbContextOptions options)
            : base(options)
        {
        }

        public DbSet<Customer> Customers { get; set; }
    }
}

File tampilan Pages/Create.cshtml:

@page
@model RazorPagesContacts.Pages.Customers.CreateModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<p>Enter a customer name:</p>

<form method="post">
    Name:
    <input asp-for="Customer.Name" />
    <input type="submit" />
</form>

Model halaman Pages/Create.cshtml.cs:

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using RazorPagesContacts.Data;
using RazorPagesContacts.Models;
using System.Threading.Tasks;

namespace RazorPagesContacts.Pages.Customers
{
    public class CreateModel : PageModel
    {
        private readonly CustomerDbContext _context;

        public CreateModel(CustomerDbContext context)
        {
            _context = context;
        }

        public IActionResult OnGet()
        {
            return Page();
        }

        [BindProperty]
        public Customer Customer { get; set; }

        public async Task<IActionResult> OnPostAsync()
        {
            if (!ModelState.IsValid)
            {
                return Page();
            }

            _context.Customers.Add(Customer);
            await _context.SaveChangesAsync();

            return RedirectToPage("./Index");
        }
    }
}

Berdasarkan konvensi, kelas PageModel disebut <PageName>Model dan berada dalam namespace layanan yang sama dengan halaman.

Kelas PageModel memungkinkan pemisahan logika halaman dari presentasinya. Ini mendefinisikan penanganan halaman untuk permintaan yang dikirim ke halaman dan data yang digunakan untuk merender halaman. Pemisahan ini memungkinkan:

Halaman memiliki OnPostAsynchandler method, yang berjalan pada permintaan POST (ketika pengguna memposting formulir). Metode penanganan untuk kata kerja HTTP apa pun dapat ditambahkan. Penanganan yang paling umum adalah:

  • OnGet untuk menginisialisasi status yang diperlukan halaman. Pada kode sebelumnya, metode OnGet menampilkan CreateModel.cshtmlRazor Page.
  • OnPost untuk menangani pengiriman formulir.

Akhiran penamaan Async sifatnya opsional tetapi sering digunakan oleh konvensi untuk fungsi asinkron. Kode sebelumnya umum untuk Razor Pages.

Jika Anda terbiasa dengan aplikasi ASP.NET yang menggunakan pengontrol dan tampilan:

  • Kode OnPostAsync dalam contoh sebelumnya terlihat mirip dengan kode pengontrol biasa.
  • Sebagian besar primitif MVC seperti pengikatan model, validasi, dan hasil tindakan bekerja dengan cara yang sama seperti Pengontrol dan Razor Pages.

Metode OnPostAsync sebelumnya:

public async Task<IActionResult> OnPostAsync()
{
    if (!ModelState.IsValid)
    {
        return Page();
    }

    _context.Customers.Add(Customer);
    await _context.SaveChangesAsync();

    return RedirectToPage("./Index");
}

Alur dasar OnPostAsync:

Periksa kesalahan validasi.

  • Jika tidak ada kesalahan, simpan data dan alihkan.
  • Jika ada kesalahan, tampilkan kembali halaman dengan pesan validasi. Dalam banyak kasus, kesalahan validasi akan terdeteksi pada klien, dan tidak pernah dikirimkan ke server.

File tampilan Pages/Create.cshtml:

@page
@model RazorPagesContacts.Pages.Customers.CreateModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<p>Enter a customer name:</p>

<form method="post">
    Name:
    <input asp-for="Customer.Name" />
    <input type="submit" />
</form>

HTML yang dirender dari Pages/Create.cshtml:

<p>Enter a customer name:</p>

<form method="post">
    Name:
    <input type="text" data-val="true"
           data-val-length="The field Name must be a string with a maximum length of 10."
           data-val-length-max="10" data-val-required="The Name field is required."
           id="Customer_Name" maxlength="10" name="Customer.Name" value="" />
    <input type="submit" />
    <input name="__RequestVerificationToken" type="hidden"
           value="<Antiforgery token here>" />
</form>

Dalam kode sebelumnya, posting formulir:

  • Dengan data yang valid:

    • Metode penanganan OnPostAsync memanggil metode pembantu RedirectToPage. RedirectToPage mengembalikan instans dari RedirectToPageResult. RedirectToPage:

      • Merupakan hasil tindakan.
      • Mirip dengan RedirectToAction atau RedirectToRoute (digunakan dalam pengontrol dan tampilan).
      • Dikustomisasi untuk halaman. Dalam sampel sebelumnya, ini dialihkan ke halaman Indeks akar (/Index). RedirectToPage dijelaskan lebih lanjut di bagian pembuatan URL untuk Pages.
  • Dengan kesalahan validasi yang diteruskan ke server:

    • Metode penanganan OnPostAsync memanggil metode pembantu Page. Page mengembalikan instans dari PageResult. Pengembalian Page mirip dengan bagaimana tindakan di pengontrol mengembalikan View. PageResult adalah jenis pengembalian default untuk metode penanganan. Metode penanganan yang mengembalikan void merender halaman.
    • Pada contoh sebelumnya, memposting formulir tanpa nilai akan menyebabkan ModelState.IsValid mengembalikan false. Dalam sampel ini, tidak ada kesalahan validasi yang ditampilkan pada klien. Penanganan kesalahan validasi dibahas nanti dalam dokumen ini.
    public async Task<IActionResult> OnPostAsync()
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }
    
        _context.Customers.Add(Customer);
        await _context.SaveChangesAsync();
    
        return RedirectToPage("./Index");
    }
    
  • Dengan kesalahan validasi yang terdeteksi oleh validasi sisi klien:

    • Data tidak diposting ke server.
    • Validasi sisi klien dijelaskan nanti dalam dokumen ini.

Properti Customer menggunakan atribut [BindProperty] untuk ikut serta dalam pengikatan model:

public class CreateModel : PageModel
{
    private readonly CustomerDbContext _context;

    public CreateModel(CustomerDbContext context)
    {
        _context = context;
    }

    public IActionResult OnGet()
    {
        return Page();
    }

    [BindProperty]
    public Customer Customer { get; set; }

    public async Task<IActionResult> OnPostAsync()
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }

        _context.Customers.Add(Customer);
        await _context.SaveChangesAsync();

        return RedirectToPage("./Index");
    }
}

[BindProperty]tidak boleh digunakan pada model yang berisi properti yang tidak boleh diubah oleh klien. Untuk informasi lebih lanjut, lihat Overposting.

Razor Pages, secara default, hanya mengikat properti dengan kata kerja non-GET. Pengikatan ke properti menghilangkan kebutuhan untuk menulis kode guna mengonversi data HTTP ke jenis model. Pengikatan mengurangi kode dengan menggunakan properti yang sama untuk merender bidang isian borang (<input asp-for="Customer.Name">) dan menerima input.

Peringatan

Karena alasan keamanan, Anda harus memilih untuk mengikat GET data permintaan ke properti model halaman. Verifikasi input pengguna sebelum memetakannya ke properti. Memilih pengikatan GET berguna saat menangani skenario yang mengandalkan string kueri atau nilai rute.

Untuk mengikat properti pada permintaan GET, atur properti SupportsGet atribut [BindProperty] ke true:

[BindProperty(SupportsGet = true)]

Untuk informasi lebih lanjut, lihat ASP.NET Core Community Standup: Bind on GET discussion (YouTube).

Meninjau file tampilan Pages/Create.cshtml:

@page
@model RazorPagesContacts.Pages.Customers.CreateModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<p>Enter a customer name:</p>

<form method="post">
    Name:
    <input asp-for="Customer.Name" />
    <input type="submit" />
</form>
  • Dalam kode sebelumnya, pembantu tag input<input asp-for="Customer.Name" /> mengikat elemen <input> HTML ke ekspresi model Customer.Name.
  • @addTagHelper membuat Pembantu Tag tersedia.

Beranda

Index.cshtml adalah beranda:

@page
@model RazorPagesContacts.Pages.Customers.IndexModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<h1>Contacts home page</h1>
<form method="post">
    <table class="table">
        <thead>
            <tr>
                <th>ID</th>
                <th>Name</th>
                <th></th>
            </tr>
        </thead>
        <tbody>
            @foreach (var contact in Model.Customer)
            {
                <tr>
                    <td> @contact.Id  </td>
                    <td>@contact.Name</td>
                    <td>
                        <!-- <snippet_Edit> -->
                        <a asp-page="./Edit" asp-route-id="@contact.Id">Edit</a> |
                        <!-- </snippet_Edit> -->
                        <!-- <snippet_Delete> -->
                        <button type="submit" asp-page-handler="delete" asp-route-id="@contact.Id">delete</button>
                        <!-- </snippet_Delete> -->
                    </td>
                </tr>
            }
        </tbody>
    </table>
    <a asp-page="Create">Create New</a>
</form>

Kelas PageModel terkait (Index.cshtml.cs):

public class IndexModel : PageModel
{
    private readonly CustomerDbContext _context;

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

    public IList<Customer> Customer { get; set; }

    public async Task OnGetAsync()
    {
        Customer = await _context.Customers.ToListAsync();
    }

    public async Task<IActionResult> OnPostDeleteAsync(int id)
    {
        var contact = await _context.Customers.FindAsync(id);

        if (contact != null)
        {
            _context.Customers.Remove(contact);
            await _context.SaveChangesAsync();
        }

        return RedirectToPage();
    }
}

File Index.cshtml berisi markup berikut:

<a asp-page="./Edit" asp-route-id="@contact.Id">Edit</a> |

<a /a>Pembantu Tag Jangkar menggunakan atribut asp-route-{value} untuk membuat tautan ke halaman Edit. Tautan berisi data rute dengan ID kontak. Contohnya,https://localhost:5001/Edit/1. Pembantu Tag mengaktifkan kode sisi server untuk berpartisipasi dalam membuat dan merender elemen HTML dalam file Razor.

File Index.cshtml berisi markup untuk membuat tombol hapus untuk setiap kontak pelanggan:

<button type="submit" asp-page-handler="delete" asp-route-id="@contact.Id">delete</button>

HTML yang dirender:

<button type="submit" formaction="/Customers?id=1&amp;handler=delete">delete</button>

Saat tombol hapus ditampilkan dalam HTML, formaction-nya menyertakan parameter untuk:

  • ID kontak pelanggan, ditentukan oleh atribut asp-route-id.
  • handler, ditentukan oleh atribut asp-page-handler.

Saat tombol dipilih, permintaan POST formulir dikirim ke server. Berdasarkan konvensi, nama metode penanganan dipilih berdasarkan nilai parameter handler menurut skema OnPost[handler]Async.

Karena handler adalah delete dalam contoh ini, metode penanganan OnPostDeleteAsync digunakan untuk memproses permintaan POST. Jika asp-page-handler diatur ke nilai yang berbeda, seperti remove, metode penanganan dengan nama OnPostRemoveAsync dipilih.

public async Task<IActionResult> OnPostDeleteAsync(int id)
{
    var contact = await _context.Customers.FindAsync(id);

    if (contact != null)
    {
        _context.Customers.Remove(contact);
        await _context.SaveChangesAsync();
    }

    return RedirectToPage();
}

Metode OnPostDeleteAsync:

  • Mendapatkan id dari string kueri.
  • Kueri database untuk kontak pelanggan dengan FindAsync.
  • Jika kontak pelanggan ditemukan, ini akan dihapus dan database diperbarui.
  • Memanggil RedirectToPage untuk mengalihkan ke halaman Indeks akar (/Index).

File Edit.cshtml

@page "{id:int}"
@model RazorPagesContacts.Pages.Customers.EditModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers


<h1>Edit Customer - @Model.Customer.Id</h1>
<form method="post">
    <div asp-validation-summary="All"></div>
    <input asp-for="Customer.Id" type="hidden" />
    <div>
        <label asp-for="Customer.Name"></label>
        <div>
            <input asp-for="Customer.Name" />
            <span asp-validation-for="Customer.Name"></span>
        </div>
    </div>

    <div>
        <button type="submit">Save</button>
    </div>
</form>

Baris pertama berisi arahan @page "{id:int}". Batasan perutean "{id:int}" memberi tahu halaman untuk menerima permintaan ke halaman yang berisi data rute int. Jika permintaan ke halaman tidak berisi data rute yang dapat dikonversi ke int, runtime bahasa umum mengembalikan kesalahan HTTP 404 (tidak ditemukan). Untuk menjadikan ID bersifat opsional, tambahkan ? ke batasan rute:

@page "{id:int?}"

File Edit.cshtml.cs:

public class EditModel : PageModel
{
    private readonly CustomerDbContext _context;

    public EditModel(CustomerDbContext context)
    {
        _context = context;
    }

    [BindProperty]
    public Customer Customer { get; set; }

    public async Task<IActionResult> OnGetAsync(int id)
    {
        Customer = await _context.Customers.FindAsync(id);

        if (Customer == null)
        {
            return RedirectToPage("./Index");
        }

        return Page();
    }

    public async Task<IActionResult> OnPostAsync()
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }

        _context.Attach(Customer).State = EntityState.Modified;

        try
        {
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            throw new Exception($"Customer {Customer.Id} not found!");
        }

        return RedirectToPage("./Index");
    }

}

Validasi

Aturan validasi:

  • Secara deklaratif ditentukan dalam kelas model.
  • Diberlakukan di mana-mana di aplikasi.

Namespace layanan System.ComponentModel.DataAnnotations menyediakan satu set atribut validasi bawaan yang diterapkan secara deklaratif ke kelas atau properti. DataAnnotations juga berisi atribut pemformatan seperti [DataType] yang membantu pemformatan dan tidak memberikan validasi apa pun.

Pertimbangkan model Customer:

using System.ComponentModel.DataAnnotations;

namespace RazorPagesContacts.Models
{
    public class Customer
    {
        public int Id { get; set; }

        [Required, StringLength(10)]
        public string Name { get; set; }
    }
}

Dengan file tampilan Create.cshtml berikut:

@page
@model RazorPagesContacts.Pages.Customers.CreateModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<p>Validation: customer name:</p>

<form method="post">
    <div asp-validation-summary="ModelOnly"></div>
    <span asp-validation-for="Customer.Name"></span>
    Name:
    <input asp-for="Customer.Name" />
    <input type="submit" />
</form>

<script src="~/lib/jquery/dist/jquery.js"></script>
<script src="~/lib/jquery-validation/dist/jquery.validate.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"></script>

Kode sebelumnya:

  • Termasuk skrip validasi jQuery dan jQuery.

  • Menggunakan <div /> dan <span />Pembantu Tag untuk mengaktifkan:

    • Validasi sisi klien.
    • Perenderan kesalahan validasi.
  • Menghasilkan HTML berikut:

    <p>Enter a customer name:</p>
    
    <form method="post">
        Name:
        <input type="text" data-val="true"
               data-val-length="The field Name must be a string with a maximum length of 10."
               data-val-length-max="10" data-val-required="The Name field is required."
               id="Customer_Name" maxlength="10" name="Customer.Name" value="" />
        <input type="submit" />
        <input name="__RequestVerificationToken" type="hidden"
               value="<Antiforgery token here>" />
    </form>
    
    <script src="/lib/jquery/dist/jquery.js"></script>
    <script src="/lib/jquery-validation/dist/jquery.validate.js"></script>
    <script src="/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"></script>
    

Memposting formulir Buat tanpa nilai nama akan menampilkan pesan kesalahan "Bidang Nama diperlukan." pada formulir. Jika JavaScript diaktifkan pada klien, browser menampilkan kesalahan tanpa memposting ke server.

Atribut [StringLength(10)] menghasilkan data-val-length-max="10" pada HTML yang dirender. data-val-length-max mencegah browser agar tidak memasukkan lebih dari panjang maksimum yang ditentukan. Jika alat seperti Fiddler digunakan untuk mengedit dan memutar kembali pos:

  • Dengan nama yang melebihi 10.
  • Pesan kesalahan "Nama bidang harus berupa string dengan panjang maksimum 10." dikembalikan.

Perhatikan model Movie berikut:

using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace RazorPagesMovie.Models
{
    public class Movie
    {
        public int ID { get; set; }

        [StringLength(60, MinimumLength = 3)]
        [Required]
        public string Title { get; set; }

        [Display(Name = "Release Date")]
        [DataType(DataType.Date)]
        public DateTime ReleaseDate { get; set; }

        [Range(1, 100)]
        [DataType(DataType.Currency)]
        [Column(TypeName = "decimal(18, 2)")]
        public decimal Price { get; set; }

        [RegularExpression(@"^[A-Z]+[a-zA-Z\s]*$")]
        [Required]
        [StringLength(30)]
        public string Genre { get; set; }

        [RegularExpression(@"^[A-Z]+[a-zA-Z0-9""'\s-]*$")]
        [StringLength(5)]
        [Required]
        public string Rating { get; set; }
    }
}

Atribut validasi menentukan perilaku yang akan diberlakukan pada properti model tempatnya diterapkan:

  • Atribut Required dan MinimumLength menunjukkan bahwa properti harus memiliki nilai, tetapi tidak ada yang mencegah pengguna memasukkan spasi kosong untuk memenuhi validasi ini.

  • Atribut RegularExpression digunakan untuk membatasi karakter apa yang dapat dimasukkan. Dalam kode sebelumnya, "Genre":

    • Hanya boleh menggunakan huruf.
    • Huruf pertama harus huruf besar. Spasi kosong, angka, dan karakter khusus tidak diperbolehkan.
  • RegularExpression "Rating":

    • Mengharuskan karakter pertama berupa huruf besar.
    • Mengizinkan karakter dan angka khusus di spasi berikutnya. "PG-13" valid untuk rating, tetapi gagal untuk "Genre".
  • Atribut Range membatasi nilai dalam rentang yang ditentukan.

  • Atribut StringLength mengatur panjang maksimum properti string, dan secara opsional, panjang minimumnya.

  • Jenis nilai (seperti decimal, int, float, DateTime) secara inheren diperlukan dan tidak memerlukan atribut [Required].

Halaman Buat untuk model Movie menunjukkan kesalahan tampilan dengan nilai yang tidak valid:

Formulir tampilan film dengan beberapa kesalahan validasi sisi klien jQuery

Untuk informasi selengkapnya, lihat:

Menangani permintaan HEAD dengan fallback penanganan OnGet

Permintaan HEAD memungkinkan pengambilan header untuk sumber daya tertentu. Tidak seperti permintaan GET, permintaan HEAD tidak mengembalikan isi respons.

Biasanya, penanganan OnHead dibuat dan dipanggil untuk permintaan HEAD:

public void OnHead()
{
    HttpContext.Response.Headers.Add("Head Test", "Handled by OnHead!");
}

Razor Pages kembali memanggil penanganan OnGet jika tidak ada penanganan OnHead yang ditentukan.

XSRF/CSRF dan Razor Pages

Razor Pages dilindungi oleh Validasi anti-pemalsuan. FormTagHelper menyuntikkan token anti-pemalsuan ke dalam elemen formulir HTML.

Menggunakan Tata Letak, parsial, template, dan Pembantu Tag dengan Razor Pages

Pages berfungsi dengan semua kemampuan mesin tampilan Razor. Tata letak, parsial, template, Pembantu Tag, _ViewStart.cshtml, dan _ViewImports.cshtml bekerja dengan cara yang sama seperti tampilan Razor konvensional.

Mari kita rapikan halaman ini dengan memanfaatkan beberapa kemampuan tersebut.

Tambahkan halaman tata letak ke Pages/Shared/_Layout.cshtml:

<!DOCTYPE html>
<html>
<head>
    <title>RP Sample</title>
    <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
</head>
<body>
    <a asp-page="/Index">Home</a>
    <a asp-page="/Customers/Create">Create</a>
    <a asp-page="/Customers/Index">Customers</a> <br />

    @RenderBody()
    <script src="~/lib/jquery/dist/jquery.js"></script>
    <script src="~/lib/jquery-validation/dist/jquery.validate.js"></script>
    <script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"></script>
</body>
</html>

Tata Letak:

  • Mengontrol tata letak setiap halaman (kecuali halaman menolak tata letak).
  • Mengimpor struktur HTML seperti JavaScript dan stylesheet.
  • Konten halaman Razor dirender di tempat @RenderBody() dipanggil.

Untuk informasi lebih lanjut, lihat halaman tata letak.

Properti Tata letak diatur di Pages/_ViewStart.cshtml:

@{
    Layout = "_Layout";
}

Tata letaknya ada di folder Pages/Shared. Halaman mencari tampilan lain (tata letak, template, parsial) secara hierarkis, dimulai dari folder yang sama dengan halaman saat ini. Tata letak dalam folder Pages/Shared dapat digunakan dari halaman Razor mana pun di pada folder Pages.

File tata letak harus masuk ke dalam folder Pages/Shared.

Sebaiknya jangan meletakkan file tata letak di folder Views/Shared. Views/Shared adalah pola tampilan MVC. Razor Pages dimaksudkan untuk mengandalkan hierarki folder, bukan konvensi jalur.

Lihat pencarian dari Razor Page yang menyertakan folder Page. Tata letak, template, dan parsial yang digunakan dengan pengontrol MVC dan tampilan Razor konvensional berfungsi.

Tambahkan file Pages/_ViewImports.cshtml:

@namespace RazorPagesContacts.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

@namespace dijelaskan nanti dalam tutorial. Arahan @addTagHelper membawa Pembantu Tag bawaan ke semua halaman dalam folder Pages.

Arahan @namespace diatur pada halaman:

@page
@namespace RazorPagesIntro.Pages.Customers

@model NameSpaceModel

<h2>Name space</h2>
<p>
    @Model.Message
</p>

Arahan @namespace mengatur namespace layanan untuk halaman. Arahan @model tidak perlu menyertakan namespace layanan.

Jika arahan @namespace ada dalam _ViewImports.cshtml, namespace layanan yang ditentukan akan menyediakan awalan untuk namespace layanan yang dihasilkan di Page yang mengimpor arahan @namespace. Sisa namespace layanan yang dihasilkan (bagian akhiran) adalah jalur relatif yang dipisahkan titik antara folder yang berisi _ViewImports.cshtml dan folder yang berisi halaman.

Misalnya, kelas PageModelPages/Customers/Edit.cshtml.cs secara eksplisit mengatur namespace layanan:

namespace RazorPagesContacts.Pages
{
    public class EditModel : PageModel
    {
        private readonly AppDbContext _db;

        public EditModel(AppDbContext db)
        {
            _db = db;
        }

        // Code removed for brevity.

File Pages/_ViewImports.cshtml mengatur namespace layanan berikut:

@namespace RazorPagesContacts.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

Namespace layanan yang dihasilkan untuk Pages/Customers/Edit.cshtmlRazorPage sama dengan kelas PageModel.

@namespacejuga berfungsi dengan tampilan Razor konvensional.

Pertimbangkan file tampilan Pages/Create.cshtml:

@page
@model RazorPagesContacts.Pages.Customers.CreateModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<p>Validation: customer name:</p>

<form method="post">
    <div asp-validation-summary="ModelOnly"></div>
    <span asp-validation-for="Customer.Name"></span>
    Name:
    <input asp-for="Customer.Name" />
    <input type="submit" />
</form>

<script src="~/lib/jquery/dist/jquery.js"></script>
<script src="~/lib/jquery-validation/dist/jquery.validate.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"></script>

File tampilan Pages/Create.cshtml yang diperbarui dengan _ViewImports.cshtml dan file tata letak sebelumnya:

@page
@model CreateModel

<p>Enter a customer name:</p>

<form method="post">
    Name:
    <input asp-for="Customer.Name" />
    <input type="submit" />
</form>

Dalam kode sebelumnya, _ViewImports.cshtml mengimpor namespace layanan dan Pembantu Tag. File tata letak mengimpor file JavaScript.

Proyek starter Razor Pages berisi Pages/_ValidationScriptsPartial.cshtml, yang menghubungkan validasi sisi klien.

Untuk informasi lebih lanjut tentang tampilan parsial, lihat Tampilan parsial di ASP.NET Core.

Pembuatan URL untuk Pages

Halaman Create, yang ditampilkan sebelumnya, menggunakan RedirectToPage:

public class CreateModel : PageModel
{
    private readonly CustomerDbContext _context;

    public CreateModel(CustomerDbContext context)
    {
        _context = context;
    }

    public IActionResult OnGet()
    {
        return Page();
    }

    [BindProperty]
    public Customer Customer { get; set; }

    public async Task<IActionResult> OnPostAsync()
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }

        _context.Customers.Add(Customer);
        await _context.SaveChangesAsync();

        return RedirectToPage("./Index");
    }
}

Aplikasi ini memiliki struktur file/folder berikut:

  • /Pages

    • Index.cshtml

    • Privacy.cshtml

    • /Customers

      • Create.cshtml
      • Edit.cshtml
      • Index.cshtml

Halaman Pages/Customers/Create.cshtml dan Pages/Customers/Edit.cshtml dialihkan ke Pages/Customers/Index.cshtml setelah berhasil. ./Index string adalah nama halaman relatif yang digunakan untuk mengakses halaman sebelumnya. Ini digunakan untuk membuat URL ke halaman Pages/Customers/Index.cshtml. Contohnya:

  • Url.Page("./Index", ...)
  • <a asp-page="./Index">Customers Index Page</a>
  • RedirectToPage("./Index")

/Index nama halaman absolut digunakan untuk membuat URL ke halaman Pages/Index.cshtml. Contohnya:

  • Url.Page("/Index", ...)
  • <a asp-page="/Index">Home Index Page</a>
  • RedirectToPage("/Index")

Nama halaman adalah jalur ke halaman dari folder /Pages akar termasuk awalan / (misalnya, /Index). Sampel pembuatan URL sebelumnya menawarkan opsi yang disempurnakan dan kemampuan fungsional jika dibandingkan dengan hard-coding URL. Pembuatan URL menggunakan perutean dan dapat membuat serta mengodekan parameter sesuai dengan cara rute ditentukan di jalur tujuan.

Pembuatan URL untuk halaman mendukung nama relatif. Tabel berikut menunjukkan halaman Indeks mana yang dipilih menggunakan parameter RedirectToPage yang berbeda di Pages/Customers/Create.cshtml.

RedirectToPage(x) Halaman
RedirectToPage("/Index") Pages/Index
RedirectToPage("./Index"); Pages/Customers/Index
RedirectToPage("../Index") Pages/Index
RedirectToPage("Index") Pages/Customers/Index

RedirectToPage("Index"), RedirectToPage("./Index"), dan RedirectToPage("../Index") adalah nama relatif. Parameter RedirectToPagedigabungkan dengan jalur halaman saat ini untuk menghitung nama halaman tujuan.

Tautan nama relatif berguna saat membangun situs dengan struktur yang kompleks. Saat nama relatif digunakan untuk menautkan antar-halaman dalam folder:

  • Mengganti nama folder tidak merusak tautan relatif.
  • Tautan tidak rusak karena tautan tidak menyertakan nama folder.

Untuk mengalihkan ke halaman di Area yang berbeda, tentukan area:

RedirectToPage("/Index", new { area = "Services" });

Untuk informasi lebih lanjut, lihat Area di ASP.NET Core serta Rute dan konvensi aplikasi Razor Pages di ASP.NET Core.

Atribut ViewData

Data dapat diteruskan ke halaman dengan ViewDataAttribute. Properti dengan atribut [ViewData] memiliki nilai yang disimpan dan dimuat dari ViewDataDictionary.

Dalam contoh berikut, AboutModel menerapkan atribut [ViewData] ke properti Title:

public class AboutModel : PageModel
{
    [ViewData]
    public string Title { get; } = "About";

    public void OnGet()
    {
    }
}

Di halaman Tentang, akses properti Title sebagai properti model:

<h1>@Model.Title</h1>

Dalam tata letak, judul dibaca dari kamus ViewData:

<!DOCTYPE html>
<html lang="en">
<head>
    <title>@ViewData["Title"] - WebApplication</title>
    ...

TempData

ASP.NET Core mengekspos TempData. Properti ini menyimpan data sampai data dibaca. Metode Keep dan Peek dapat digunakan untuk memeriksa data tanpa menghapusnya. TempData berguna untuk pengalihan, ketika data diperlukan untuk lebih dari satu permintaan.

Kode berikut menetapkan nilai Message menggunakan TempData:

public class CreateDotModel : PageModel
{
    private readonly AppDbContext _db;

    public CreateDotModel(AppDbContext db)
    {
        _db = db;
    }

    [TempData]
    public string Message { get; set; }

    [BindProperty]
    public Customer Customer { get; set; }

    public async Task<IActionResult> OnPostAsync()
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }

        _db.Customers.Add(Customer);
        await _db.SaveChangesAsync();
        Message = $"Customer {Customer.Name} added";
        return RedirectToPage("./Index");
    }
}

Markup berikut dalam file Pages/Customers/Index.cshtml menampilkan nilai Message menggunakan TempData.

<h3>Msg: @Model.Message</h3>

Model halaman Pages/Customers/Index.cshtml.cs menerapkan atribut [TempData] ke properti Message.

[TempData]
public string Message { get; set; }

Untuk informasi lebih lanjut, lihat TempData.

Beberapa penanganan per halaman

Halaman berikut menghasilkan markup untuk dua penanganan menggunakan Pembantu Tag asp-page-handler:

@page
@model CreateFATHModel

<html>
<body>
    <p>
        Enter your name.
    </p>
    <div asp-validation-summary="All"></div>
    <form method="POST">
        <div>Name: <input asp-for="Customer.Name" /></div>
        <!-- <snippet_Handlers> -->
        <input type="submit" asp-page-handler="JoinList" value="Join" />
        <input type="submit" asp-page-handler="JoinListUC" value="JOIN UC" />
        <!-- </snippet_Handlers> -->
    </form>
</body>
</html>

Formulir dalam contoh sebelumnya memiliki dua tombol kirim, masing-masing menggunakan FormActionTagHelper untuk mengirim ke URL yang berbeda. Atribut asp-page-handler adalah pendamping asp-page. asp-page-handler menghasilkan URL yang dikirimkan ke setiap metode penanganan yang ditentukan oleh halaman. asp-page tidak ditentukan karena sampel menautkan ke halaman saat ini.

Model halaman :

using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using RazorPagesContacts.Data;

namespace RazorPagesContacts.Pages.Customers
{
    public class CreateFATHModel : PageModel
    {
        private readonly AppDbContext _db;

        public CreateFATHModel(AppDbContext db)
        {
            _db = db;
        }

        [BindProperty]
        public Customer Customer { get; set; }

        public async Task<IActionResult> OnPostJoinListAsync()
        {
            if (!ModelState.IsValid)
            {
                return Page();
            }

            _db.Customers.Add(Customer);
            await _db.SaveChangesAsync();
            return RedirectToPage("/Index");
        }

        public async Task<IActionResult> OnPostJoinListUCAsync()
        {
            if (!ModelState.IsValid)
            {
                return Page();
            }
            Customer.Name = Customer.Name?.ToUpperInvariant();
            return await OnPostJoinListAsync();
        }
    }
}

Kode sebelumnya menggunakan metode penanganan bernama. Metode pengendali bernama dibuat dengan mengambil teks dalam nama setelah On<HTTP Verb> dan sebelum Async (jika ada). Dalam contoh sebelumnya, metode halaman adalah OnPostJoinListAsync dan OnPostJoinListUCAsync. Dengan OnPost dan Async dihapus, nama penangannya adalah JoinList dan JoinListUC.

<input type="submit" asp-page-handler="JoinList" value="Join" />
<input type="submit" asp-page-handler="JoinListUC" value="JOIN UC" />

Dengan menggunakan kode sebelumnya, jalur URL yang mengirimkan ke OnPostJoinListAsync adalah https://localhost:5001/Customers/CreateFATH?handler=JoinList. Jalur URL yang mengirimkan ke OnPostJoinListUCAsync adalah https://localhost:5001/Customers/CreateFATH?handler=JoinListUC.

Rute kustom

Gunakan arahan @page untuk:

  • Menentukan rute kustom ke halaman. Misalnya, rute ke halaman Tentang dapat diatur ke /Some/Other/Path dengan @page "/Some/Other/Path".
  • Menambahkan segmen ke rute default halaman. Misalnya, segmen "item" dapat ditambahkan ke rute default halaman dengan @page "item".
  • Menambahkan parameter ke rute default halaman. Misalnya, parameter ID, id, dapat diperlukan untuk halaman dengan @page "{id}".

Jalur relatif akar yang ditunjuk oleh tilde (~) di awal jalur didukung. Misalnya, @page "~/Some/Other/Path" sama dengan @page "/Some/Other/Path".

Jika Anda tidak menyukai ?handler=JoinList string kueri di URL, ubah rute untuk meletakkan nama penanganan di bagian jalur URL. Rute dapat dikustomisasi dengan menambahkan template rute yang diapit tanda kutip ganda setelah arahan @page.

@page "{handler?}"
@model CreateRouteModel

<html>
<body>
    <p>
        Enter your name.
    </p>
    <div asp-validation-summary="All"></div>
    <form method="POST">
        <div>Name: <input asp-for="Customer.Name" /></div>
        <input type="submit" asp-page-handler="JoinList" value="Join" />
        <input type="submit" asp-page-handler="JoinListUC" value="JOIN UC" />
    </form>
</body>
</html>

Dengan menggunakan kode sebelumnya, jalur URL yang mengirimkan ke OnPostJoinListAsync adalah https://localhost:5001/Customers/CreateFATH/JoinList. Jalur URL yang mengirimkan ke OnPostJoinListUCAsync adalah https://localhost:5001/Customers/CreateFATH/JoinListUC.

? setelah handler berarti parameter rute adalah opsional.

Konfigurasi dan pengaturan tingkat lanjut

Konfigurasi dan pengaturan di bagian berikut tidak diperlukan oleh sebagian besar aplikasi.

Untuk mengonfigurasi opsi tingkat lanjut, gunakan overload AddRazorPages yang mengonfigurasi RazorPagesOptions:

public void ConfigureServices(IServiceCollection services)
{            
    services.AddRazorPages(options =>
    {
        options.RootDirectory = "/MyPages";
        options.Conventions.AuthorizeFolder("/MyPages/Admin");
    });
}

Gunakan RazorPagesOptions untuk mengatur direktori akar untuk halaman, atau menambahkan konvensi model aplikasi untuk halaman. Untuk informasi lebih lanjut tentang konvensi, lihat Konvensi otorisasi Razor Pages.

Untuk mengompilasi tampilan sebelumnya, lihat kompilasi tampilan Razor.

Menentukan bahwa Razor Pages berada di akar konten

Secara default, Razor Pages berakar di direktori /Pages. Tambahkan WithRazorPagesAtContentRoot untuk menentukan bahwa Razor Pages Anda berada di akar konten (ContentRootPath) aplikasi:

public void ConfigureServices(IServiceCollection services)
{            
    services.AddRazorPages(options =>
        {
            options.Conventions.AuthorizeFolder("/MyPages/Admin");
        })
        .WithRazorPagesAtContentRoot();
}

Menentukan bahwa Razor Pages berada di direktori akar kustom

Tambahkan WithRazorPagesRoot untuk menentukan bahwa Razor Pages berada di direktori akar kustom di aplikasi (berikan jalur relatif):

public void ConfigureServices(IServiceCollection services)
{            
    services.AddRazorPages(options =>
        {
            options.Conventions.AuthorizeFolder("/MyPages/Admin");
        })
        .WithRazorPagesRoot("/path/to/razor/pages");
}

Sumber Daya Tambahan: