demoshop

demo, trying to be the best_

情境解說

在資料庫中的資料表欄位會依據資料面來設計,但往往會與「顯示」「新增」「編輯」時所需的欄位不同,可能有多也可能有少,得過且過的開發人員會一股腦的都用 DB Model 來傳遞資料,但這是一種浪費也可能造成資安問題,依據我們開發 MVC 的習慣會建立稱為 View Model 的傳遞物件,本篇文章就來示範此情境在 ASP.NET MVC 與 Razor Pages 我們會怎麼做。

為了讓範例可以達到不用解釋的目的,我們將以會員註冊這大家都寫過的東西來做範例。

資料庫表單建立

public class Account
{
    [Key]
    public Guid Id { get; set; }
    [Required]
    [StringLength(50)]
    public string Name { get; set; }
    [Required]
    [StringLength(1000)]
    public string Password { get; set; }
    [Required]
    [StringLength(50)]
    public string NickName { get; set; }
    [Required]
    [StringLength(300)]
    public string Email { get; set; }
    public bool IsEnable { get; set; }
}
在 .NET Core 開發人員可以使用Code First 的方式由程式建立出資料表,而這個建立的類別又剛好可以拿來當成 DB Model 建議各位開發人員真的要試試看。

View Model 建立

首先要開發的是帳號列表功能,在此案例中顯示給管理者看的帳號列表並不會將使用者的帳號[Name]與密碼[Password]顯示出來,為了節省資料傳遞通常會新增一個資料傳遞用的類別,並且只設定需要的屬性,而這個類別在 MVC 中稱做 View Model 。

public class AccountListViewModel
{
    public Guid Id { get; set; }
    [Display(Name = "暱稱")] 
    public string NickName { get; set; }
    [Display(Name = "EMAIL")] 
    public string Email { get; set; }
    [Display(Name = "啟用")] 
    public bool IsEnable { get; set; }
}
View Model 依據習慣取代配置原則都會加上 ViewModel 後綴

列表實做

既然我們都選擇了這個內建 DI 的 .NET Core 了,不免俗的先建立介面吧    

public interface IAccountService
{
    Task<AccountListViewModel> LookupAllDataAsync();
}

接下來就先使用各位都普遍熟習的 MVC 撰寫相關程式碼

public class AccountController : Controller
{
    private readonly IAccountService _accountService;

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

    // GET
    public async Task<IActionResult> Index()
    {
        var source = await _accountService.LookupAllDataAsync();
        return View(source);
    }
}

MVC 與 RazorPages

前端的部分就不貼 Code ,我們單純點講後端就好,依據上面這些程式我們可以看到在 ASP.NET MVC 中我們的列表功能是由 DB Model, View Model, Service, Controller, View 結合成而這是我們開發 MVC 一直以來的習慣,至少分層三層,再視專案的需求增加 Service 等層,這樣的習慣在改成屬於 MVVM 的 Razor Pages 後會有什麼變化呢?

在 Razor Pages 的設計中每一個頁面都會有自己專屬的 Model ,稱為 Page Model 而此案例有實做 Service 層所以可以利用 View Model 來作為 Service 與 Razor Page 的傳遞物件,你可以發現當改用 Razor Pages 開發後 Service 層以上的東西還是可以保有 MVC 留下來的習慣,只有應用上的寫法不同,以下就是 Razor Pages 的寫法。

public class Index : PageModel
{
    private readonly IAccountService _accountService;

    public Index(IAccountService accountService)
    {
        _accountService = accountService;
    }
    
    public AccountListViewModel AccountList { get; set; }
    
    public async Task OnGetAsync()
    {
        AccountList = await _accountService.LookupAllDataAsync();
    }
}        
MVC 的 Controller 繼承 Controller ,Razor Pages 的 Page 繼承 PageModel   

在這裡你可以看到public AccountListViewModel AccountList { get; set; } 這行指令就是在 Index 這頁的 Page Model 建立了一個名稱為 AccountList 的屬性,型別是  AccountListViewModel,在 Index 類別中擁有 OnGetAsync() 方法,我們在此方法中賦予 AccountList 屬性值,結束時您甚至不用 Return 就可以在前端頁面使用 @Model.AccountList 存取相關內容,這段 Code 有沒有好熟習?是不是和我們日常在設計 Class 一樣的自然、直覺。

如果還感受不到差異,不如再加上兩個常見的功能,分頁搜尋吧。

實做分頁與搜尋後的差異

先來看看 MVC 版本的

public class AccountController : Controller
{
    private readonly IAccountService _accountService;

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

    // GET
    public async Task<IActionResult> Index(int? p, string searchNickName, string searchEmail)
    {
        var source = await _accountService.LookupAllDataAsync(searchNickName, searchEmail, p, 20);
        return View(source);
    }
}

再來看看 Razor Pages 的版本

public class Index : PageModel
{
    private readonly IAccountService _accountService;

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

    public IPagedList<AccountListViewModel> AccountList { get; set; }
    
    [BindProperty(SupportsGet = true)]
    public int P { get; set; }
    
    [BindProperty(SupportsGet = true)]
    public string SearchNickName { get; set; }
    
    [BindProperty(SupportsGet = true)]
    public string SearchEmail { get; set; }
    
    public async Task OnGetAsync()
    {
        AccountList = await _accountService.LookupAllDataAsync(SearchNickName,SearchEmail,P, 20);
    }
}

在 MVC 中方法(Action)內所需要的參數要使用傳入的方式,而 Razor Pages 則是使用屬性的資料綁定,而且也同時支援參數的傳入方式,不過在我們開發 Razor Pages 的時候通常都會選屬性,單一頁面、單一功能需要的資訊都在同一個檔案,就和我們在設計類別是一樣的習慣一切都被封裝在一個 Class(Page)內,不會影響別人也不會被別人影響,到這裡你應該可以稍微有點感覺 Razor Pages 就和我們以前寫的 Web Forms 很像,一個前端頁面 *.cshtml 搭配一個後端程式 *.cshtml.cs ,但只有這樣絕對是不會讓筆者把主力框架從 MVC 改成 Razor Pages 的,下一章我們將會看到另一個實務上經常面對的議題,新增與編輯所需欄位不同在 Razor Pages 的表現。

回應討論