上回介紹過 Razor Pages,是 ASP.NET Core 在 MVC 之外的簡便選擇, 概念上更貼近 WebForm 的簡單直覺,只需一個 .cshtml 加一個 .cshtml.cs 就可寫出動態網頁。

Razor Pages 的 GET 與 POST 伺服器端邏輯要寫成 Model 物件的 OnGet 與 OnPost 方法, 若不同按鈕要對映不同 POST 行為則可寫成 OnPostMethod1()、OnPostMethod2(), 前端再透過 asp-page-handler 註記 <button type="submit" asp-page-handler="Method1"><button type="submit" asp-page-handler="Method2"> 指向各自的 Post 端邏輯。 呼叫時會 URL 多出 ?handler=Method1、?hadler=Method2 參數,Razor Pages 就是依據它 決定該由 OnPostMethod1() 還是 OnPostMethod2() 處理。

但如果是要透過 jQuery.get()、jQuery.post() 呼叫 Razor Pages 怎麼辦?

洞悉 Razor Pages 是透過 ?handler 決定呼叫哪一段程式,不難推導只要在 AJAX URL 加上 ?handler=MethodName, 伺服器端再寫個 OnGetMethodName() 或 OnPostMethodName() 傳回內容便大功告成了。

來試試。

開個 ASP.NET Core 新專案,在 Index.cshtml 放入三個 <button>, 分別測試標準的 <form> 送出、AJAX GET 呼叫以及 AJAX POST 呼叫:

@page
@model IndexModel
@{
    Layout = null;
    ViewData["Title"] = "Home page";
}
<html>
<body>
    <form method="post">
        <div id="dvMsg">@Model.Message</div>
        <button type="submit" asp-page-handler="Refresh">NewGuid (Postback)</button>
        <button type="button" id="btnAjaxGet">NewGuid (Ajax GET)</button>
        <button type="button" id="btnAjaxPost">NewGuid (Ajax POST)</button>
    </form>
    <script src="~/lib/jquery/dist/jquery.min.js"></script>
    <script>
        $("#btnAjaxGet").click(function () {
            //忽略例外處理
            $.get("Index?handler=ReadMsg").done(function (res) {
                $("#dvMsg").text(res);
            });
        });
        $("#btnAjaxPost").click(function () {
            $.ajax({
                method: "post",
                url: "Index?handler=ReadMsg",
                error: function (xhr, status, err) {
                    alert(err);
                }
            }).done(function (res) {
                $("#dvMsg").text(res);
            });
        });
    </script>
</body>
</html>

Index.cshtml.cs 部分也很簡單,只需三個方法:

  • OnPostRefresh() 處理標準的表單送出動作
  • OnGetReadMsg() 處理 jQuery.get() 取值
  • OnPostReadMsg() 則處理 jQuery.ajax() 送出的 POST 呼叫

程式碼如下:

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

namespace Demo1.Pages
{
    public class IndexModel : PageModel
    {
        public string Message { get; set; } = DateTime.Now.Ticks.ToString();
        public void OnGet()
        {
            
        }

        public void OnPostRefresh()
        {
            Message = DateTime.Now.Ticks.ToString() + ":PostBack";
        }

        public IActionResult OnGetReadMsg()
        {
            return Content(DateTime.Now.Ticks.ToString() + ":AjaxGet");
        }

        public IActionResult OnPostReadMsg()
        {
            return Content(DateTime.Now.Ticks.ToString() + ":AjaxPost");
        }
    }
}

實測一下,表單送出與 AJAX GET 成功,但是 AJAX POST 得到 Bad Request 錯誤。

原因是 ASP.NET Core 內建 XSRF/CSRF (Cross-Site Request Forgery,跨站請求偽造) 防護功能, 會阻擋來路不明的 POST 請求。其原理是在 Razor Pages 網頁加入隱藏欄位 __RequestVerificationToken,內含類似通關密碼的一串編碼, 發送 POST 時必須附上讓伺服器端檢查真偽,若未提供或驗證未過便判定呼叫不合法回傳 HTTP 400 Bad Request。

因此在做 AJAX POST 呼叫時,我們要在 Request Header 附上 __RequestVerificationToken 內容,伺服器則要調整設定通知 ASP.NET Core 從 Header 取出驗證 Token 檢查。

我們先修改 JavaScript 端,送出 POST 請求前,由<input type="hidden" name="__RequestVerificationToken">取出防偽 Token, 以 X-CSRF-TOKEN 為名加入 Request Header:

$("#btnAjaxPost").click(function () {
    $.ajax({
        method: "post",
        url: "Index?handler=ReadMsg",
        //加上X-CSRF-TOKEN header
        beforeSend: function (xhr) {
            xhr.setRequestHeader("X-CSRF-TOKEN",
                $('input:hidden[name="__RequestVerificationToken"]').val());
        },
        error: function (xhr, status, err) {
            alert(err);
        }
    }).done(function (res) {
        $("#dvMsg").text(res);
    });
});

伺服器端則要修改 Startup.cs,宣告我們會在 X-CSRF-TOKEN Request Header 傳送防偽 Token:

public void ConfigureServices(IServiceCollection services)
{
    //... 省略 ...
    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
    //宣告 AJAX POST 使用的 Header 名稱
    services.AddAntiforgery(o => o.HeaderName = "X-CSRF-TOKEN");
}

搞定收工!

Tutorial of how to implement AJAX call in Razor Pages.


Comments

# by overing

最近正好也在研究 Razor Pages 怎麼寫 黑大的及時雨太棒了

# by Gain

您好,如果要用get傳值 $.get("Index?handler=ReadMsg").done(function (res) { $("#dvMsg").text(res); }); 裡面的Url要怎麼去定義呢? 還有後臺的OnGet方法裡面的參數要怎麼定義去接收呢? 謝謝。

# by Jeffrey

to Gain, 用GET+Handler的範例不多,這裡有一個:https://medium.com/@DomBurf/asp-net-core-2-0-razor-page-handlers-369116576b19

# by longer

使用Post下面方法,回傳出來的卻是整個頁面的html code?? public IActionResult OnPostReadMsg() { return Content("test"); }

# by Jeffrey

to longer,先確認呼叫 URL 是 Index?handler=ReadMsg,建議用瀏覽器 F12 工具看一下 Request 內容。

# by longer

你的提醒讓我想到是不有快取? 所以我把Ajax post url改成錯誤的ulr,直接出錯404,再回來試就正常,這種狀況讓人試到快抓狂 感謝你!

# by longer

您的提醒,我剛剛把網頁Ajax post url改成錯誤的ulr,直接出錯404,再回來試就正常,哪有這回事?難不成有快取?真試人試到快抓狂

# by Lauyea

黑大你好,我有個功能是:按鈕按下去就發出一個要求,去改資料表中的一個bool值。 我目前是用form+post去呼叫handler的方式去發要求,但我發現這樣還是會變成postback,他還是會重整整個頁面,讓我其他沒有拿出來的資料顯示null。 因為我不太熟悉AJAX,想問問有沒有什麼辦法,只發出一個簡單的要求,就可以改動資料表其中一個值,又不用重整頁面? 感謝百忙中抽空解惑!

# by Jeffrey

to Lauyea,這樣的需求建議用 JavaScript XHR 處理,你可以參考文章 $("#btnAjaxPost").click(...) 範例,done() 那一段會收到伺服器端傳回的內容,再用它來改動網頁表格的特定元素,但前題是你要懂一些 JavaScript 或 jQuery 才能勝任。有個逃避不學 JS 的做法是把要變動的部分切成 IFrame 嵌入一個只顯示數字/文字的特別 cshtml,需要更新時重新載入內嵌網頁(網頁其他部分不變),但建議還用 JS 解決才是王道。

# by Lauyea

了解了,看起來還是AJAX比較容易,如果希望達成類似效果還是避不開JS。 因為我是用Razor pages當作輕前端,JS程式碼的Debug一直都讓我很頭痛...而且之後.NET 的 Blazor如果可以實際應用,對於只擅長C#的我來說,容易Debug又能夠做出SPA的效果,感覺真的很棒XD 再次感謝黑大的幫忙!

Post a comment