Razor Page入門 - 5

by vivid 5. 八月 2020 02:38

.NET Magazine國際中文電子雜誌
作 者:許薰尹
審 稿:張智凱
文章編號:N200822101
出刊日期: 2020/8/5

這篇文章將延續《Razor Page入門 - 4》一文的情境,介紹當在ASP.NET Core Razor Page網站應用程式中如何使用「System.ComponentModel.DataAnnotations」命名空間下的類別來進行語意標註,並可在資料編輯作業時利用模型驗證(Model Validation)檢查資料的正確性。

「System.ComponentModel.DataAnnotations」命名空間下常用的Attribute類別包含以下:

  • 「Display」 Attribute:設定顯示名稱,並非用來進行資料驗證。
  • 「Required」 Attribute:要求驗證資料必須輸入。
  • 「MaxLength」 Attribute:來設定資料最大長度。
  • 「Range」Attribute:設定資料範圍,可指定資料最小、最大值。

安裝「Microsoft.AspNetCore.Mvc.DataAnnotations」套件

本文範例的模型類別是放在一個名為「MyModels」的.NET Standard類別庫(.NET Standard Class Library),你需要先安裝「Microsoft.AspNetCore.Mvc.DataAnnotations」套件才能夠在專案中使用「System.ComponentModel.DataAnnotations」命名空間下的Attribute類別。

從「Solution Explorer」視窗 –「MyModels」專案名稱上方,按滑鼠右鍵,從快捷選單選擇「Manage NuGet Packages」項目,請參考下圖所示:

clip_image002

圖 1:「Manage NuGet Packages」項目。

從對話盒上方文字方塊中,輸入查詢關鍵字「DataAnnotations」,找到「Microsoft.AspNetCore.Mvc.DataAnnotations」套件後,點選「Install」按鈕進行安裝,請參考下圖所示:

clip_image004

圖 2:安裝「Microsoft.AspNetCore.Mvc.DataAnnotations」套件。

下一步會看到「Preview Changes」視窗(預覽變更視窗),按一下「OK」按鈕,請參考下圖所示:

clip_image006

圖 3:「Preview Changes」視窗。

按一下「License Acceptance」視窗中的「I Accept」按鈕,請參考下圖所示:

clip_image008

圖 4:「License Acceptance」視窗。

接著就會進行安裝的動作,完成後專案相依的套件與版本會紀錄在「MyModels.csproj」專案檔之中,參考以下程式碼:

MyModels.csproj

 

<Project Sdk = "Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework> netstandard2.0 </TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include = "Microsoft.AspNetCore.Mvc.DataAnnotations" Version = "2.2.0" />
  </ItemGroup>

</Project>



 

「Microsoft.AspNetCore.Mvc.DataAnnotations」套件安裝完成之後,我們就可以在模型類別之中使用到語意標註相關類別。修改「MyModels\Book.cs」檔案中的「Book」類別,加入以下程式碼:

MyModels\Book.cs

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Text;

namespace MyModels {
  public class Book {
    [Display( Name = "圖書編號" )]
    public int Id { get; set; }
    [Display( Name = "圖書名稱" )]
    [Required( ErrorMessage = "圖書名稱不可為空白" )]
    [MaxLength( 50 , ErrorMessage = "長度不可超過 {0}" )]
    public string Title { get; set; }
    [Display( Name = "價格" )]
    [Range( 1 , int.MaxValue , ErrorMessage = "{0} 有效範圍在 {1} 與 {2} 之間" )]
    public int Price { get; set; }
    [Display( Name = "出版日期" )]
    public DateTime PublishDate { get; set; }
    [Display( Name = "庫存" )]
    public bool InStock { get; set; }
    [Display( Name = "說明" )]
    [MaxLength( 50 , ErrorMessage = "長度不可超過 {0}" )]
    public string Description { get; set; }
    [Display( Name = "圖書分類" )]
    public Category? Category { get; set; }
  }

}


 

我們修改了「Book 」類別程式碼,使用Data Annotation加上以下語意標註,使用「DisplayName 」Attribute設定顯示名稱;「Title」屬性套用「Required」 Attribute要求資料必需輸入;「MaxLength」 Attribute設定資料最大長度;「Price」屬性套用「Range」 Attribute要求資料的有效範圍是正整數。若資料驗證有錯,再透過這些Attribute類別的「ErrorMessage 」屬性來自訂錯誤訊息。

選取Visual Studio開發工具「Build」-「Build Solution」項目編譯目前的專案,確認程式碼能正確編譯。

在Razor Page顯示錯誤驗證訊息

回到「MyRazorWeb」網站專案,修改「\Edit.cshtml」Razor Page程式碼,顯示資料驗證錯誤訊息。在<form>標籤內加入一個<div>標籤,套用「asp-validation-summary」標記協助程式(Tag Helper),以集中顯示多個<Input>項目驗證錯誤時的錯誤訊息。此外在每一行<Input>標籤程式下方加入一個<span>標籤,設定「asp-validation-for」標記協助程式(Tag Helper)進行驗證:

MyRazorWeb\Pages\Books\Edit.cshtml

@page
@model MyRazorWeb.Pages.Books.EditModel
@{
    ViewData["Title"] = "Edit";
}

<h1> Book Edit </h1>
<hr />
<div class = "row">
    <div class = "col-md-8">
        <form method = "post">
            <div asp-validation-summary = "All" class = "text-danger"> </div>
            <input type = "hidden" asp-for = "Book.Id" />
            <div class = "form-group">
                <label asp-for = "Book.Title" class = "control-label"> </label>
                <input asp-for = "Book.Title" class = "form-control" />
                <span asp-validation-for = "Book.Title" class = "text-danger"> </span>
            </div>
            <div class = "form-group">
                <label asp-for = "Book.Price" class = "control-label"> </label>
                <input asp-for = "Book.Price" class = "form-control" />
                <span asp-validation-for = "Book.Price" class = "text-danger"> </span>
            </div>
            <div class = "form-group">
                <label asp-for = "Book.PublishDate" class = "control-label"> </label>
                <input asp-for = "Book.PublishDate" class = "form-control" />
                <span asp-validation-for = "Book.PublishDate" class = "text-danger"> </span>
            </div>
            <div class = "form-group form-check">
                <label class = "form-check-label">
                    <input class = "form-check-input" asp-for = "Book.InStock" /> @Html.DisplayNameFor( model => model.Book.InStock )
                </label>
            </div>
            <div class = "form-group">
                <label asp-for = "Book.Description" class = "control-label"> </label>
                <input asp-for = "Book.Description" class = "form-control" />
                <span asp-validation-for = "Book.Description" class = "text-danger"> </span>
            </div>
            <div class = "form-group">
                <label asp-for = "Book.Category" class = "control-label"> </label>
                <select asp-for = "Book.Category" class = "form-control"
                        asp-items = "Html.GetEnumSelectList<MyModels.Category>()">
                    <option value = ""> Please Select </option>
                </select>
                <span asp-validation-for = "Book.Category" class = "text-danger"> </span>
            </div>
            <div class = "form-group">
                <input type = "submit" value = "Save" class = "btn btn-primary" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-page = "./List"> Back to List </a>
</div>

 

參考以下程式碼,接下來修改「\Edit.cshtml.cs」檔案程式碼,特別注意,在「Book」屬性套用了「BindProperty」Attribute,模型繫結程式將搜集到的資料填入Book物件的屬性值時,會根據模型類別屬性套用的語意標註Attribute(Data Annotations Attribute)驗證資料的有效性,只要資料有問題,就會將「ModelState.IsValid」屬性的值設定為「false」,若所有屬性都通過資料驗證,就會將「ModelState.IsValid」屬性的值設定為「true」:

MyRazorWeb\Pages\Books\Edit.cshtml.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using MyModels;
using MyServices;

namespace MyRazorWeb.Pages.Books {
  public class EditModel : PageModel {
    private readonly IBookRepository bookRepository;
    public EditModel( IBookRepository bookRepository ) {
      this.bookRepository = bookRepository;
    }
    [BindProperty]
    public Book Book { get; set; }
    public IActionResult OnGet( int id ) {
      Book = bookRepository.GetBook( id );

      if ( Book == null ) {
        return RedirectToPage( "/NotFound" );
      }

      return Page();
    }
    public IActionResult OnPost() {
      if ( !ModelState.IsValid ) {
        return Page();
      }
      Book = bookRepository.Update( Book );
      return RedirectToPage( "List" );
    }
  }
}

 

因此我們在「OnPost」方法之中只要判斷驗證的結果是成功還是失敗再進行因應的處理就好,以本例而言,若驗證不成功,便直接叫用「Page()」方法,回傳「Edit」Razor Page,讓使用者修訂錯誤,以便再重新提交表單;若驗證沒有問題,便更新伺服端的資料,然後透過「RedirectToPage」方法導向「List」Razor Page。

選取Visual Studio開發工具「Build」-「Build Solution」項目編譯目前的專案,確認程式碼能正確編譯。在Visual Studio開發工具,按CTRL+F5執行網站「Books\List」Razor Page,執行結果參考如下,點選某筆圖書後方的「Edit」超連結時,「Book」物件的「id」屬性值就以查詢字串的型式,傳遞到「Edit」Razor Page:

clip_image010

圖 5:錯誤驗證測試。

下一步便會進入編輯畫面,任意修改文字方塊中的值,再按下「Save」按鈕,請參考下圖所示,我們故意不填「Title(圖書名稱)」;並將「Price(價格)」設為負數值,錯誤訊息會自動出現在「asp-validation-summary」與「asp-validation-for」標記協助程式(Tag Helper)所在的位置:

clip_image012

圖 6:顯示錯誤訊息。

用戶端驗證

預設ASP.NET Core Razor Pages類型的專案已經將用戶端驗證所需的「jQuery」、「jquery-validation」與「jquery-validation-unobtrusive」三個JavaScript程式庫放在「wwwroot\lib」資料夾之中,請參考下圖所示:

clip_image014

圖 7:JavaScript程式庫。

要讓ASP.NET Core Razor Pages用戶端驗證能夠生效,只要在需要驗證的Razor Page引用這三個JavaScript程式庫即可。但每個需要使用到驗證的Razor Page都要重複做引用的動作在設計上稍嫌煩雜,也不易後續管理與維護,能夠集中處理的話是最好的選擇。

檢視一下目前專案中的「_Layout」檔案程式碼已經使用<script>標籤引用「jquery.min.js」程式庫,參考以下程式碼:

MyRazorWeb\Pages\Shared\_Layout.cshtml

<!DOCTYPE html>
<html lang = "en">
<head>
    <meta charset = "utf-8" />
    <meta name = "viewport" content = "width=device-width, initial-scale=1.0" />
    <title> @ViewData["Title"] – MyRazorWeb </title>
    <link rel = "stylesheet" href = "~/lib/bootstrap/dist/css/bootstrap.min.css" />
    <link rel = "stylesheet" href = "~/css/site.css" />
</head>
<body>
    <header>
        <nav class = "navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
            <div class = "container">
                <a class = "navbar-brand" asp-area = "" asp-page = "/Index"> MyRazorWeb </a>
                <button class = "navbar-toggler" type = "button" data-toggle = "collapse" data-target = ".navbar-collapse" aria-controls = "navbarSupportedContent"
                        aria-expanded = "false" aria-label = "Toggle navigation">
                    <span class = "navbar-toggler-icon"> </span>
                </button>
                <div class = "navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse">
                    <ul class = "navbar-nav flex-grow-1">
                        <li class = "nav-item">
                            <a class = "nav-link text-dark" asp-area = "" asp-page = "/Index"> Home </a>
                        </li>
                        <li class = "nav-item">
                            <a class = "nav-link text-dark" asp-area = "" asp-page = "/Books/List"> Books </a>
                        </li>
                        <li class = "nav-item">
                            <a class = "nav-link text-dark" asp-area = "" asp-page = "/Privacy"> Privacy </a>
                        </li>
                    </ul>
                </div>
            </div>
        </nav>
    </header>
    <div class = "container">
        <main role = "main" class = "pb-3">
            @RenderBody()
        </main>
    </div>

    <footer class = "border-top footer text-muted">
        <div class = "container">
            &copy; 2020 - MyRazorWeb - <a asp-area = "" asp-page = "/Privacy"> Privacy </a>
        </div>
    </footer>

    <script src = "~/lib/jquery/dist/jquery.min.js"> </script>
    <script src = "~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"> </script>
    <script src = "~/js/site.js" asp-append-version = "true"> </script>

    @RenderSection( "Scripts", required: false )
</body>
</html>

 

在專案中多個Razor Page可能都會使用到「jquery」程式庫,而考慮到只有需要驗證資料的「Edit」Razor Page以及未來要設計用來新增資料的「Create」Razor Page才會用到「jquery-validation」與「jquery-validation-unobtrusive」這兩個JavaScript程式庫,因此我們不在「_Layout」檔案中引用「jquery-validation」與「jquery-validation-unobtrusive」。

預設ASP.NET Core Razor Pages類型的專案在「MyRazorWeb\Pages\Shared\」資料夾之中,存放一個「_ValidationScriptsPartial.cshtml」檔案,包含了引用「jquery-validation」與「jquery-validation-unobtrusive」這兩個JavaScript程式庫的程式碼如下:

_ValidationScriptsPartial.cshtml

<script src = "~/lib/jquery-validation/dist/jquery.validate.min.js"> </script>
<script src = "~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"> </script>

 

參考以下程式碼,修改「Edit.cshtml」程式碼,利用「section」關鍵字定義一個「Scripts」區段,在其中叫用「Html.RenderPartialAsync」方法將「_ValidationScriptsPartial.cshtml」檔案中的程式碼插入「_Layout」檔案中「 @RenderSection("Scripts", required: false)」這行程式碼出現的位置:

MyRazorWeb\Pages\Books\Edit.cshtml

@page
@model MyRazorWeb.Pages.Books.EditModel
@{
    ViewData["Title"] = "Edit";
}

<h1> Book Edit </h1>
<hr />
<div class = "row">
    <div class = "col-md-8">
        <form method = "post">
            <div asp-validation-summary = "All" class = "text-danger"> </div>
            <input type = "hidden" asp-for = "Book.Id" />
            <div class = "form-group">
                <label asp-for = "Book.Title" class = "control-label"> </label>
                <input asp-for = "Book.Title" class = "form-control" />
                <span asp-validation-for = "Book.Title" class = "text-danger"> </span>
            </div>
            <div class = "form-group">
                <label asp-for = "Book.Price" class = "control-label"> </label>
                <input asp-for = "Book.Price" class = "form-control" />
                <span asp-validation-for = "Book.Price" class = "text-danger"> </span>
            </div>
            <div class = "form-group">
                <label asp-for = "Book.PublishDate" class = "control-label"> </label>
                <input asp-for = "Book.PublishDate" class = "form-control" />
                <span asp-validation-for = "Book.PublishDate" class = "text-danger"> </span>
            </div>
            <div class = "form-group form-check">
                <label class = "form-check-label">
                    <input class = "form-check-input" asp-for = "Book.InStock" /> @Html.DisplayNameFor( model => model.Book.InStock )
                </label>
            </div>
            <div class = "form-group">
                <label asp-for = "Book.Description" class = "control-label"> </label>
                <input asp-for = "Book.Description" class = "form-control" />
                <span asp-validation-for = "Book.Description" class = "text-danger"> </span>
            </div>
            <div class = "form-group">
                <label asp-for = "Book.Category" class = "control-label"> </label>
                <select asp-for = "Book.Category" class = "form-control"
                        asp-items = "Html.GetEnumSelectList<MyModels.Category>()">
                    <option value = ""> Please Select </option>
                </select>
                <span asp-validation-for = "Book.Category" class = "text-danger"> </span>
            </div>
            <div class = "form-group">
                <input type = "submit" value = "Save" class = "btn btn-primary" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-page = "./List"> Back to List </a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync( "_ValidationScriptsPartial" );}
}

 

除了叫用「Html.RenderPartialAsync」方法插入「_ValidationScriptsPartial」檔案的程式碼之外,你也可以改用partial標記協助程式(Tag Helper)來插入「_ValidationScriptsPartial」,參可以下範例程式碼:

@section Scripts {

   <partial name="_ValidationScriptsPartial" />

}

選取Visual Studio開發工具「Build」-「Build Solution」項目編譯目前的專案,確認程式碼能正確編譯。

在Visual Studio開發工具,按CTRL+F5執行網站「Books\List」Razor Page,執行結果參考如下,現在「Edit」Razor Page便可以使用用戶端驗證了,若使用Chrome瀏覽器除錯工具來測試資料編修作業,當你想要按「Save」按鈕將資料儲存時,用戶端驗證就會生效,從Chrome瀏覽器除錯工具「Network」功能攔不到任何用戶端與伺服端之間有互動,這就代表驗證的動作是發生在用戶端瀏覽器的電腦中,請參考下圖所示:

clip_image016

圖 8:用戶端驗證。

那麼用戶端驗證是如何運作的呢? 使用從Chrome瀏覽器除錯工具「Elements」功能來檢視目前的「Edit」Razor Page,你可以看到許多「data-」開頭的HTML Attribute,加上許多應用在資料驗證的資訊。其中「data-val="true"」啟用用戶端驗證;「data-val-maxlength="長度不可超過 圖書名稱"」設定資料長度超過時要顯示的驗證錯誤訊息;依此類推「data-val-required="圖書名稱不可為空白"」則是資料未輸入時要顯示的錯誤訊息。這些資訊將提供給「jquery-validation-unobtrusive」程式庫來進行用戶端驗證動作。

clip_image018

圖 9:用戶端驗證。

有了用戶端驗證,那麼還需要伺服端驗證嗎? 答案是:「要」。用戶端驗證發生在瀏覽器的電腦上,為了安全性了理由,驗證過程中無法存取伺服端資源,例如想要在新增圖書資料時,檢查這筆資料是否已存在於伺服端的資料庫中,那麼驗證的程式碼就必需撰寫在伺服端。

此外若用戶端瀏覽器關閉用戶端可執行JavaScript的功能,那麼伺服端驗證就是排除有問題資料的唯一選擇了。

Tags:

.NET Magazine國際中文電子雜誌 | ASP.NET Razor Pages | C# | Visual Studio | 許薰尹Vivid Hsu

新增評論




  Country flag
biuquote
  • 評論
  • 線上預覽
Loading






NET Magazine國際中文電子雜誌

NET Magazine國際中文電子版雜誌,由恆逸資訊創立於2000,自發刊日起迄今已發行超過500篇.NET相關技術文章,擁有超過40000名註冊讀者群。NET Magazine國際中文電子版雜誌希望藉於電子雜誌與NET Developer達到共同學習與技術新知分享,歡迎每一位對.NET 技術有興趣的朋友們多多支持本雜誌,讓作者群們可以有持續性的動力繼續爬文。<請加入免費訂閱>

月分類Month List