Razor Page入門 - 4

by vivid 22. 七月 2020 04:59

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

這篇文章將延續《Razor Page入門 - 3》一文的情境,介紹當ASP.NET Core Razor Page網站應用程式執行時,若找不到伺服端資源,要如何做錯誤處理,並且了解如何設計圖書資料編輯網頁。

回顧《Razor Page入門 - 3》撰寫的「Details.cshtml.cs」程式碼如下,「OnGet」方法用來查詢id相符的圖書資料,但程式中並沒有進行錯誤處理:

MyRazorWeb\Pages\Books\Details.cshtml.cs

using System;
using System.Collections.Generic;
using System.Drawing;
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 DetailsModel : PageModel {
    private readonly IBookRepository bookRepository;
    public DetailsModel( IBookRepository bookRepository ) {
      this.bookRepository = bookRepository;
    }
    public Book Book { get; private  set; }
    public void OnGet(int id) {
      Book = bookRepository.GetBook( id );
    }
  }
}

若我們在執行網頁時,故意在URL輸入「id」查詢字串參數值為「100」:

https://localhost:44300/books/details/?id=100

因為伺服端並沒有「id」值相符的圖書資料,得到的執行結果如下圖:

clip_image002

圖 1:找不到相符的圖書資料。

較好的做法是顯示錯誤資訊讓使用者能夠知道錯誤在哪裏,接下來讓我們來談談如何處理找不到伺服端圖書資料的例外錯誤處理方式。

例外錯誤處理

首先我們希望錯誤發生時,可以導向一個錯誤頁面顯示錯誤訊息,這個頁面將命名為「NotFound」。在「Solution Explorer」視窗,「MyRazorPage」專案 - 「Pages」資料夾項目上方,按滑鼠右鍵,從快捷選單選擇「Add」- 「Razor Page」項目,請參考下圖所示:

clip_image004

圖 2:加入「Razor Page」項目。

在「Add New Scaffolded Item」對話盒左方選取「Razor Pages」分類,然後選取「Razor Page」項目,按下「Add 」按鈕,請參考下圖所示:

clip_image006

圖 3:選取「Razor Page」項目。

在「Add Razor Page」對話盒中,設定Razor Page名稱為「NotFound」,勾選下方「Generate PageModel class」以及「Use a layout page」項目;然後按下「Add 」按鈕,請參考下圖所示:

clip_image008

圖 4:「Add Razor Page」對話盒。

在Visual Studio工具產生的「NotFound」Razor Page類別檔案加入以下程式碼,顯示自訂錯誤訊息:

NotFound.cshtml

@page
@model MyRazorWeb.Pages.NotFoundModel
@{
    ViewData["Title"] = "NotFound";
}

<h1 class="text-danger"> NotFound </h1>
<h2 class="text-danger"> An error occurred while processing your request. </h2>


修改「Details.cshtml.cs」Razor Page程式碼,加上錯誤處理功能,當「bookRepository.GetBook」方法沒有取回「id」相符的「Book」物件時,則叫用「RedirectToPage」方法,導向「NotFound」Razor Page顯示錯誤訊息:

MyRazorWeb\Pages\Books\Details.cshtml.cs

using System;
using System.Collections.Generic;
using System.Drawing;
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 DetailsModel : PageModel {
    private readonly IBookRepository bookRepository;
    public DetailsModel( IBookRepository bookRepository ) {
      this.bookRepository = bookRepository;
    }
    public Book Book { get; private set; }
    public IActionResult OnGet( int id ) {
      Book = bookRepository.GetBook( id );
      if ( Book == null ) {
        return RedirectToPage( "/NotFound" );
      }
      return Page();
    }
  }
}

 

同時我們修改了「OnGet」方法的回傳型別為「IActionResult 」介面,這是因為「RedirectToPage」方法回傳「RedirectToPageResult」型別,而「RedirectToPageResult」型別實作了「IActionResult 」介面。若找到「id」相符的「Book」物件時,則利用「Page()」方法重新渲染Razor Page,「Page()」方法回傳「PageResult 」物件,而「PageResult 」繼承自「ActionResult」類別,「ActionResult」類別又實作了「IActionResult」。簡單的說就是「RedirectToPageResult」型別與「PageResult 」都實作了「IActionResult 」介面的關係。

接著測試「Details」Razor Page程式碼,故意在URL將「id」查詢字串參數值設定為「100」:

https://localhost:44300/books/details/?id=100

這次將會顯示自訂錯誤訊息「NotFound」的畫面,請參考下圖所示:

clip_image010

圖 5:顯示自訂錯誤訊息「NotFound」的畫面。

編輯圖書資料

在「MyRazorWeb」Razor Page網站應用程式專案中,我們想要為圖書系統加上功能,讓使用者可以在「List」Razor Page網頁,選取特定圖書,「List」Razor Page將利用查詢字串(Query String)傳遞圖書代號(id)到「Edit」Razor Page網頁,在網頁中將此筆圖書資料取回,並顯示在「Edit」Razor Page網頁之中,以便讓使用者進行修改,修改完成後可以選擇將異動的資料儲存。

在「MyRazorWeb」Razor Page網站專案加入「Edit」Razor Page。在「Solution Explorer」視窗,「MyRazorPage」專案 - 「Pages \ Books」資料夾項目上方,按滑鼠右鍵,從快捷選單選擇「Add」- 「Razor Page」項目,請參考下圖所示:

clip_image012

圖 6:加入「Razor Page」項目。

在「Add New Scaffolded Item」對話盒左方選取「Razor Pages」分類,然後選取「Razor Page」項目,按下「Add 」按鈕,請參考下圖所示:

clip_image013

圖 7:選取「Razor Page」項目。

在「Add Razor Page」對話盒中,設定Razor Page名稱為「Edit」,勾選下方「Generate PageModel class」以及「Use a layout page」項目;然後按下「Add 」按鈕,請參考下圖所示:

clip_image015

圖 8:「Add Razor Page」對話盒。

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;
    }
    public Book Book { get; private set; }
    public IActionResult OnGet( int id ) {
      Book = bookRepository.GetBook( id );

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

      return Page();
    }
  }
}


修改「Edit.cshtml」檔案中的程式碼,加入「Form」標簽,在標簽中利用標記協助程式(Tag Helper)來產生文字方塊、核取方塊與Select清單欄位:

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-4">
        <form method="post">
            <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" />
            </div>
            <div class="form-group">
                <label asp-for="Book.Price" class="control-label"></label>
                <input asp-for="Book.Price" class="form-control" />
            </div>
            <div class="form-group">
                <label asp-for="Book.PublishDate" class="control-label"></label>
                <input asp-for="Book.PublishDate" class="form-control" />
            </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" />
            </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>
            </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>

 

修改「Pages\Books\List.cshtml」檔案,在每一筆圖書資料後方加上一個<a>超連結以導向「Edit」Razor Page,利用查詢字串(Query String)傳遞圖書代號(id)到「Edit」Razor Page網頁,參考以下程式碼:

MyRazorWeb\Pages\Books\List.cshtml

@page
@model MyRazorWeb.ListModel
@{
    ViewData["Title"] = "List";
}

<h1>List</h1>

<table class="table">
    <thead>
        <tr>
            <th>
                @Html.DisplayNameFor( model => ( (IList<MyModels.Book>) model.Books )[0].Id )
            </th>
            <th>
                @Html.DisplayNameFor( model => ( (IList<MyModels.Book>) model.Books )[0].Title )
            </th>
            <th>
                @Html.DisplayNameFor( model => ( (IList<MyModels.Book>) model.Books )[0].Price )
            </th>
            <th>
                @Html.DisplayNameFor( model => ( (IList<MyModels.Book>) model.Books )[0].PublishDate )
            </th>
            <th>
                @Html.DisplayNameFor( model => ( (IList<MyModels.Book>) model.Books )[0].InStock )
            </th>
            <th>
                @Html.DisplayNameFor( model => ( (IList<MyModels.Book>) model.Books )[0].Description )
            </th>
            <th>
                @Html.DisplayNameFor( model => ( (IList<MyModels.Book>) model.Books )[0].Category )
            </th>
            <th></th>
        </tr>
    </thead>
    <tbody>
        @foreach ( var item in Model.Books ) {
        <tr>
            <td>
                @Html.DisplayFor( modelItem => item.Id )
            </td>
            <td>
                @Html.DisplayFor( modelItem => item.Title )
            </td>
            <td>
                @Html.DisplayFor( modelItem => item.Price )
            </td>
            <td>
                @Html.DisplayFor( modelItem => item.PublishDate )
            </td>
            <td>
                @Html.DisplayFor( modelItem => item.InStock )
            </td>
            <td>
                @Html.DisplayFor( modelItem => item.Description )
            </td>
            <td>
                @Html.DisplayFor( modelItem => item.Category )
            </td>
            <td>
                <a asp-page="./Details" asp-route-Id="@item.Id"> Details </a> |
                <a asp-page="./Edit" asp-route-Id="@item.Id"> Edit </a> |
            </td>
        </tr>
        }
    </tbody>
</table>

 

選取Visual Studio開發工具「Build」-「Build Solution」項目編譯目前的專案,確認程式碼能正確編譯。在Visual Studio開發工具,按CTRL+F5執行網站「Books/List」Razor Page,執行結果參考如下:

clip_image017

圖 9:選取要編輯資料。

當你點選某筆圖書後方的「Edit」超連結時,「Book」物件的「id」屬性值就以查詢字串的型式,傳遞到「Edit」Razor Page,如此我們便可利用查詢字串在「Books/Edit」Razor Page顯示要編輯的圖書資料,執行結果,請參考下圖所示:

clip_image019

圖 10:編輯圖書資料。

儲存修改的資料

當使用者將圖書資料修改後,我們需要透過服務來儲存修改的資料。先修改「MyServices」服務專案「IBookRepository」介面程式碼,新增一個「Update」方法,根據「editBook」參數「id」的值,來修改圖書資料,並回傳修改過的「Book」物件:

MyServices\IBookRepository.cs

using MyModels;
using System;
using System.Collections.Generic;
using System.Text;

namespace MyServices {
  public interface IBookRepository {
    IEnumerable<Book> GetAllBooks();
    Book GetBook( int id );
    Book Update( Book editBook );
  }
}

 

接著我們在「BookRepository」類別中,實作「Update」方法,請參考以下程式碼:

MyServices\BookRepository.cs

using MyModels;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MyServices {
  public class BookRepository : IBookRepository {
    private List<Book> _books = null;
    public BookRepository() {
      _books = new List<Book>() {
         new Book() {
           Id = 1 ,
           Title = " Essential Programming Language " ,
           Price = 250 ,
           PublishDate = new DateTime( 2019 ,1,2 ) ,
           InStock = true ,
           Description = "Essential Programming Language "  ,
          Category = Category.Computers
         },
         new Book() {
           Id = 2 ,
           Title = " Telling Arts " ,
           Price = 245 ,
           PublishDate = new DateTime( 2019 , 4 , 15 ) ,
           InStock = true ,
           Description = " Telling Arts "  ,
          Category = Category.Arts
         },
           new Book() {
           Id = 3 ,
           Title = " Marvel " ,
           Price = 150  ,
           PublishDate = new DateTime( 2019 , 2, 21 ) ,
           InStock = true ,
           Description = " Marvel "  ,
          Category = Category.Commics
         },
          new Book() {
           Id = 4 ,
           Title = " The Beauty of Cook" ,
           Price = 450 ,
           PublishDate = new DateTime( 2019 ,12,2 ) ,
           InStock = true ,
           Description = " The Beauty of Cook "  ,
           Category = Category.Cooking
         }
      };
    }
    public IEnumerable<Book> GetAllBooks() {
      return _books;
    }

    public Book GetBook( int id ) {
        return _books.FirstOrDefault( b => b.Id == id );
    }

    public Book Update( Book editBook ) {
      Book book  = _books.FirstOrDefault( b => b.Id == editBook.Id );
      if ( book!=null ) {
        book.Title = editBook.Title;
        book.Price = editBook.Price;
        book.PublishDate = editBook.PublishDate;
        book.InStock = editBook.InStock;
        book.Description = editBook.Description;
        book.Category = editBook.Category;
      }
      return book;
    }
  }
}

 

修改「MyRazorWeb\Pages\Books\Edit.cshtml.cs」檔案,加入以下程式碼:

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() {
      Book = bookRepository.Update( Book );
      return RedirectToPage( "List" );
    }
  }
}

 

我們加入了「OnPost」方法,這個方法會在網頁送出HTTP Post請求時被呼叫,也就是當使用者按下「Edit」畫面中「<input type="submit" />」按鈕時。特別注意,「EditModel」類別的「Book」屬性上方標註了「BindProperty」Attribute,如此ASP.NET Core 模型繫結功能會自動將表單欄位名稱與「EditModel」類別「Book」屬性名稱做對應,並將名稱相符的表單欄位資料,複製到對應的「Book」屬性之中。

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

clip_image021

圖 11:選取要編輯資料。

下一步便會進入編輯畫面,任意修改文字方塊中的值,再按下「Save」按鈕,請參考下圖所示:

clip_image023

圖 12:儲存編輯完成的資料。

修改完成後將回到「List」頁面,顯示最新圖書清單資料,請參考下圖所示:

clip_image025

圖 13:顯示修改後的資料。

Tags:

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

新增評論




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






NET Magazine國際中文電子雜誌

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

月分類Month List