demoshop

demo, trying to be the best_

以前 MVC 框架有人反應拆太細了,大多數專案其實不需要那麼龐大的架構,現在有了更聚焦更輕量化的 Razor Pages,或許對於我們是一個不錯的解法。

前情提要

延續上一篇的架構,繼續開發新增與編輯功能,在實務中新增與編輯所需的欄位往往不同,不會和範例一樣單純使用一個 View Model 就解決,為了這些不同有許多種作法,都能達到需求但也都有一些議題,以這次的例子會員註冊來說要填寫的欄位與編輯就不會是相同的,首先使用 MVC 的寫法來看看。

MVC 使用參數傳遞

註冊(新增)只需要填帳號[Name]、密碼[Password]、暱稱[NickName]、EMAIL 但如果直接使用 DB Model 會因為驗證而出錯,為了避開這問題,最直覺的寫法就是這樣

[HttpPost]
public async Task<IActionResult> Create(string name, string nickName, string password, string email)
{
    if (ModelState.IsValid==false)
    {
        return View();
    }
    var newData = new Account()
                  {
                      Id       = Guid.NewGuid()
                    , Name     = name
                    , NickName = nickName
                    , Password = password
                    , Email    = email
                    , IsEnable = true
                  };
    await _accountService.AddAsync(newData);
    await _accountService.CommitAsync();
    return RedirectToAction(nameof(Index));
}
範例程式把會省略部分資料驗證與例外處理,範例可以省略,實務上你不可以省

MVC 使用 DB Model

上面的程式當然是可以正常執行的,但當一切都使用參數來傳,資料驗證的部分就需要手工處理,都已經使用了那麼強的框架還要自己來是很不明智的,為了讓資料驗證可以輕鬆自在點就會有開發者是這樣寫的。

[HttpPost]
public async Task<IActionResult> Create(Account model)
{
    ModelState.Remove("Id");
    ModelState.Remove("IsEnable");
    if (ModelState.IsValid==false)
    {
        return View();
    }
    model.Id       = Guid.NewGuid();
    model.IsEnable = true;
 await _accountService.AddAsync(model);
 await _accountService.CommitAsync();
 return RedirectToAction(nameof(Index));
}

不使用參數一個一個傳,而改成直接使用 DB Model 所以我們當初因為 Code First 所設定的驗證屬性自然就會生效,開發者是自己當然就很清楚知道 Account 中的 Id 和 IsEnable 兩個欄位沒有輸入的話會引發例外,因此就明確的使用 ModelState.Remove("Id");來排除不想被驗證的欄位,排除後ModelState.IsValid就不會因為這兩個欄位而驗證失敗了!但 [Id] [IsEnable] 兩個欄位不會讓使用者填,雖然沒驗證錯誤了但它們依然是空的,記得要自己把正確的值補上去。   

MVC 使用 Create Model

這樣子看起來程式碼已經比第一版好,而且還擁有了資料驗證的功能,但其實這樣的寫法就是在系統中埋下一顆地雷,只要那一天對應的 DB Model 異動,多出了新的不可為空的欄位,我們的註冊功能就馬上死去,為了隔開它們的相依 Create Model 因應而生。    

public class AccountCreateModel
{
    [Display(Name = "帳號")] 
    [Required]
    [StringLength(50)]
    public string Name { get; set; }
    
    [Display(Name = "密碼")] 
    [Required]
    [StringLength(1000)]
    public string Password { get; set; }
    
    [Display(Name = "暱稱")] 
    [Required]
    [StringLength(50)]
    public string NickName { get; set; }
    
    [Display(Name = "EMAIL")] 
    [Required]
    [StringLength(300)]
    public string Email { get; set; }
}

既然已經是客制化的 Create Model 了,當然就可以在這直接加上驗證所需的屬性,改用 Create Model 後程式碼就可以縮減成這樣

[HttpPost]
public async Task<IActionResult> Create(AccountCreateModel model)
{
    if (ModelState.IsValid==false)
    {
        return View();
    }

    var id=await _accountService.AddAsync(model);
    await _accountService.CommitAsync();
    return RedirectToAction(nameof(Index));
}

改用 Create Model 後 Controller 的程式碼就會相對乾淨,介面接收的參數也由原本的 DB Model 改為 Create Model 隔離的狀況也比較好了,但回過頭來看,這樣的設計下我們開始會有許多因不同欄位而產生的 Create Model 最後很容易導致命名困難,每個名稱都開始模擬兩可,更慘的是每個檔案打開都長的大同小異,不仔細看還很容易改錯檔案… 那如果改用 Razor Pages 開發會有什麼不同呢?

Razor Pages 登場

首先還是要新增介面 

Task AddAsync(Account newData);

你可以看到在 Razor Pages 內 AddAsync 一樣是使用 DB Model

public class Create : PageModel
{
    private readonly IAccountService _accountService;

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

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

        [Display(Name = "密碼")] 
        [Required]
        [StringLength(1000)]
        public string Password { get; set; }

        [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; }
    
    public void OnGet()
    {
       
    }

    public async Task<IActionResult> OnPostAsync()
    {
        //Post 會跑來這,就是看方法名稱直接對應
        
        if (ModelState.IsValid==false)
        {
            return Page();
        }
        
        var newData = new Data.Account()
                      {
                          Id       = Guid.NewGuid()
                        , Name     = Input.Name
                        , NickName = Input.NickName
                        , Password = Input.Password
                        , Email    = Input.Email
                        , IsEnable = true
                      };
        
        await _accountService.AddAsync(newData);
        await _accountService.CommitAsync();
        return RedirectToPage("./Index");
    }
}

不要直接滑過,仔細看一下上方的 Code ,Razor Pages 因為有 Page Model 因此新增需要的欄位就直接在這頁建立,包含驗證與欄位資訊都在同一份檔案,再來看屬性的部分,InputModel 是這頁專屬,它的名稱 Input 也是這頁的這代表什麼?這表示我們不用再想命名了...可以所有的頁面都是這樣用,外部的 Model 資料夾也不再有一堆 Class 在那裡。

以前 MVC 框架有人反應拆太細了,大多數專案其實不需要那麼龐大的架構,現在有了更聚焦更輕量化的 Razor Pages,或許對於我們是一個不錯的解法,原本新增的部分沒有打算示範那麼多 MVC 的寫法,但一不小心就寫了一堆,所以編輯的部分就留到下一次吧。

 

如果你對於 Razo Pages 有點興趣的話歡迎參考我們的 Razor Pages 課程 https://skilltr.ee/razorpage 

回應討論