共用方式為


Razor ASP.NET Core 中的頁面架構和概念

作者:Rick AndersonDave BrockKirk Larkin

Note

這不是這篇文章的最新版本。 關於目前版本,請參閱 本文的 .NET 10 版本

Warning

不再支援此版本的 ASP.NET Core。 如需詳細資訊,請參閱 .NET 和 .NET Core 支持原則。 如需目前的版本,請參閱 本文的 .NET 9 版本。

Razor Pages 可以讓針對以頁面為焦點的案例撰寫程式碼比使用控制器和檢視更簡單、更具生產力。

如果您在尋找使用模型檢視控制器方法的教學課程,請參閱開始使用 ASP.NET Core MVC

本文介紹了使 Pages 有效地 Razor 構建以頁面為中心的 Web 應用程序的架構、概念和模式。 它解釋了 Pages 的運作方式 Razor 、其關鍵元件以及實施的最佳實踐。 如果您偏好透過逐步指示進行實作學習,請參閱 教學課程:使用 ASP.NET Core建立 Razor Pages網頁應用程式。 如需 ASP.NET Core 的概觀,請參閱ASP.NET Core 簡介

Prerequisites

建立 Razor Pages 專案

如需如何建立 Razor Pages 專案的詳細說明,請參閱

Razor 頁面

會在 Razor 中啟用 Program.cs Pages:

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

在上述程式碼中:

請考慮使用基本頁面:

@page

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

上述程式碼看起來很像用於 ASP.NET Core 應用程式的 Razor 檢視檔案,含有控制器和檢視。 讓它不同的是 @page 指示詞。 @page 會將檔案轉換成 MVC 動作,這表示其會直接處理要求,不用透過控制器。 @page 必須是頁面上的第一個 Razor 指示詞。 @page 會影響其他的 Razor 建構行為。 Razor Pages 檔案名稱具有 .cshtml 尾碼。

使用PageModel類別的類似頁面,顯示於下列兩個檔案中。 Pages/Index2.cshtml 檔案:

@page
@using RazorPagesIntro.Pages
@model Index2Model

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

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

依照慣例,PageModel 類別檔案和附加 Razor 的 .cs Page 檔案名稱相同。 例如,先前的 Razor Page 是 Pages/Index2.cshtml。 包含 PageModel 類別的檔案名稱為 Pages/Index2.cshtml.cs

頁面的 URL 路徑關聯是由頁面在檔案系統中的位置決定。 下表顯示 Razor Page 路徑和相符的 URL:

檔案名稱和路徑 相符的網址
/Pages/Index.cshtml //Index
/Pages/Contact.cshtml /Contact
/Pages/Store/Contact.cshtml /Store/Contact
/Pages/Store/Index.cshtml /Store/Store/Index

Notes:

  • 執行階段預設會在 Razor 資料夾中尋找 Pages 的檔案。
  • Index 是 URL 未包含頁面時的預設頁面。

撰寫基本表單

Razor Pages 設計用於製作一般的模式,可搭配網頁瀏覽器一起使用,在建置應用程式時能易於實作。 模型繫結標記協助程式和 HTML 協助程式可與 Razor Page 類別中定義的屬性搭配使用。 Contact 模型請考慮實作基本的「與我們連絡」格式頁面:

在本文件的範例中,會在 DbContext 檔案中初始化

記憶體內資料庫需要 Microsoft.EntityFrameworkCore.InMemory NuGet 套件。

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

資料模型:

using System.ComponentModel.DataAnnotations;

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

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

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

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>

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

依照慣例,PageModel 類別稱之為 <PageName>Model,與頁面位於相同的命名空間。

PageModel 類別可以分離頁面邏輯與頁面展示。 此類別會定義頁面的處理常式,以處理傳送至頁面的要求與用於轉譯頁面的資料。 此分隔允許:

OnPostAsync 要求上執行的頁面具有 POST (當使用者張貼表單時)。 可新增任何 HTTP 指令動詞的處理常式方法。 最常見的處理常式包括:

  • OnGet 以初始化頁面所需的狀態。 在上述程式碼中,OnGet 方法會顯示 Create.cshtmlRazor Page。
  • OnPost 以處理表單提交。

Async 命名尾碼為選擇性,但依慣例通常用於非同步函式。 上述程式碼一般用於 Razor Pages。

如果您熟悉使用控制器和檢視的 ASP.NET 應用程式:

  • 上述範例中的 OnPostAsync 程式碼看起來類似一般的控制器程式碼。
  • 大部分的 MVC 基本類型,例如模型繫結驗證和動作結果,都與 Controllers 和 Razor Pages 相同。

前一個 OnPostAsync 方法:

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

OnPostAsync 的基本流程:

檢查驗證錯誤。

  • 如果沒有任何錯誤,會儲存資料並重新導向。
  • 如果有錯誤,會再次顯示有驗證訊息的頁面。 在許多情況下,會在用戶端上偵測到驗證錯誤,且永遠不會提交到伺服器。

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>

Pages/Customers/Create.cshtml 轉譯的 HTML 標記:

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

在先前的程式碼中,張貼表單:

  • 使用有效的資料:

    • OnPostAsync 處理常式方法會呼叫 RedirectToPage 協助程式方法。 RedirectToPage 會傳回 RedirectToPageResult 的執行個體。 RedirectToPage

      • 這是動作結果。
      • 類似於 RedirectToActionRedirectToRoute (用於控制器和檢視)。
      • 已針對頁面自訂。 在上述範例中,它會重新導向至根索引頁面 (/Index)。 RedirectToPage一節會詳細說明
  • 包含傳遞至伺服器的驗證錯誤:

    • OnPostAsync 處理常式方法會呼叫 Page 協助程式方法。 Page 會傳回 PageResult 的執行個體。 傳回 Page 類似於控制站中的動作傳回 ViewPageResult 是處理常式方法的預設傳回類型。 傳回 void 的處理常式方法會呈現頁面。
    • 在上述範例中,張貼沒有值的表單會導致 ModelState.IsValid 傳回 false。 在此範例中,用戶端上不會顯示任何驗證錯誤。 本文件稍後會討論驗證錯誤處理。
    [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");
    }
    
  • 包含用戶端驗證偵測到的驗證錯誤:

    • 資料不會張貼到伺服器。
    • 本文件稍後會說明用戶端驗證。

Customer 屬性 (property) 使用 [BindProperty] 屬性 (attribute) 加入模型繫結:

[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] 應該用於包含用戶端不應變更屬性的模型。 如需詳細資訊,請參閱大量指派

根據預設,Razor Pages 只會建立屬性與非 GET 動詞之間的繫結。 繫結至屬性就無需撰寫程式碼即可將 HTTP 資料轉換成模型類型。 透過使用相同的屬性呈現表單欄位 (<input asp-for="Customer.Name">) 並接受輸入,繫結可以減少程式碼。

Warning

基於安全性考量,您必須選擇將 GET 要求資料繫結到頁面模型屬性。 請先驗證使用者輸入再將其對應至屬性。 在解決仰賴查詢字串或路由值的案例時,選擇使用 GET 繫結會很有幫助。

若要在 GET 要求上繫結屬性,請將 [BindProperty] 屬性的 SupportsGet 屬性設定為 true

[BindProperty(SupportsGet = true)]

如需詳細資訊,請參閱 ASP.NET Core Community Standup: Bind on GET discussion (YouTube)

檢閱 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>
  • 在上述程式碼中,輸入標籤協助程式<input asp-for="Customer.Name" /> 會將 HTML <input> 元素繫結至 Customer.Name 模型運算式。
  • @addTagHelper 讓標籤協助程式可用。

首頁

Index.cshtml 是首頁:

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

關聯的 PageModel 類別 (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();
    }
}

Index.cshtml 檔案包含下列標記:

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

<a /a> 錨定標記協助程式過去使用 asp-route-{value} 屬性產生 [編輯] 頁面的連結。 該連結包含路由資料和連絡人識別碼。 例如: https://localhost:5001/Edit/1標記協助程式可啟用伺服器端程式碼,以參與建立和轉譯 Razor 檔案中的 HTML 元素。

Index.cshtml 檔案包含能夠為每個客戶連絡人建立刪除按鈕的標記:

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

轉譯的 HTML 標記:

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

使用 HTML 轉譯刪除按鈕時,其 formaction 會包含下列項目的參數:

  • asp-route-id 屬性指定的客戶連絡人識別碼。
  • handler 屬性指定的 asp-page-handler

選取按鈕時,表單 POST 要求會傳送至伺服器。 依照慣例,會依據配置 handler,按 OnPost[handler]Async 參數的值來選取處理常式方法。

在此範例中,因為 handlerdelete,所以會使用 OnPostDeleteAsync 處理常式方法來處理 POST 要求。 若 asp-page-handler 設為其他值 (例如 remove),則會選取名為 OnPostRemoveAsync 的處理常式方法。

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

OnPostDeleteAsync 方法:

  • 取得查詢字串的 id
  • 使用 FindAsync 在資料庫中查詢客戶連絡人。
  • 如果找到客戶連絡人,則會移除該連絡人,並更新資料庫。
  • 呼叫 RedirectToPage 以重新導向至根索引頁 (/Index)。

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

第一行包含 @page "{id:int}" 指示詞。 路由條件約束 "{id:int}" 通知頁面接受包含 int 路由資料的頁面要求。 如果頁面要求不包含可以轉換成 int 的路由資料,執行階段會傳回 HTTP 404 (找不到) 錯誤。 若要使識別碼成為選擇性,請將 ? 附加至路由條件約束:

@page "{id:int?}"

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

Validation

驗證規則:

  • 在模型類別中以宣告方式指定。
  • 會在應用程式中的任何位置強制執行。

System.ComponentModel.DataAnnotations 命名空間提供一組內建的驗證屬性 (attribute),其以宣告方式套用至類別或屬性 (property)。 DataAnnotations 也包含格式化屬性 (如 [DataType]),可協助進行格式化,但不提供任何驗證。

請考量 Customer 模型:

using System.ComponentModel.DataAnnotations;

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

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

使用下列 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>

上述 程式碼:

  • 包含 jQuery 和 jQuery 驗證指令碼。

  • 使用 <div /><span />標籤協助程式來啟用:

    • 用戶端驗證。
    • 驗證錯誤轉譯。
  • 產生下列 HTML:

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

張貼沒有名稱值的 [建立] 表單會顯示表單上的錯誤訊息「需要名稱欄位」。 如果在用戶端上啟用 JavaScript,則瀏覽器會顯示錯誤,而不會張貼到伺服器。

[StringLength(10)] 屬性會在 data-val-length-max="10" 轉譯的 HTML 上產生。 data-val-length-max 會防止瀏覽器輸入超過指定的長度上限。 如果使用 Fiddler 之類的工具來編輯並重新執行貼文:

  • 名稱超過 10。
  • 傳回錯誤訊息「欄位名稱必須是長度上限為 10 的字串」。

請考慮下列 Movie 模型:

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

驗證屬性會指定要對套用目標模型屬性強制執行的行為:

  • RequiredMinimumLength 屬性 (attribute) 指出屬性 (property) 必須是值;但無法防止使用者輸入空格以滿足此驗證。

  • RegularExpression 屬性則用來限制可輸入的字元。 在上述程式碼中,"Genre":

    • 必須指使用字母。
    • 第一個字母必須是大寫。 不允許使用空格、數字和特殊字元。
  • RegularExpression "Rating":

    • 第一個字元必須為大寫字母。
    • 允許後續空格中的特殊字元和數位。 "PG-13" 對分級而言有效,但不適用於 "Genre"。
  • Range 屬性會將值限制在指定的範圍內。

  • StringLength 屬性會設定字串屬性的最大長度,並選擇性設定其最小長度。

  • 實值型別 (如decimalintfloatDateTime) 原本就是必要項目,而且不需要 [Required] 屬性。

Movie 模型的 [建立] 頁面會顯示具有無效值的錯誤:

有多個 jQuery 用戶端驗證錯誤的電影檢視表單

如需詳細資訊,請參閱

CSS 隔離

將 CSS 樣式隔離至個別頁面、檢視和元件,以減少或避免:

  • 相依於可能難以維護的全域樣式。
  • 巢狀內容中的樣式衝突。

若要新增頁面或檢視的限定範圍 CSS 檔案,請將 CSS 樣式放在符合 .cshtml.css 檔案名稱的隨附 .cshtml 檔案中。 在下列範例中,Index.cshtml.css 檔案會提供只套用至 Index.cshtml 頁面或檢視的 CSS 樣式。

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

h1 {
    color: red;
}

在建置時會發生 CSS 隔離。 架構會重寫 CSS 選取器,以符合應用程式頁面或檢視所轉譯的標記。 重寫的 CSS 樣式會組合並產生做為靜態資產 ({APP ASSEMBLY}.styles.css)。 預留位置 {APP ASSEMBLY} 是專案的組件名稱。 組合 CSS 樣式的連結會放在應用程式的版面配置中。

在應用程式 <head> (Pages/Shared/_Layout.cshtml Pages) 的 Razor 內容或 Views/Shared/_Layout.cshtml (MVC) 中,新增或確認組合 CSS 樣式的連結是否存在:

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

在下列範例中,應用程式的組件名稱為 WebApp

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

範圍 CSS 檔案中定義的樣式只會套用至相符檔案的轉譯輸出。 在上述範例中,應用程式其他位置所定義的任何 h1 CSS 宣告都不會與 Index 的標題樣式衝突。 CSS 樣式串聯和繼承規則對於限定範圍的 CSS 檔案仍有效。 例如,直接套用至 <h1> 檔案中 Index.cshtml 元素的樣式會覆寫 Index.cshtml.css 中限定範圍 CSS 檔案的樣式。

Note

為了在發生組合時保證 CSS 樣式隔離,不支援在 Razor 程式碼區塊中匯入 CSS。

CSS 隔離僅適用於 HTML 元素。 標籤協助程式不支援 CSS 隔離。

在組合 CSS 檔案內,每個頁面、檢視或 Razor 元件都會與格式為 b-{STRING} 的範圍識別碼相關聯,其中 {STRING} 預留位置是架構所產生的十個字元字串。 下列範例提供 <h1> Pages 應用程式 Index 頁面中上述 Razor 元素的樣式:

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

在從組合檔案套用 CSS 樣式的 Index 頁面中,範圍識別碼會附加為 HTML 屬性:

<h1 b-3xxtam6d07>

識別碼對於應用程式而言是唯一的。 在建置階段,會使用慣例 {STATIC WEB ASSETS BASE PATH}/Project.lib.scp.css 來建立專案組合,其中預留位置 {STATIC WEB ASSETS BASE PATH} 是靜態 Web 資產基底路徑。

如果使用其他專案 (例如 NuGet 套件或 Razor 類別庫),則組合的檔案:

  • 參考使用 CSS 匯入的樣式。
  • 不會發佈為取用樣式應用程式的靜態 Web 資產。

CSS 前置處理器支援

CSS 前置處理器可用來利用變數、巢狀、模組、混合和繼承等功能來改善 CSS 開發。 雖然 CSS 隔離原生不支援 Sass 或 Less 等 CSS 前置處理器,但只要架構在建置程序期間重寫 CSS 選取器之前進行前置處理器編譯,整合 CSS 前置處理器就會很順暢。 例如,使用 Visual Studio,在 Visual Studio 工作執行器總管中將現有的前置處理器編譯設定為 [建置之前] 工作。

許多協力廠商 NuGet 套件 (例如 AspNetCore.SassCompiler) 可以在 CSS 隔離發生之前,先在建置程序開始時編譯 SASS/SCSS 檔案,而且不需要額外的設定。

CSS 隔離設定

CSS 隔離允許某些進階案例的設定,例如當相依於現有工具或工作流程時。

自訂範圍識別碼格式

在本節中,{Pages|Views} 預留位置為 Pages (適用於 Razor Pages 應用程式) 或為 Views (適用於 MVC 應用程式)。

根據預設,範圍識別碼會使用 b-{STRING} 格式,其中 {STRING} 預留位置是架構所產生的十個字元字串。 若要自訂範圍識別碼格式,請將專案檔更新為所需的模式:

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

在上述範例中,針對 Index.cshtml.css 產生的 CSS 會將其範圍識別碼從 b-{STRING} 變更為 custom-scope-identifier

使用範圍識別碼來達到限定範圍 CSS 檔案的繼承。 在下列專案檔範例中,BaseView.cshtml.css 檔案包含跨檢視的通用樣式。 DerivedView.cshtml.css 檔案會繼承這些樣式。

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

使用萬用字元 (*) 運算子,跨多個檔案共用範圍識別碼:

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

變更靜態 Web 資產的基底路徑

會在應用程式的根目錄產生限定範圍 CSS 檔案。 在專案檔中,使用 StaticWebAssetBasePath 屬性來變更預設路徑。 下列範例會將限定範圍的 CSS 檔案和應用程式的其餘資產放在 _content 路徑:

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

停用自動組合

若要退出架構在執行階段發佈及載入限定範圍檔案的方式,請使用 DisableScopedCssBundling 屬性。 使用此屬性時,其他工具或程序會負責從 obj 目錄取得隔離的 CSS 檔案,並在執行階段將其發佈及載入:

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

Razor 類別庫 (RCL) 支援

Razor 類別庫 (RCL) 提供隔離樣式時,<link> 標籤的 href 屬性會指向 {STATIC WEB ASSET BASE PATH}/{PACKAGE ID}.bundle.scp.css,其中預留位置為:

  • {STATIC WEB ASSET BASE PATH}:靜態 Web 資產基底路徑。
  • {PACKAGE ID}:程式庫的套件識別碼。 如果未在專案檔中指定封裝識別碼,則封裝識別碼預設為專案的組件名稱。

在以下範例中:

  • 靜態 Web 資產基底路徑為 _content/ClassLib
  • 類別庫的組件名稱為 ClassLib

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

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

如需有關 RCL 的詳細資訊,請參閱下列文件:

如需 Blazor CSS 隔離的相關資訊,請參閱 ASP.NET Core Blazor CSS 隔離

使用 OnGet 處理常式後援來處理 HEAD 要求

HEAD 要求可讓您擷取特定資源的標頭。 不同於 GET 要求,HEAD 要求不會傳回回應主體。

一般來說,會為 OnHead 要求建立及呼叫 HEAD 處理常式:

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

若未定義任何 Razor 處理常式,OnGet Pages 會轉而呼叫 OnHead 處理常式。

XSRF/CSRF 和 Razor Pages

Razor Pages 會受到 Antiforgery 驗證的保護。 FormTagHelper 會將防偽權杖插入 HTML 表單元素中。

搭配 Razor Pages 使用版面配置、部分、範本和標記協助程式

Pages 可搭配 Razor 檢視引擎的所有功能一起使用。 版面配置、部分、範本、標記協助程式、_ViewStart.cshtml_ViewImports.cshtml 運作方式一如其在傳統 Razor 檢視中的方式。

可利用這些功能的一部分來整理這個頁面。

新增版面配置頁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>

佈局

  • 控制每個頁面的版面配置 (除非頁面退出版面配置)。
  • 匯入 HTML 結構,例如 JavaScript 和樣式表。
  • 呼叫 Razor 的位置會轉譯 @RenderBody() 頁面的內容。

如需詳細資訊,請參閱版面配置頁

Layout 屬性是在 Pages/_ViewStart.cshtml 中設定:

@{
    Layout = "_Layout";
}

版面配置位於 Pages/Shared 資料夾。 頁面會以階層方式尋找其他檢視 (版面配置、範本、部分),從目前頁面的相同資料夾開始。 您可以從任何 頁面中的 Razor 資料夾下,使用 Pages/Shared 資料夾中的版面配置。

版面配置頁面應位於 Pages/Shared 資料夾中。

我們建議您將配置檔案放入 Views/Shared 資料夾。 Views/Shared 是 MVC 檢視模式。 Razor Pages 應該要依賴資料夾階層,而不是路徑慣例。

Razor 頁面的檢視搜尋包括 Pages 資料夾。 搭配 MVC 控制器使用的版面配置、範本和部分以及傳統的 Razor 檢視「就這麼簡單」

增加 Pages/_ViewImports.cshtml 檔案:

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

本教學課程稍後會說明 @namespace@addTagHelper 指示詞會將內建標記協助程式帶入 Pages 資料夾中的所有頁面。

頁面上設定的 @namespace 指示詞:

@page
@namespace RazorPagesIntro.Pages.Customers

@model NameSpaceModel

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

@namespace 指示詞會設定頁面的命名空間。 @model 指示詞不需要包含命名空間。

@namespace 指示詞包含在 _ViewImports.cshtml 中時,指定的命名空間會在匯入 @namespace 指示詞的頁面中提供所產生之命名空間的前置詞。 所產生命名空間的其餘部分(後綴部分)是包含 _ViewImports.cshtml 的資料夾與包含頁面的資料夾之間的點分隔相對路徑。

例如,PageModel 類別 Pages/Customers/Edit.cshtml.cs 會明確設定命名空間:

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

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

        // Code removed for brevity.

Pages/_ViewImports.cshtml 檔案會設定下列命名空間:

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

Pages/Customers/Edit.cshtmlRazor Page 產生的命名空間與 PageModel 類別相同。

@namespace 也適用於傳統的 Razor 檢視。

請考慮 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>

更新的 Pages/Customers/Create.cshtml 檢視檔案,包含 _ViewImports.cshtml 和上述版面配置檔案:

@page
@model CreateModel

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

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

在上述程式碼中,_ViewImports.cshtml 已匯入命名空間和標籤協助程式。 版面配置檔案已匯入 JavaScript 檔案。

Razor Pages 入門專案包含 Pages/_ValidationScriptsPartial.cshtml,這會連結用戶端驗證。

如需關於部分檢視的詳細資訊,請參閱 ASP.NET Core 中的部分檢視

產生頁面 URL

前面出現過的 Create 頁面使用 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");
    }
}

應用程式有下列檔案/資料夾結構:

  • /Pages

    • Index.cshtml

    • Privacy.cshtml

    • /Customers

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

Pages/Customers/Create.cshtmlPages/Customers/Edit.cshtml 頁面會在成功之後重新導向至 Pages/Customers/Index.cshtml。 字串 ./Index 是用來存取上述頁面的相對頁面名稱。 其可用來產生 Pages/Customers/Index.cshtml 頁面的 URL。 例如:

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

絕對頁面名稱 /Index 可用來產生 Pages/Index.cshtml 頁面的 URL。 例如:

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

頁面名稱是從根 /Pages 資料夾到該頁面的路徑 (包括前置的 /,例如 /Index)。 上述 URL 產生範例,透過硬式編碼的 URL 提供更加優異的選項與功能。 URL 產生使用路由,可以根據路由在目的地路徑中定義的方式,產生並且編碼參數。

產生頁面 URL 支援相關的名稱。 下表顯示從 RedirectToPage 以不同的 Pages/Customers/Create.cshtml 參數選取的索引頁。

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

RedirectToPage("Index")RedirectToPage("./Index")RedirectToPage("../Index") 是「相對名稱」RedirectToPage 參數「結合」了目前頁面的路徑,以計算目的地頁面的名稱。

相對名稱連結在以複雜結構建置網站時很有用。 當使用相對名稱在資料夾中的頁面之間連結時:

  • 重新命名資料夾並不會中斷相對連結。
  • 連結不會中斷,因為其不包含資料夾名稱。

若要重新導向到不同區域中的頁面,請指定區域:

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

如需詳細資訊,請參閱 ASP.NET Core 中的區域ASP.NET Core 中的 Razor Pages 路由以及應用程式慣例

ViewData 屬性

資料可以傳遞至具有 ViewDataAttribute 的頁面。 具有 [ViewData] 屬性 (attribute) 的屬性 (property) 會儲存其值並從 ViewDataDictionary 載入。

在下列範例中,AboutModel 會將 [ViewData] 屬性 (attribute) 套用至 Title 屬性 (property):

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

    public void OnGet()
    {
    }
}

在 [關於] 頁面上,存取 Title 屬性作為模型屬性:

<h1>@Model.Title</h1>

在此配置中,標題會從 ViewData 字典中讀取:

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

TempData

ASP.NET Core 會公開 TempData。 這個屬性會儲存資料,直到讀取為止。 KeepPeek 方法可以用來檢查資料,不用刪除。 TempData 對重新導向很有幫助 (當有多個要求需要資料時)。

下列程式碼會設定使用 MessageTempData 值:

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

Pages/Customers/Index.cshtml 檔案中的下列標記會顯示使用 MessageTempData 值。

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

Pages/Customers/Index.cshtml.cs 頁面模型會將 [TempData] 屬性 (attribute) 套用到 Message 屬性 (property)。

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

如需詳細資訊,請參閱 TempData

每頁面有多個處理常式

下列頁面會使用 asp-page-handler 標記協助程式為兩個處理常式產生標記:

@page
@model CreateFATHModel

<html>
<body>
    <p>
        Enter your name.
    </p>
    <div asp-validation-summary="All"></div>
    <form method="POST">
        <div><label>Name: <input asp-for="Customer.Name" /></label></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>

上例中的表單有兩個提交按鈕,每一個都使用 FormActionTagHelper 提交至不同的 URL。 asp-page-handler 屬性附隨於 asp-pageasp-page-handler 產生的 URL 會提交至頁面所定義的每一個處理常式方法。 因為範例連結至目前的頁面,所以未指定 asp-page

頁面模型:

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

上述程式碼使用「具名的處理常式方法」。 具名的處理常式方法的建立方式是採用名稱中在 On<HTTP Verb> 後面、Async 之前 (如有) 的文字。 在上例中,頁面方法是 OnPostJoinListAsync 和 OnPostJoinListUCAsync。 移除 OnPostAsync,處理常式名稱就是 JoinListJoinListUC

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

使用上述程式碼,提交至 OnPostJoinListAsync 的 URL 路徑是 https://localhost:5001/Customers/CreateFATH?handler=JoinList。 提交至 OnPostJoinListUCAsync 的 URL 路徑是 https://localhost:5001/Customers/CreateFATH?handler=JoinListUC

自訂路由

使用 @page 指示詞,可以:

  • 指定頁面的自訂路由。 例如,[關於] 頁面的路由可使用 /Some/Other/Path 設為 @page "/Some/Other/Path"
  • 將區段附加到頁面的預設路由。 例如,使用 @page "item" 可將 "item" 區段新增到頁面的預設路由。
  • 將參數附加到頁面的預設路由。 例如,具有 id 的頁面可要求識別碼參數 @page "{id}"

支援在路徑開頭以波狀符號 (~) 指定根相對路徑。 例如,@page "~/Some/Other/Path"@page "/Some/Other/Path" 相同。

如果您不喜歡 URL 有查詢字串 ?handler=JoinList,請變更路由,將處理常式名稱置於 URL 的路徑部分。 您可以新增路由範本,在 @page 指示詞後面用雙引號括住,以將路由進行自訂。

@page "{handler?}"
@model CreateRouteModel

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

使用上述程式碼,提交至 OnPostJoinListAsync 的 URL 路徑是 https://localhost:5001/Customers/CreateFATH/JoinList。 提交至 OnPostJoinListUCAsync 的 URL 路徑是 https://localhost:5001/Customers/CreateFATH/JoinListUC

跟在 ? 後面的 handler 表示路由參數為選擇性。

共置 JavaScript (JS) 檔案

將 JavaScript (JS) 檔案組合在一起以用於頁面和檢視,是組織應用程式中指令碼的便利方式。

使用下列副檔名慣例共置 JS 檔案:

  • MVC 應用程式的 Razor Pages 應用程式和檢視的頁面:.cshtml.js。 Examples:
    • Pages/Index.cshtml.js Pages 應用程式 Index 頁面的 Razor 位於 Pages/Index.cshtml
    • MVC 應用程式 Views/Home/Index.cshtml.js 檢視的 Index 位於 Views/Home/Index.cshtml

共置的 JS 檔案可使用專案中檔案的路徑公開定址:

  • 應用程式中並置指令檔中的頁面和檢視:

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

    • {PATH} 預留位置是頁面、檢視或元件的路徑。
    • {PAGE, VIEW, OR COMPONENT} 預留位置是頁面、檢視或元件。
    • {EXTENSION}預留位置符合頁面、檢視或元件的延伸模組,可以是 razorcshtml

    Razor 頁面範例:

    JS 頁面的 Index 檔案會放在 Pages 頁面 (Pages/Index.cshtml.js) 旁的 Index 資料夾 (Pages/Index.cshtml) 中。 在 Index 頁面中,會在 Pages 資料夾的路徑上參考指令碼:

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

預設版面配置 Pages/Shared/_Layout.cshtml 可以設定為包含共置的 JS 檔案,就不需要個別逐一設定單一分頁:

<script asp-src-include="@(ViewContext.View.Path).js"></script>

樣本下載 會使用到上述程式碼片段,即可在預設版面配置中加入共置的 JS 檔案。

發佈應用程式時,架構會自動將指令碼移至 Web 根目錄。 在上述範例中,指令碼會移至 bin\Release\{TARGET FRAMEWORK MONIKER}\publish\wwwroot\Pages\Index.cshtml.js,其中 {TARGET FRAMEWORK MONIKER} 預留位置是目標 Framework Moniker (TFM)。 在 Index 頁面中不需要變更指令碼的相對 URL。

發佈應用程式時,架構會自動將指令碼移至 Web 根目錄。 在上述範例中,指令碼會移至 bin\Release\{TARGET FRAMEWORK MONIKER}\publish\wwwroot\Components\Pages\Index.razor.js,其中 {TARGET FRAMEWORK MONIKER} 預留位置是目標 Framework Moniker (TFM)。 在 Index 元件中不需要變更指令碼的相對 URL。

  • 如需 Razor 類別庫 (RCL) 所提供的指令碼:

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

    • {PACKAGE ID} 預留位置是 RCL 的封裝識別碼 (或應用程式所參考類別庫的程式庫名稱)。
    • {PATH} 預留位置是頁面、檢視或元件的路徑。 如果 Razor 元件位於 RCL 的根目錄,則不包含路徑區段。
    • {PAGE, VIEW, OR COMPONENT} 預留位置是頁面、檢視或元件。
    • {EXTENSION}預留位置符合頁面、檢視或元件的延伸模組,可以是 razorcshtml

進階組態與設定

大部分應用程式不需要下列各節中的組態和設定。

若要設定進階選項,請使用設定 AddRazorPagesRazorPagesOptions 多載:

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

使用 RazorPagesOptions 設定頁面的根目錄,或新增頁面的應用程式模型慣例。 如需慣例的詳細資訊,請參閱 Razor Pages 授權慣例

若要先行編譯檢視,請參閱 Razor 檢視編譯

指定 Razor Pages 位於內容根目錄

根據預設,Razor Pages 位於 /Pages 根目錄。 新增 WithRazorPagesAtContentRoot 以指定您的 Razor Pages 位於應用程式的內容根目錄 (ContentRootPath):

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

指定 Razor Pages 位於自訂根目錄

WithRazorPagesRoot 新增至可指定位於應用程式自訂根目錄的 Razor Pages (提供相對路徑):

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

其他資源

建立 Razor Pages 專案

如需如何建立 Razor Pages 專案的詳細說明,請參閱

Razor 頁面

會在 Razor 中啟用 Startup.cs Pages:

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

請考慮使用基本頁面:

@page

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

上述程式碼看起來很像用於 ASP.NET Core 應用程式的 Razor 檢視檔案,含有控制器和檢視。 讓它不同的是 @page 指示詞。 @page 會將檔案轉換成 MVC 動作,這表示它會直接處理要求,不用透過控制器。 @page 必須是頁面上的第一個 Razor 指示詞。 @page 會影響其他的 Razor 建構行為。 Razor Pages 檔案名稱具有 .cshtml 尾碼。

使用PageModel類別的類似頁面,顯示於下列兩個檔案中。 Pages/Index2.cshtml 檔案:

@page
@using RazorPagesIntro.Pages
@model Index2Model

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

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

依照慣例,PageModel 類別檔案和附加 Razor 的 .cs Page 檔案名稱相同。 例如,先前的 Razor Page 是 Pages/Index2.cshtml。 包含 PageModel 類別的檔案名稱為 Pages/Index2.cshtml.cs

頁面的 URL 路徑關聯是由頁面在檔案系統中的位置決定。 下表顯示 Razor Page 路徑和相符的 URL:

檔案名稱和路徑 相符的網址
/Pages/Index.cshtml //Index
/Pages/Contact.cshtml /Contact
/Pages/Store/Contact.cshtml /Store/Contact
/Pages/Store/Index.cshtml /Store/Store/Index

Notes:

  • 執行階段預設會在 Razor 資料夾中尋找 Pages 的檔案。
  • Index 是 URL 未包含頁面時的預設頁面。

撰寫基本表單

Razor Pages 設計用於製作一般的模式,可搭配網頁瀏覽器一起使用,在建置應用程式時能易於實作。 模型繫結標記協助程式和 HTML 協助程式搭配 Page 類別中定義的屬性「就這麼簡單」Razor。 Contact 模型請考慮實作基本的「與我們連絡」格式頁面:

本文件中的範例,會在 DbContext 檔案中初始化

記憶體內資料庫需要 Microsoft.EntityFrameworkCore.InMemory NuGet 套件。

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

資料模型:

using System.ComponentModel.DataAnnotations;

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

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

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

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>

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

依照慣例,PageModel 類別稱之為 <PageName>Model,與頁面位於相同的命名空間。

PageModel 類別可以分離頁面邏輯與頁面展示。 此類別會定義頁面的處理常式,以處理傳送至頁面的要求與用於轉譯頁面的資料。 此分隔允許:

OnPostAsync 要求上執行的頁面具有 POST (當使用者張貼表單時)。 可新增任何 HTTP 指令動詞的處理常式方法。 最常見的處理常式包括:

  • OnGet 以初始化頁面所需的狀態。 在上述程式碼中,OnGet 方法會顯示 CreateModel.cshtmlRazor Page。
  • OnPost 以處理表單提交。

Async 命名尾碼為選擇性,但依慣例通常用於非同步函式。 上述程式碼一般用於 Razor Pages。

如果您熟悉使用控制器和檢視的 ASP.NET 應用程式:

  • 上述範例中的 OnPostAsync 程式碼看起來類似一般的控制器程式碼。
  • 大部分的 MVC 基本類型,例如模型繫結驗證和動作結果,都與 Controllers 和 Razor Pages 相同。

前一個 OnPostAsync 方法:

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

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

    return RedirectToPage("./Index");
}

OnPostAsync 的基本流程:

檢查驗證錯誤。

  • 如果沒有任何錯誤,會儲存資料並重新導向。
  • 如果有錯誤,會再次顯示有驗證訊息的頁面。 在許多情況下,會在用戶端上偵測到驗證錯誤,且永遠不會提交到伺服器。

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>

Pages/Create.cshtml 轉譯的 HTML 標記:

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

在先前的程式碼中,張貼表單:

  • 使用有效的資料:

    • OnPostAsync 處理常式方法會呼叫 RedirectToPage 協助程式方法。 RedirectToPage 會傳回 RedirectToPageResult 的執行個體。 RedirectToPage

      • 這是動作結果。
      • 類似於 RedirectToActionRedirectToRoute (用於控制器和檢視)。
      • 已針對頁面自訂。 在上述範例中,它會重新導向至根索引頁面 (/Index)。 RedirectToPage一節會詳細說明
  • 包含傳遞至伺服器的驗證錯誤:

    • OnPostAsync 處理常式方法會呼叫 Page 協助程式方法。 Page 會傳回 PageResult 的執行個體。 傳回 Page 類似於控制站中的動作傳回 ViewPageResult 是處理常式方法的預設傳回類型。 傳回 void 的處理常式方法會呈現頁面。
    • 在上述範例中,張貼沒有值的表單會導致 ModelState.IsValid 傳回 false。 在此範例中,用戶端上不會顯示任何驗證錯誤。 本文件稍後會討論驗證錯誤處理。
    public async Task<IActionResult> OnPostAsync()
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }
    
        _context.Customers.Add(Customer);
        await _context.SaveChangesAsync();
    
        return RedirectToPage("./Index");
    }
    
  • 包含用戶端驗證偵測到的驗證錯誤:

    • 資料不會張貼到伺服器。
    • 本文件稍後會說明用戶端驗證。

Customer 屬性 (property) 使用 [BindProperty] 屬性 (attribute) 加入模型繫結:

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] 應該用於包含用戶端不應變更屬性的模型。 如需詳細資訊,請參閱大量指派

根據預設,Razor Pages 只會建立屬性與非 GET 動詞之間的繫結。 繫結至屬性就無需撰寫程式碼即可將 HTTP 資料轉換成模型類型。 透過使用相同的屬性呈現表單欄位 (<input asp-for="Customer.Name">) 並接受輸入,繫結可以減少程式碼。

Warning

基於安全性考量,您必須選擇將 GET 要求資料繫結到頁面模型屬性。 請先驗證使用者輸入再將其對應至屬性。 在解決仰賴查詢字串或路由值的案例時,選擇使用 GET 繫結會很有幫助。

若要在 GET 要求上繫結屬性,請將 [BindProperty] 屬性的 SupportsGet 屬性設定為 true

[BindProperty(SupportsGet = true)]

如需詳細資訊,請參閱 ASP.NET Core Community Standup: Bind on GET discussion (YouTube)

檢閱 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>
  • 在上述程式碼中,輸入標籤協助程式<input asp-for="Customer.Name" /> 會將 HTML <input> 元素繫結至 Customer.Name 模型運算式。
  • @addTagHelper 讓標籤協助程式可用。

首頁

Index.cshtml 是首頁:

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

關聯的 PageModel 類別 (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();
    }
}

Index.cshtml 檔案包含下列標記:

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

<a /a> 錨定標記協助程式過去使用 asp-route-{value} 屬性產生 [編輯] 頁面的連結。 該連結包含路由資料和連絡人識別碼。 例如: https://localhost:5001/Edit/1標記協助程式可啟用伺服器端程式碼,以參與建立和轉譯 Razor 檔案中的 HTML 元素。

Index.cshtml 檔案包含能夠為每個客戶連絡人建立刪除按鈕的標記:

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

轉譯的 HTML 標記:

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

使用 HTML 轉譯刪除按鈕時,其 formaction 會包含下列項目的參數:

  • asp-route-id 屬性指定的客戶連絡人識別碼。
  • handler 屬性指定的 asp-page-handler

選取按鈕時,表單 POST 要求會傳送至伺服器。 依照慣例,會依據配置 handler,按 OnPost[handler]Async 參數的值來選取處理常式方法。

在此範例中,因為 handlerdelete,所以會使用 OnPostDeleteAsync 處理常式方法來處理 POST 要求。 若 asp-page-handler 設為其他值 (例如 remove),則會選取名為 OnPostRemoveAsync 的處理常式方法。

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

OnPostDeleteAsync 方法:

  • 取得查詢字串的 id
  • 使用 FindAsync 在資料庫中查詢客戶連絡人。
  • 如果找到客戶連絡人,則會移除該連絡人,並更新資料庫。
  • 呼叫 RedirectToPage 以重新導向至根索引頁 (/Index)。

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>

第一行包含 @page "{id:int}" 指示詞。 路由條件約束 "{id:int}" 通知頁面接受包含 int 路由資料的頁面要求。 如果頁面要求不包含可以轉換成 int 的路由資料,執行階段會傳回 HTTP 404 (找不到) 錯誤。 若要使識別碼成為選擇性,請將 ? 附加至路由條件約束:

@page "{id:int?}"

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

}

Validation

驗證規則:

  • 在模型類別中以宣告方式指定。
  • 會在應用程式中的任何位置強制執行。

System.ComponentModel.DataAnnotations 命名空間提供一組內建的驗證屬性 (attribute),其以宣告方式套用至類別或屬性 (property)。 DataAnnotations 也包含格式化屬性 (如 [DataType]),可協助進行格式化,但不提供任何驗證。

請考量 Customer 模型:

using System.ComponentModel.DataAnnotations;

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

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

使用下列 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>

上述 程式碼:

  • 包含 jQuery 和 jQuery 驗證指令碼。

  • 使用 <div /><span />標籤協助程式來啟用:

    • 用戶端驗證。
    • 驗證錯誤轉譯。
  • 產生下列 HTML:

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

張貼沒有名稱值的 [建立] 表單會顯示表單上的錯誤訊息「需要名稱欄位」。 如果在用戶端上啟用 JavaScript,則瀏覽器會顯示錯誤,而不會張貼到伺服器。

[StringLength(10)] 屬性會在 data-val-length-max="10" 轉譯的 HTML 上產生。 data-val-length-max 會防止瀏覽器輸入超過指定的長度上限。 如果使用 Fiddler 之類的工具來編輯並重新執行貼文:

  • 名稱超過 10。
  • 傳回錯誤訊息「欄位名稱必須是長度上限為 10 的字串」。

請考慮下列 Movie 模型:

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

驗證屬性會指定要對套用目標模型屬性強制執行的行為:

  • RequiredMinimumLength 屬性 (attribute) 指出屬性 (property) 必須是值;但無法防止使用者輸入空格以滿足此驗證。

  • RegularExpression 屬性則用來限制可輸入的字元。 在上述程式碼中,"Genre":

    • 必須指使用字母。
    • 第一個字母必須是大寫。 不允許使用空格、數字和特殊字元。
  • RegularExpression "Rating":

    • 第一個字元必須為大寫字母。
    • 允許後續空格中的特殊字元和數位。 "PG-13" 對分級而言有效,但不適用於 "Genre"。
  • Range 屬性會將值限制在指定的範圍內。

  • StringLength 屬性會設定字串屬性的最大長度,並選擇性設定其最小長度。

  • 實值型別 (如decimalintfloatDateTime) 原本就是必要項目,而且不需要 [Required] 屬性。

Movie 模型的 [建立] 頁面會顯示具有無效值的錯誤:

有多個 jQuery 用戶端驗證錯誤的電影檢視表單

如需詳細資訊,請參閱

使用 OnGet 處理常式後援來處理 HEAD 要求

HEAD 要求可讓您擷取特定資源的標頭。 不同於 GET 要求,HEAD 要求不會傳回回應主體。

一般來說,會為 OnHead 要求建立及呼叫 HEAD 處理常式:

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

若未定義任何 Razor 處理常式,OnGet Pages 會轉而呼叫 OnHead 處理常式。

XSRF/CSRF 和 Razor Pages

Razor Pages 會受到 Antiforgery 驗證的保護。 FormTagHelper 會將防偽權杖插入 HTML 表單元素中。

搭配 Razor Pages 使用版面配置、部分、範本和標記協助程式

Pages 可搭配 Razor 檢視引擎的所有功能一起使用。 版面配置、部分、範本、標記協助程式、_ViewStart.cshtml_ViewImports.cshtml 運作方式一如其在傳統 Razor 檢視中的方式。

可利用這些功能的一部分來整理這個頁面。

新增版面配置頁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>

佈局

  • 控制每個頁面的版面配置 (除非頁面退出版面配置)。
  • 匯入 HTML 結構,例如 JavaScript 和樣式表。
  • 呼叫 Razor 的位置會轉譯 @RenderBody() 頁面的內容。

如需詳細資訊,請參閱版面配置頁

Layout 屬性是在 Pages/_ViewStart.cshtml 中設定:

@{
    Layout = "_Layout";
}

版面配置位於 Pages/Shared 資料夾。 頁面會以階層方式尋找其他檢視 (版面配置、範本、部分),從目前頁面的相同資料夾開始。 您可以從任何 頁面中的 Razor 資料夾下,使用 Pages/Shared 資料夾中的版面配置。

版面配置頁面應位於 Pages/Shared 資料夾中。

我們建議您將配置檔案放入 Views/Shared 資料夾。 Views/Shared 是 MVC 檢視模式。 Razor Pages 應該要依賴資料夾階層,而不是路徑慣例。

Razor 頁面的檢視搜尋包括 Pages 資料夾。 搭配 MVC 控制器使用的版面配置、範本和部分以及傳統的 Razor 檢視「就這麼簡單」

增加 Pages/_ViewImports.cshtml 檔案:

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

本教學課程稍後會說明 @namespace@addTagHelper 指示詞會將內建標記協助程式帶入 Pages 資料夾中的所有頁面。

頁面上設定的 @namespace 指示詞:

@page
@namespace RazorPagesIntro.Pages.Customers

@model NameSpaceModel

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

@namespace 指示詞會設定頁面的命名空間。 @model 指示詞不需要包含命名空間。

@namespace 指示詞包含在 _ViewImports.cshtml 中時,指定的命名空間會在匯入 @namespace 指示詞的頁面中提供所產生之命名空間的前置詞。 所產生命名空間的其餘部分(後綴部分)是包含 _ViewImports.cshtml 的資料夾與包含頁面的資料夾之間的點分隔相對路徑。

例如,PageModel 類別 Pages/Customers/Edit.cshtml.cs 會明確設定命名空間:

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

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

        // Code removed for brevity.

Pages/_ViewImports.cshtml 檔案會設定下列命名空間:

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

Pages/Customers/Edit.cshtmlRazor Page 產生的命名空間與 PageModel 類別相同。

@namespace 也適用於傳統的 Razor 檢視。

請考慮 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>

更新的 Pages/Create.cshtml 檢視檔案,包含 _ViewImports.cshtml 和上述版面配置檔案:

@page
@model CreateModel

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

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

在上述程式碼中,_ViewImports.cshtml 已匯入命名空間和標籤協助程式。 版面配置檔案已匯入 JavaScript 檔案。

Razor Pages 入門專案包含 Pages/_ValidationScriptsPartial.cshtml,這會連結用戶端驗證。

如需關於部分檢視的詳細資訊,請參閱 ASP.NET Core 中的部分檢視

產生頁面 URL

前面出現過的 Create 頁面使用 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");
    }
}

應用程式有下列檔案/資料夾結構:

  • /Pages

    • Index.cshtml

    • Privacy.cshtml

    • /Customers

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

Pages/Customers/Create.cshtmlPages/Customers/Edit.cshtml 頁面會在成功之後重新導向至 Pages/Customers/Index.cshtml。 字串 ./Index 是用來存取上述頁面的相對頁面名稱。 其可用來產生 Pages/Customers/Index.cshtml 頁面的 URL。 例如:

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

絕對頁面名稱 /Index 可用來產生 Pages/Index.cshtml 頁面的 URL。 例如:

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

頁面名稱是從根 /Pages 資料夾到該頁面的路徑 (包括前置的 /,例如 /Index)。 上述 URL 產生範例,透過硬式編碼的 URL 提供更加優異的選項與功能。 URL 產生使用路由,可以根據路由在目的地路徑中定義的方式,產生並且編碼參數。

產生頁面 URL 支援相關的名稱。 下表顯示從 RedirectToPage 以不同的 Pages/Customers/Create.cshtml 參數選取的索引頁。

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

RedirectToPage("Index")RedirectToPage("./Index")RedirectToPage("../Index") 是「相對名稱」RedirectToPage 參數「結合」了目前頁面的路徑,以計算目的地頁面的名稱。

相對名稱連結在以複雜結構建置網站時很有用。 當使用相對名稱在資料夾中的頁面之間連結時:

  • 重新命名資料夾並不會中斷相對連結。
  • 連結不會中斷,因為其不包含資料夾名稱。

若要重新導向到不同區域中的頁面,請指定區域:

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

如需詳細資訊,請參閱 ASP.NET Core 中的區域ASP.NET Core 中的 Razor Pages 路由以及應用程式慣例

ViewData 屬性

資料可以傳遞至具有 ViewDataAttribute 的頁面。 具有 [ViewData] 屬性 (attribute) 的屬性 (property) 會儲存其值並從 ViewDataDictionary 載入。

在下列範例中,AboutModel 會將 [ViewData] 屬性 (attribute) 套用至 Title 屬性 (property):

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

    public void OnGet()
    {
    }
}

在 [關於] 頁面上,存取 Title 屬性作為模型屬性:

<h1>@Model.Title</h1>

在此配置中,標題會從 ViewData 字典中讀取:

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

TempData

ASP.NET Core 會公開 TempData。 這個屬性會儲存資料,直到讀取為止。 KeepPeek 方法可以用來檢查資料,不用刪除。 TempData 對重新導向很有幫助 (當有多個要求需要資料時)。

下列程式碼會設定使用 MessageTempData 值:

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

Pages/Customers/Index.cshtml 檔案中的下列標記會顯示使用 MessageTempData 值。

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

Pages/Customers/Index.cshtml.cs 頁面模型會將 [TempData] 屬性 (attribute) 套用到 Message 屬性 (property)。

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

如需詳細資訊,請參閱 TempData

每頁面有多個處理常式

下列頁面會使用 asp-page-handler 標記協助程式為兩個處理常式產生標記:

@page
@model CreateFATHModel

<html>
<body>
    <p>
        Enter your name.
    </p>
    <div asp-validation-summary="All"></div>
    <form method="POST">
        <div><label>Name: <input asp-for="Customer.Name" /></label></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>

上例中的表單有兩個提交按鈕,每一個都使用 FormActionTagHelper 提交至不同的 URL。 asp-page-handler 屬性附隨於 asp-pageasp-page-handler 產生的 URL 會提交至頁面所定義的每一個處理常式方法。 因為範例連結至目前的頁面,所以未指定 asp-page

頁面模型:

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

上述程式碼使用「具名的處理常式方法」。 具名的處理常式方法的建立方式是採用名稱中在 On<HTTP Verb> 後面、Async 之前 (如有) 的文字。 在上例中,頁面方法是 OnPostJoinListAsync 和 OnPostJoinListUCAsync。 移除 OnPostAsync,處理常式名稱就是 JoinListJoinListUC

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

使用上述程式碼,提交至 OnPostJoinListAsync 的 URL 路徑是 https://localhost:5001/Customers/CreateFATH?handler=JoinList。 提交至 OnPostJoinListUCAsync 的 URL 路徑是 https://localhost:5001/Customers/CreateFATH?handler=JoinListUC

自訂路由

使用 @page 指示詞,可以:

  • 指定頁面的自訂路由。 例如,[關於] 頁面的路由可使用 /Some/Other/Path 設為 @page "/Some/Other/Path"
  • 將區段附加到頁面的預設路由。 例如,使用 @page "item" 可將 "item" 區段新增到頁面的預設路由。
  • 將參數附加到頁面的預設路由。 例如,具有 id 的頁面可要求識別碼參數 @page "{id}"

支援在路徑開頭以波狀符號 (~) 指定根相對路徑。 例如,@page "~/Some/Other/Path"@page "/Some/Other/Path" 相同。

如果您不喜歡 URL 有查詢字串 ?handler=JoinList,請變更路由,將處理常式名稱置於 URL 的路徑部分。 您可以新增路由範本,在 @page 指示詞後面用雙引號括住,以將路由進行自訂。

@page "{handler?}"
@model CreateRouteModel

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

使用上述程式碼,提交至 OnPostJoinListAsync 的 URL 路徑是 https://localhost:5001/Customers/CreateFATH/JoinList。 提交至 OnPostJoinListUCAsync 的 URL 路徑是 https://localhost:5001/Customers/CreateFATH/JoinListUC

跟在 ? 後面的 handler 表示路由參數為選擇性。

進階組態與設定

大部分應用程式不需要下列各節中的組態和設定。

若要設定進階選項,請使用設定 AddRazorPagesRazorPagesOptions 多載:

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

使用 RazorPagesOptions 設定頁面的根目錄,或新增頁面的應用程式模型慣例。 如需慣例的詳細資訊,請參閱 Razor Pages 授權慣例

若要先行編譯檢視,請參閱 Razor 檢視編譯

指定 Razor Pages 位於內容根目錄

根據預設,Razor Pages 位於 /Pages 根目錄。 新增 WithRazorPagesAtContentRoot 以指定您的 Razor Pages 位於應用程式的內容根目錄 (ContentRootPath):

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

指定 Razor Pages 位於自訂根目錄

WithRazorPagesRoot 新增至可指定位於應用程式自訂根目錄的 Razor Pages (提供相對路徑):

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

其他資源