demoshop

demo, trying to be the best_

相信很多人是直接跳這裡看結論的,但我真的希望你結論看完以後回頭看看其他兩篇的文字與範例程式碼,你應該更可以感受到 Razor Pages 的優良之處,我現在一個專案開始的時候我會優先選擇使用  Razor Pages , 除非我在初期就明確的知道這個專案在 MVC 會比較好寫,這兩者也不應該是堅持一種,應該看專案的不同與專案成員的能力來選擇,只會一種你就沒得選,會兩種你的機會就大,我們有在開設 Razor Pages 的課程,歡迎你來加大自己的版圖,讓你也有[選]的能力。

精準解析 RazorPages
https://skilltr.ee/razorpage            

前言

前一篇談完了新增,這篇真的要來講編輯了,在會員帳號這案例中的編輯至少會分兩種:

  1. 使用者自己的操作
  2. 管理者後台的操作

針對使用者自己操作的編輯又可以再細分為:

  1. 個人資料修改
  2. 密碼修改

而管理者操作的編輯也至少會有:

  1. 會員資料修改
  2. 會員密碼重置

如果是用 MVC 來開發,依據前一篇的建議想想看會有多少個 Edit Model ?

這篇我們還是會使用 MVC 示範後再使用 Razor Pages 示範,來看看為什麼我近期開專案都是 Razor Pages 優先吧。

編輯會員資料(使用者)

在會員資料欄位中可以讓使用者調整的只有兩個欄位 [NickName] 和 [Email] 就用這兩個欄位建立 Edit Model 

public class AccountEditModel
{
    public Guid Id { get; set; }
    
    [Display(Name = "暱稱")] 
    [Required]
    [StringLength(50)]
    public string NickName { get; set; }
    
    [Display(Name = "EMAIL")] 
    [Required]
    [StringLength(300)]
    public string Email { get; set; }
}
雖然 Id 欄位是用不到的,但我們需要靠此欄位知道要編輯哪一筆資料,所以記得要加入。

MVC Controller 的實做範例部分 

[HttpPost]
public async Task<IActionResult> Edit(AccountEditModel model)
{
    var source = await _accountService.GetSingleDataAsync(model.Id);
    if (source==null)
    {
        return NotFound();
    }

    if (ModelState.IsValid==false)
    {
        return View();
    }

    source.Email    = model.Email;
    source.NickName = model.NickName;
    await _accountService.CommitAsync();
    return View();
}
為了減少跳轉模糊焦點,我們將範例程式直接放在 Controller 實務上你可以把它們抽出去。

Razor Pages 的實做範例

public class Edit : PageModel
{
    private readonly IAccountService _accountService;

    public Edit(IAccountService accountService)
    {
        _accountService = accountService;
    }
    //建立需要的 Model
    public class InputModel
    {
        [Display(Name = "暱稱")] 
        [Required]
        [StringLength(50)]
        public string NickName { get; set; }

        [Display(Name = "EMAIL")] 
        [Required]
        [StringLength(300)]
        public string Email { get; set; }
    }
    //指定前端可以使用 @Model.Input 取得資料
    [BindProperty]
    public InputModel Input { get; set; }
    
    [BindProperty(SupportsGet = true)]
    public Guid? Id { get; set; }
    
    public void OnGet()
    {
        
    }

    public async Task<IActionResult> OnPostAsync()
    {
        if (Id.HasValue == false)
        {
            return NotFound();
        }

        var source = await _accountService.GetSingleDataAsync(Id.Value);
        if (source==null)
        {
            return Page();
        }
        source.Email    = Input.Email;
        source.NickName = Input.NickName;
        await _accountService.CommitAsync();
        return RedirectToPage("./Index");
    }
}

只有兩個欄位可以編輯,所以頁面上也只需要兩個表單欄位,後端 Page Model 當然也就只有兩個,在 OnPost 方法內將兩個欄位更新至資料庫就可以完成安全的編輯功能,使用者沒有任何偷渡改其他欄位的可能。

然後再注意一下上方程式碼,我將 Id 單獨設定一個屬性,和 MVC 時把 Id 包在 Edit Model 的作法不同,因為當設定了SupportsGet=true就可以直接收到 Query 的參數,其他的方法要取得 Id 就可以直接拿更方便好用。

編輯會員資料(管理者)

管理者和使用者在編輯單一會員資料的差異就是可不可以編輯 [Name] 與 [IsEnable] 欄位,所以與上述的寫法差異只有欄位的不同,就不浪費篇幅介紹了,現在來到了一個新議題,修改密碼!

修改密碼(使用者)

使用者在已經登入的狀態下要修改自己的密碼,為了安全,常見的設計會有三個欄位:

  1. 現在的密碼
  2. 新密碼
  3. 新密碼確認(就是再打一次新密碼)

依據這樣的設計產生出對應的 AccountPasswordEditModel

public class AccountPasswordEditModel
{
    public Guid Id { get; set; }
    
    [Display(Name = "密碼")] 
    [Required]
    [StringLength(50)]
    public string Password { get; set; }
    
    [Display(Name = "新密碼")] 
    [Required]
    [StringLength(50)]
    public string NewPassword { get; set; }
    
    [Display(Name = "密碼確認")] 
    [Required]
    [Compare(nameof(NewPassword))]
    public string ConfirmPassword { get; set; }
}

MVC 的實做範例

其實沒啥可看的,都是假 Code😏

public async Task<IActionResult> ChangePassword(AccountPasswordEditModel model)
{
    var source = await _accountService.GetSingleDataAsync(model.Id);
    if (source==null)
    {
        return NotFound();
    }

    if (ModelState.IsValid==false)
    {
        return View();
    }

    if (model.Password!=source.Password)
    {
        //實務上你不可能可以這樣判斷密碼,這只是範例!這只是範例!這只是範例!
        return View();
    }

    //實務上你也不可能這樣存密碼,密碼記得雜湊處理!
    source.Password = model.Password;
    await _accountService.CommitAsync();
    return RedirectToAction(nameof(Index));
}

Razor Pages 的實做範例

public class ChangePassword : PageModel
{
    private readonly IAccountService _accountService;

    public ChangePassword(IAccountService accountService)
    {
        _accountService = accountService;
    }


    //建立需要的 Model
    public class InputModel
    {
        [Display(Name = "密碼")]
        [Required]
        [StringLength(50)]
        public string Password { get; set; }

        [Display(Name = "新密碼")]
        [Required]
        [StringLength(50)]
        public string NewPassword { get; set; }

        [Display(Name = "密碼確認")]
        [Required]
        [Compare(nameof(NewPassword))]
        public string ConfirmPassword { get; set; }
    }

    //指定前端可以使用 @Model.Input 取得資料
    [BindProperty] public InputModel Input { get; set; }

    [BindProperty(SupportsGet = true)] public Guid? Id { get; set; }

    public void OnGet()
    {
    }

    public async Task<IActionResult> OnPostAsync()
    {
        if (Id.HasValue == false)
        {
            return NotFound();
        }

        var source = await _accountService.GetSingleDataAsync(Id.Value);
        if (source == null)
        {
            return Page();
        }

        if (source.Password != Input.Password)
        {
            //實務上你不可能可以這樣判斷密碼,這只是範例!這只是範例!這只是範例!
            return Page();
        }

        //實務上你也不可能這樣存密碼,密碼記得雜湊處理!
        source.Password = Input.Password;
        await _accountService.CommitAsync();
        return RedirectToPage("./Index");
    }
}

各位看到這裡可能覺得好像 Razor Pages 的範例程式每次都比 MVC 的長啊😅沒錯!看起來是這樣,但如果你有仔細看 Code 應該可以發現在 MVC 的範例我貼的都是單一 Action 而 Razor Pages 我貼的都是完整的整個檔案,你應該還沒忘記我們是在推廣 Razor Pages 吧😉

小結

目前我們的功能已經完成了(重置密碼涉及太多技術與觀念,會扯太遠所以我們就跳過吧....),回頭來看看專案的長相

左邊是 MVC,右邊是 Razor Pages 

看圖說故事時間,MVC 開發的專案如果你真的每一個功能都實做一個 Model(ViewModel, EditModel, CreateModel) 你的專案就會長這樣,看看左邊的 Models 資料夾,這只是一個功能的 CRUD  喔,自己想看看專案完成後這裡會長的多茁壯😁。

Razor Pages 因為原生設計就包含 Page Model 的機制,因此右邊 Razor Pages 專案的 Models 資料夾只有一個我們為了讓 Service 傳遞資料回 Page 的 ViewModel,其餘表單輸入需要而產生的 Edit Model, Create Model 都已經變成  Page Model 包含在個別檔案中。

現在再來看看功能的呈現, MVC 的設計一個 Controller 內包含了此功能所有的動作方法(Action),所以使用 MVC 開發時只有一個 AccountController 檔案,而 Razor Pages 是每一個功能都是獨立的 Page 因此產生了四個,誰好誰壞說不出一個準,但你想一下假設今天要調整「修改密碼」的功能,在 MVC 中開發者點開了 AccountController 會看到所有功能的程式碼,而 Razor Pages 的開發者只需要明確的點開 /Pages/Account/ChangePassword.cshtml 而且看到的也只有「修改密碼」功能的相關程式,你覺得哪個比較好?哪個比較不容易改錯而影響其他功能?

我相信很多人是直接跳這裡看結論的,但我真的希望你結論看完以後回頭看看其他兩篇的文字與範例程式碼,你應該更可以感受到 Razor Pages 的優良之處,我現在一個專案開始的時候我會優先選擇使用  Razor Pages , 除非我在初期就明確的知道這個專案在 MVC 會比較好寫,這兩者也不應該是堅持一種,應該看專案的不同與專案成員的能力來選擇,只會一種你就沒得選,會兩種你的機會就大,我們有在開設 Razor Pages 的課程,歡迎你來加大自己的版圖,讓你也有[選]的能力。

回應討論