依循 ASP.NET 的發展規劃,WebForm 將留在原地,目送 ASP.NET Core 的車尾燈離去。(延伸閱讀:ASP.NET 簡史)

不過,與 MVC 相比,WebForm 以單一網頁為核心的精神並非一無可取,對一些超小型應用而言,硬要拆分 Model、View、Controller 反而讓事情複雜化。 WebForm 的組成單純,靠一個 .aspx 放 HTML/CSS/JavaScript、一個 .aspx.cs 寫按鈕的 Server 端事件,兩個檔案搞定網頁完整流程,邏輯集中,維護修改方便, 不失為處理簡單情境的良好設計。(但別誤會,中大型專案建議還是該回歸 MVC 這類嚴謹分割關注點的設計架構,日後擴充維護才不致變成災難。)

有個好消息! WebForm 將逝,但它的簡約精神會由 Razor Pages 接手,在 ASP.NET Core 時代繼續傳承下去。

最近啟動一個 Coding4Fun 專案,我心一橫選了 Razor Page 嚐鮮,寫了兩天感覺還不錯,順手寫篇筆記跟大家介紹 Razor Pages 長什麼樣子。

我用一個簡單的 WebForm 例子來對照,展示 Razor Page 如何在 ASP.NET Core 時代重現 WebForm 的簡約精神。

假設 WebForm 範例長這樣:

Test.aspx

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Test.aspx.cs" Inherits="WebApplication1.Test" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
        <div>
            <asp:Label ID="txtNow" runat="server"></asp:Label>
            ,姓名:
            <asp:TextBox ID="txtName" runat="server" placeholder="請輸入姓名" autocomplete="off"></asp:TextBox>
        </div>
        <div>
            <asp:Button ID="btnEngGreet" runat="server" Text="Say Hello" OnClick="btnEngGreet_Click" /> 
            <asp:Button ID="btnChtGreet" runat="server" Text="問好" OnClick="btnChtGreet_Click" />
        </div>
        <div>
            <asp:Label ID="lblMessage" runat="server"></asp:Label>
        </div>
    </form>
</body>
</html>

一個輸入框兩顆按鈕,按鈕後觸發不同行為,後端邏輯寫在 Test.aspx.cs:

using System;
using System.Web.UI;

namespace WebApplication1
{
    public partial class Test : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            if (!Page.IsPostBack) txtNow.Text = $"{DateTime.Now:HH:mm:ss}";
        }

        protected void btnEngGreet_Click(object sender, EventArgs e)
        {
            lblMessage.Text = $"Hello, {txtName.Text}";
        }

        protected void btnChtGreet_Click(object sender, EventArgs e)
        {
            lblMessage.Text = $"{txtName.Text}, 您好";
        }
    }
}

程式原理很簡單,輸入姓名欄位(txtName),按下兩顆按鈕(btnEngGreet、btnChtGreet)會觸發 Server 端事件修改 lblMessage 顯示英文或中文問侯語。 最前方有個 lblNow 顯示現在時刻,它只在第一次載入時取得現在時間,之後 PostBack 時以 ViewState 保存原本內容。lblNow 是我故意加入用來演練 Razor Page 要如何不靠 ViewState 機制實踐狀態保存效果:

如何建立 ASP.NET Core Razor Pages 專案請參考 ASP.NET 官方網站教學,在此不再贅述。 我們在專案 Pages 資料夾新增名為 Test 的 Razor page,試著做出跟 Test.aspx 相同的效果:

WebForm 有 Test.aspx 與 Test.aspx.cs,用 VS2019 新增 Razor Page 後,專案會產生 Test.cshtml 與 Test.cshtml.cs 兩個檔案,性質相當於 Test.aspx 與 Test.aspx.cs。

Test.cshtml 範例如下。

@page
@model RazorPageWeb.Pages.TestModel
@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Test</title>
</head>
<body>
    <form method="POST">
        <div>
            <span>@Model.Now</span>
            <input type="hidden" asp-for="Now" />
            ,姓名:
            <input asp-for="Name" placeholder="請輸入姓名" autocomplete="off" />
        </div>
        <div>
            <button type="submit" asp-page-handler="EngGreet">
                Say Hello
            </button>
            <button type="submit" asp-page-handler="ChtGreet">
                問好
            </button>
        </div>
        <div>
            @Model.Message
        </div>
    </form>
</body>
</html>

一開始的 @model RazorPageWeb.Pages.TestModel 將面頁繫結對象指向名為 TestModel 的專屬型別,而 TestModel 被定義在 Test.cshtml.cs,用以實作 cshtml 所需的欄位屬性、按鈕事件。 <input> 元素的 asp-for="..." 可宣告將該欄位繫結到 TestModel 的特定屬性。由於 Razor Page 沒有 ViewState,我設了一個 <input type="hidden" asp-for="Now" /> 保存第一次 GET 載入時取得的時間,每次 POST 時持續回傳以維持內容不變。兩顆按鈕需觸發不同事件,Razor Page 的做法是在 button 上加註 asp-page-handler="HandlerName", 則 TestModel 可宣告 OnPostHandlerName() 方法與其對應,例如:asp-page-handler="EngGreet" 與 asp-page-handler="ChtGreet" 將對應到 OnPostEngGreet() 與 OnPostChtGreet()。

Test.cshtml.cs 範例如下:

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

namespace RazorPageWeb.Pages
{
    public class TestModel : PageModel
    {
        [BindProperty]
        public string Name { get; set; }
        [BindProperty]
        public string Now { get; set; }
        public string Message { get; set; }

        public void OnGet()
        {
            Now = DateTime.Now.ToString("HH:mm:ss");
        }

        public void OnPostEngGreet()
        {
            Message = $"Hello, {Name}";
        }

        public void OnPostChtGreet()
        {
            Message = $"{Name}, 您好";
        }

    }
}

程式的主體是一個繼承 PageModel 的類別,名為 TestModel,即 Test.cshtml 一開始 @model 所指向的型別。其中的 Name、Now、Message 在 cshtml 都出現過,Name 與 Now 加上 [BindProperty] Attribute 與 .cshtml 中的 <input asp-for="..."> 呼應,OnPostEngGreet()/OnPostChtGreet() 時 Razor Page 也會自動將前端傳入的欄位值寫入 Name、Now,可省去自己 Request.Form["Name"] 取值的麻煩。餘下的邏輯很簡單,應不用多做解釋大家就看得懂。

實測一下,薑薑薑薑~~~ 成功!

就這樣,我們輕輕鬆鬆用 Razor Page 重現 WebForm 的簡約風 ASP.NET 網頁設計,很酷吧!

This articel use a simple WebForm example to demostrate how to use Razor Page to implement simple web page.


Comments

# by solonglin

請教一下黑大,Razor Pages 好像不支援在OnGet 或OnPost 套上 [Authorize] 請教實務上這個問題通常會怎麼做比較簡單 只是真的只能在方法裡面寫 User.Identity.IsAuthenticated 來判斷

# by Jeffrey

to solonglin, 依據查到的資料 https://github.com/dotnet/aspnetcore/issues/8737 ,Razor Pages 的 [Authorize] 只適用在 Page Model 層,無法依 Handler 個別設定,如果不想在每個 Handler 加程式,可考慮寫在 IAsyncPageFilter.OnPageHandlerSelectionAsync。

# by solonglin

感謝黑大,我再研究看看!

# by Ho.Chun

了解了 dotnet framwork 的 webform 對應 dotnet (core) 的 razor page 想請問 dotnet framwork 的 webpage 對應 dotnet (core) 哪種類型的專案 ?

# by Jeffrey

to Ho.Chun, VS 的話選「ASP.NET Core Web 應用程式/ASP.NET Core Web App」,VSCode 的話可下指令 dotnet new webapp https://learn.microsoft.com/zh-tw/aspnet/core/tutorials/razor-pages/razor-pages-start?view=aspnetcore-7.0&tabs=visual-studio&WT.mc_id=DOP-MVP-37580#create-a-razor-pages-web-app

Post a comment