第 5 部分,更新 ASP.NET Core 應用程式中產生的頁面

注意

這不是這篇文章的最新版本。 如需目前版本,請參閱本文的 .NET 8 版本

重要

這些發行前產品的相關資訊在產品正式發行前可能會有大幅修改。 Microsoft 對此處提供的資訊,不做任何明確或隱含的瑕疵擔保。

如需目前版本,請參閱本文的 .NET 8 版本

Scaffolded 電影應用程式是一個不錯的起點,但其呈現效果卻不理想。 ReleaseDate 應該是 Release Date (兩個字)。

在 Chrome 中開啟的電影應用程式

更新模型

使用下列醒目提示的程式碼更新 Models/Movie.cs

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace RazorPagesMovie.Models;

public class Movie
{
    public int Id { get; set; }
    public string Title { get; set; } = string.Empty;

    [Display(Name = "Release Date")]
    [DataType(DataType.Date)]
    public DateTime ReleaseDate { get; set; }
    public string Genre { get; set; } = string.Empty;

    [Column(TypeName = "decimal(18, 2)")]
    public decimal Price { get; set; }
}

在先前的程式碼中:

  • [Column(TypeName = "decimal(18, 2)")] 資料註解可讓 Entity Framework Core 將 Price 正確對應到資料庫中的貨幣。 如需詳細資訊,請參閱資料類型
  • [Display] 屬性會指定欄位的顯示名稱。 在上述程式碼中,會使用 Release Date 而不是 ReleaseDate
  • [DataType] 屬性會指定資料的類型 (Date)。 不會顯示儲存在欄位中的時間資訊。

接下來的教學課程會涵蓋 DataAnnotations

瀏覽至 [Pages/Movies],然後將滑鼠停留在 [編輯] 連結,以查看目標 URL。

滑鼠停留在 Edit 連結並顯示 https://localhost:1234/Movies/Edit/5 的 Url 的瀏覽器視窗

Pages/Movies/Index.cshtml 檔案中的 Core MVC 錨點標籤協助程式會產生 [編輯]、[詳細資料] 和 [刪除] 連結。

@foreach (var item in Model.Movie) {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.Title)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.ReleaseDate)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Genre)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Price)
            </td>
            <td>
                <a asp-page="./Edit" asp-route-id="@item.ID">Edit</a> |
                <a asp-page="./Details" asp-route-id="@item.ID">Details</a> |
                <a asp-page="./Delete" asp-route-id="@item.ID">Delete</a>
            </td>
        </tr>
}
    </tbody>
</table>

標記協助程式可啟用伺服器端程式碼,以參與建立和轉譯 Razor 檔案中的 HTML 元素。

在上述程式碼中,錨點標籤協助程式href會從 Razor Page (路由是相對路由)、asp-page 和路由識別碼 (asp-route-id) 動態產生 HTML 屬性值。 如需詳細資訊,請參閱 Pages 的 URL 產生

從瀏覽器中使用 [檢視原始檔] 來檢查產生的標記。 產生的 HTML 部分如下所示:

<td>
  <a href="/Movies/Edit?id=1">Edit</a> |
  <a href="/Movies/Details?id=1">Details</a> |
  <a href="/Movies/Delete?id=1">Delete</a>
</td>

動態產生的連結會傳遞含有查詢字串的電影識別碼。 例如,https://localhost:5001/Movies/Details?id=1 中的 ?id=1

新增路由範本

更新 [編輯]、[詳細資料] 和 [刪除] Razor 頁面,以使用 {id:int} 路由範本。 將這些頁面每一頁的頁面指示詞從 @page 變更為 @page "{id:int}"。 執行應用程式,然後檢視原始檔。

產生的 HTML 將識別碼新增至 URL 的路徑部分:

<td>
  <a href="/Movies/Edit/1">Edit</a> |
  <a href="/Movies/Details/1">Details</a> |
  <a href="/Movies/Delete/1">Delete</a>
</td>

對使用 {id:int} 路由範本的頁面提出的要求若包含整數,會導致傳回 HTTP 404 (找不到) 錯誤。 例如,https://localhost:5001/Movies/Details 傳回 404 錯誤。 若要使識別碼成為選擇性,請將 ? 附加至路由條件約束:

@page "{id:int?}"

測試 @page "{id:int?}" 的行為:

  1. Pages/Movies/Details.cshtml 中的 page 指示詞設定為 @page "{id:int?}"
  2. public async Task<IActionResult> OnGetAsync(int? id)Pages/Movies/Details.cshtml.cs 中設定中斷點。
  3. 瀏覽至 https://localhost:5001/Movies/Details/

使用 @page "{id:int}" 指示詞,永遠不會叫用中斷點。 路由引擎會傳回 HTTP 404。 使用 @page "{id:int?}"OnGetAsync 方法會傳回 NotFound (HTTP 404):

public async Task<IActionResult> OnGetAsync(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    Movie = await _context.Movie.FirstOrDefaultAsync(m => m.ID == id);

    if (Movie == null)
    {
        return NotFound();
    }
    return Page();
}

檢閱並行存取例外狀況處理

檢閱 Pages/Movies/Edit.cshtml.cs 檔案中的 OnPostAsync 方法:

public async Task<IActionResult> OnPostAsync()
{
    if (!ModelState.IsValid)
    {
        return Page();
    }

    _context.Attach(Movie).State = EntityState.Modified;

    try
    {
        await _context.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException)
    {
        if (!MovieExists(Movie.Id))
        {
            return NotFound();
        }
        else
        {
            throw;
        }
    }

    return RedirectToPage("./Index");
}

private bool MovieExists(int id)
{
  return _context.Movie.Any(e => e.Id == id);
}

當一個用戶端刪除電影,而另一個用戶端發佈對電影的變更時,先前的程式碼會偵測並行存取例外狀況。

若要測試 catch 區段:

  1. catch (DbUpdateConcurrencyException) 上設定中斷點。
  2. 針對電影選取 [編輯],進行變更,但不要輸入 [儲存]
  3. 在另一個瀏覽器視窗中,選取相同電影的 Delete 連結,然後刪除電影。
  4. 在先前的瀏覽器視窗中,發佈對電影的變更。

實際執行程式碼可能需要偵測並行存取衝突。 如需詳細資訊,請參閱處理並行存取衝突

發佈和繫結檢閱內容

檢查 Pages/Movies/Edit.cshtml.cs 檔案:

public class EditModel : PageModel
{
    private readonly RazorPagesMovie.Data.RazorPagesMovieContext _context;

    public EditModel(RazorPagesMovie.Data.RazorPagesMovieContext context)
    {
        _context = context;
    }

    [BindProperty]
    public Movie Movie { get; set; } = default!;

    public async Task<IActionResult> OnGetAsync(int? id)
    {
        if (id == null || _context.Movie == null)
        {
            return NotFound();
        }

        var movie =  await _context.Movie.FirstOrDefaultAsync(m => m.Id == id);
        if (movie == null)
        {
            return NotFound();
        }
        Movie = movie;
        return Page();
    }

    // To protect from overposting attacks, enable the specific properties you want to bind to.
    // For more details, see https://aka.ms/RazorPagesCRUD.
    public async Task<IActionResult> OnPostAsync()
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }

        _context.Attach(Movie).State = EntityState.Modified;

        try
        {
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(Movie.Id))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }

        return RedirectToPage("./Index");
    }

    private bool MovieExists(int id)
    {
      return _context.Movie.Any(e => e.Id == id);
    }

對 Movies/Edit 頁面提出 HTTP GET 要求時 (例如,https://localhost:5001/Movies/Edit/3):

  • OnGetAsync 方法會從資料庫擷取電影,並傳回 Page 方法。
  • Page 方法會轉譯 Pages/Movies/Edit.cshtmlRazor Page。 Pages/Movies/Edit.cshtml 檔案包含模型指示詞 @model RazorPagesMovie.Pages.Movies.EditModel,這會讓電影模型可以在頁面上使用。
  • Edit 表單會顯示來自電影的值。

發佈 Movies/Edit 頁面時:

  • 頁面上的表單值會繫結至 Movie 屬性。 [BindProperty] 屬性可讓模型繫結

    [BindProperty]
    public Movie Movie { get; set; }
    
  • 如果模型狀態中有錯誤 (例如 ReleaseDate 無法轉換為日期),則會以提交的值重新顯示表單。

  • 如果沒有任何模型錯誤,則會儲存電影。

Index、Create 和 Delete Razor 頁面中的 HTTP GET 方法都會依循類似的模式。 Create Razor Page中的 HTTP POST OnPostAsync 方法,會依循與 Edit Razor 頁面中的 OnPostAsync 方法類似的模式。

下一步

Scaffolded 電影應用程式是一個不錯的起點,但其呈現效果卻不理想。 ReleaseDate 應該是 Release Date (兩個字)。

在 Chrome 中開啟的電影應用程式

更新模型

使用下列醒目提示的程式碼更新 Models/Movie.cs

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace RazorPagesMovie.Models;

public class Movie
{
    public int Id { get; set; }
    public string Title { get; set; } = string.Empty;

    [Display(Name = "Release Date")]
    [DataType(DataType.Date)]
    public DateTime ReleaseDate { get; set; }
    public string Genre { get; set; } = string.Empty;

    [Column(TypeName = "decimal(18, 2)")]
    public decimal Price { get; set; }
}

在先前的程式碼中:

  • [Column(TypeName = "decimal(18, 2)")] 資料註解可讓 Entity Framework Core 將 Price 正確對應到資料庫中的貨幣。 如需詳細資訊,請參閱資料類型
  • [Display] 屬性會指定欄位的顯示名稱。 在上述程式碼中,會使用 Release Date 而不是 ReleaseDate
  • [DataType] 屬性會指定資料的類型 (Date)。 不會顯示儲存在欄位中的時間資訊。

接下來的教學課程會涵蓋 DataAnnotations

瀏覽至 [Pages/Movies],然後將滑鼠停留在 [編輯] 連結,以查看目標 URL。

滑鼠停留在 Edit 連結並顯示 https://localhost:1234/Movies/Edit/5 的 Url 的瀏覽器視窗

Pages/Movies/Index.cshtml 檔案中的 Core MVC 錨點標籤協助程式會產生 [編輯]、[詳細資料] 和 [刪除] 連結。

@foreach (var item in Model.Movie) {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.Title)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.ReleaseDate)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Genre)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Price)
            </td>
            <td>
                <a asp-page="./Edit" asp-route-id="@item.ID">Edit</a> |
                <a asp-page="./Details" asp-route-id="@item.ID">Details</a> |
                <a asp-page="./Delete" asp-route-id="@item.ID">Delete</a>
            </td>
        </tr>
}
    </tbody>
</table>

標記協助程式可啟用伺服器端程式碼,以參與建立和轉譯 Razor 檔案中的 HTML 元素。

在上述程式碼中,錨點標籤協助程式href會從 Razor Page (路由是相對路由)、asp-page 和路由識別碼 (asp-route-id) 動態產生 HTML 屬性值。 如需詳細資訊,請參閱 Pages 的 URL 產生

從瀏覽器中使用 [檢視原始檔] 來檢查產生的標記。 產生的 HTML 部分如下所示:

<td>
  <a href="/Movies/Edit?id=1">Edit</a> |
  <a href="/Movies/Details?id=1">Details</a> |
  <a href="/Movies/Delete?id=1">Delete</a>
</td>

動態產生的連結會傳遞含有查詢字串的電影識別碼。 例如,https://localhost:5001/Movies/Details?id=1 中的 ?id=1

新增路由範本

更新 [編輯]、[詳細資料] 和 [刪除] Razor 頁面,以使用 {id:int} 路由範本。 將這些頁面每一頁的頁面指示詞從 @page 變更為 @page "{id:int}"。 執行應用程式,然後檢視原始檔。

產生的 HTML 將識別碼新增至 URL 的路徑部分:

<td>
  <a href="/Movies/Edit/1">Edit</a> |
  <a href="/Movies/Details/1">Details</a> |
  <a href="/Movies/Delete/1">Delete</a>
</td>

對使用 {id:int} 路由範本的頁面提出的要求若包含整數,會導致傳回 HTTP 404 (找不到) 錯誤。 例如,https://localhost:5001/Movies/Details 傳回 404 錯誤。 若要使識別碼成為選擇性,請將 ? 附加至路由條件約束:

@page "{id:int?}"

測試 @page "{id:int?}" 的行為:

  1. Pages/Movies/Details.cshtml 中的 page 指示詞設定為 @page "{id:int?}"
  2. public async Task<IActionResult> OnGetAsync(int? id)Pages/Movies/Details.cshtml.cs 中設定中斷點。
  3. 瀏覽至 https://localhost:5001/Movies/Details/

使用 @page "{id:int}" 指示詞,永遠不會叫用中斷點。 路由引擎會傳回 HTTP 404。 使用 @page "{id:int?}"OnGetAsync 方法會傳回 NotFound (HTTP 404):

public async Task<IActionResult> OnGetAsync(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    Movie = await _context.Movie.FirstOrDefaultAsync(m => m.ID == id);

    if (Movie == null)
    {
        return NotFound();
    }
    return Page();
}

檢閱並行存取例外狀況處理

檢閱 Pages/Movies/Edit.cshtml.cs 檔案中的 OnPostAsync 方法:

public async Task<IActionResult> OnPostAsync()
{
    if (!ModelState.IsValid)
    {
        return Page();
    }

    _context.Attach(Movie).State = EntityState.Modified;

    try
    {
        await _context.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException)
    {
        if (!MovieExists(Movie.Id))
        {
            return NotFound();
        }
        else
        {
            throw;
        }
    }

    return RedirectToPage("./Index");
}

private bool MovieExists(int id)
{
  return _context.Movie.Any(e => e.Id == id);
}

當一個用戶端刪除電影,而另一個用戶端發佈對電影的變更時,先前的程式碼會偵測並行存取例外狀況。

若要測試 catch 區段:

  1. catch (DbUpdateConcurrencyException) 上設定中斷點。
  2. 針對電影選取 [編輯],進行變更,但不要輸入 [儲存]
  3. 在另一個瀏覽器視窗中,選取相同電影的 Delete 連結,然後刪除電影。
  4. 在先前的瀏覽器視窗中,發佈對電影的變更。

實際執行程式碼可能需要偵測並行存取衝突。 如需詳細資訊,請參閱處理並行存取衝突

發佈和繫結檢閱內容

檢查 Pages/Movies/Edit.cshtml.cs 檔案:

public class EditModel : PageModel
{
    private readonly RazorPagesMovie.Data.RazorPagesMovieContext _context;

    public EditModel(RazorPagesMovie.Data.RazorPagesMovieContext context)
    {
        _context = context;
    }

    [BindProperty]
    public Movie Movie { get; set; } = default!;

    public async Task<IActionResult> OnGetAsync(int? id)
    {
        if (id == null || _context.Movie == null)
        {
            return NotFound();
        }

        var movie =  await _context.Movie.FirstOrDefaultAsync(m => m.Id == id);
        if (movie == null)
        {
            return NotFound();
        }
        Movie = movie;
        return Page();
    }

    // To protect from overposting attacks, enable the specific properties you want to bind to.
    // For more details, see https://aka.ms/RazorPagesCRUD.
    public async Task<IActionResult> OnPostAsync()
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }

        _context.Attach(Movie).State = EntityState.Modified;

        try
        {
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(Movie.Id))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }

        return RedirectToPage("./Index");
    }

    private bool MovieExists(int id)
    {
      return _context.Movie.Any(e => e.Id == id);
    }

對 Movies/Edit 頁面提出 HTTP GET 要求時 (例如,https://localhost:5001/Movies/Edit/3):

  • OnGetAsync 方法會從資料庫擷取電影,並傳回 Page 方法。
  • Page 方法會轉譯 Pages/Movies/Edit.cshtmlRazor Page。 Pages/Movies/Edit.cshtml 檔案包含模型指示詞 @model RazorPagesMovie.Pages.Movies.EditModel,這會讓電影模型可以在頁面上使用。
  • Edit 表單會顯示來自電影的值。

發佈 Movies/Edit 頁面時:

  • 頁面上的表單值會繫結至 Movie 屬性。 [BindProperty] 屬性可讓模型繫結

    [BindProperty]
    public Movie Movie { get; set; }
    
  • 如果模型狀態中有錯誤 (例如 ReleaseDate 無法轉換為日期),則會以提交的值重新顯示表單。

  • 如果沒有任何模型錯誤,則會儲存電影。

Index、Create 和 Delete Razor 頁面中的 HTTP GET 方法都會依循類似的模式。 Create Razor Page中的 HTTP POST OnPostAsync 方法,會依循與 Edit Razor 頁面中的 OnPostAsync 方法類似的模式。

下一步

Scaffolded 電影應用程式是一個不錯的起點,但其呈現效果卻不理想。 ReleaseDate 應該是 Release Date (兩個字)。

在 Chrome 中開啟的電影應用程式

更新產生的程式碼

使用下列醒目提示的程式碼更新 Models/Movie.cs

using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace RazorPagesMovie.Models
{
    public class Movie
    {
        public int ID { get; set; }
        public string Title { get; set; } = string.Empty;

        [Display(Name = "Release Date")]
        [DataType(DataType.Date)]
        public DateTime ReleaseDate { get; set; }
        public string Genre { get; set; } = string.Empty;

        [Column(TypeName = "decimal(18, 2)")]
        public decimal Price { get; set; }
    }
}

在先前的程式碼中:

  • [Column(TypeName = "decimal(18, 2)")] 資料註解可讓 Entity Framework Core 將 Price 正確對應到資料庫中的貨幣。 如需詳細資訊,請參閱資料類型
  • [Display] 屬性會指定欄位的顯示名稱。 在上述程式碼中,應為「Release Date」,而不是「ReleaseDate」。
  • [DataType] 屬性會指定資料的類型 (Date)。 不會顯示儲存在欄位中的時間資訊。

接下來的教學課程會涵蓋 DataAnnotations

瀏覽至 [Pages/Movies],然後將滑鼠停留在 [編輯] 連結,以查看目標 URL。

滑鼠停留在 Edit 連結並顯示 https://localhost:1234/Movies/Edit/5 的 Url 的瀏覽器視窗

Pages/Movies/Index.cshtml 檔案中的 Core MVC 錨點標籤協助程式會產生 [編輯]、[詳細資料] 和 [刪除] 連結。

@foreach (var item in Model.Movie) {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.Title)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.ReleaseDate)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Genre)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Price)
            </td>
            <td>
                <a asp-page="./Edit" asp-route-id="@item.ID">Edit</a> |
                <a asp-page="./Details" asp-route-id="@item.ID">Details</a> |
                <a asp-page="./Delete" asp-route-id="@item.ID">Delete</a>
            </td>
        </tr>
}
    </tbody>
</table>

標記協助程式可啟用伺服器端程式碼,以參與建立和轉譯 Razor 檔案中的 HTML 元素。

在上述程式碼中,錨點標籤協助程式href會從 Razor Page (路由是相對路由)、asp-page 和路由識別碼 (asp-route-id) 動態產生 HTML 屬性值。 如需詳細資訊,請參閱 Pages 的 URL 產生

從瀏覽器中使用 [檢視原始檔] 來檢查產生的標記。 產生的 HTML 部分如下所示:

<td>
  <a href="/Movies/Edit?id=1">Edit</a> |
  <a href="/Movies/Details?id=1">Details</a> |
  <a href="/Movies/Delete?id=1">Delete</a>
</td>

動態產生的連結會傳遞含有查詢字串的電影識別碼。 例如,https://localhost:5001/Movies/Details?id=1 中的 ?id=1

新增路由範本

更新 [編輯]、[詳細資料] 和 [刪除] Razor 頁面,以使用 {id:int} 路由範本。 將這些頁面每一頁的頁面指示詞從 @page 變更為 @page "{id:int}"。 執行應用程式,然後檢視原始檔。

產生的 HTML 將識別碼新增至 URL 的路徑部分:

<td>
  <a href="/Movies/Edit/1">Edit</a> |
  <a href="/Movies/Details/1">Details</a> |
  <a href="/Movies/Delete/1">Delete</a>
</td>

對使用 {id:int} 路由範本的頁面提出的要求若包含整數,將傳回 HTTP 404 (找不到) 錯誤。 例如,https://localhost:5001/Movies/Details 會傳回 404 錯誤。 若要使識別碼成為選擇性,請將 ? 附加至路由條件約束:

@page "{id:int?}"

測試 @page "{id:int?}" 的行為:

  1. Pages/Movies/Details.cshtml 中的 page 指示詞設定為 @page "{id:int?}"
  2. public async Task<IActionResult> OnGetAsync(int? id)Pages/Movies/Details.cshtml.cs 中設定中斷點。
  3. 瀏覽至 https://localhost:5001/Movies/Details/

使用 @page "{id:int}" 指示詞,永遠不會叫用中斷點。 路由引擎會傳回 HTTP 404。 使用 @page "{id:int?}"OnGetAsync 方法會傳回 NotFound (HTTP 404):

public async Task<IActionResult> OnGetAsync(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    Movie = await _context.Movie.FirstOrDefaultAsync(m => m.ID == id);

    if (Movie == null)
    {
        return NotFound();
    }
    return Page();
}

檢閱並行存取例外狀況處理

檢閱 Pages/Movies/Edit.cshtml.cs 檔案中的 OnPostAsync 方法:

public async Task<IActionResult> OnPostAsync()
{
    if (!ModelState.IsValid)
    {
        return Page();
    }

    _context.Attach(Movie).State = EntityState.Modified;

    try
    {
        await _context.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException)
    {
        if (!MovieExists(Movie.ID))
        {
            return NotFound();
        }
        else
        {
            throw;
        }
    }

    return RedirectToPage("./Index");
}

private bool MovieExists(int id)
{
  return (_context.Movie?.Any(e => e.ID == id)).GetValueOrDefault();
}

當一個用戶端刪除電影,而另一個用戶端發佈對電影的變更時,先前的程式碼會偵測並行存取例外狀況。 先前的程式碼不會偵測因為兩個或多個用戶端同時編輯相同電影而發生的衝突。 在此情況下,多個用戶端的編輯會依呼叫 SaveChanges 的順序套用,後續所套用的編輯可能會覆寫先前帶有過時值的編輯。

若要測試 catch 區段:

  1. catch (DbUpdateConcurrencyException) 上設定中斷點。
  2. 針對電影選取 [編輯],進行變更,但不要輸入 [儲存]
  3. 在另一個瀏覽器視窗中,選取相同電影的 Delete 連結,然後刪除電影。
  4. 在先前的瀏覽器視窗中,發佈對電影的變更。

生產程式碼可能會偵測其他並行衝突,例如:同時針對實體進行編輯的多個用戶端。 如需詳細資訊,請參閱處理並行存取衝突

發佈和繫結檢閱內容

檢查 Pages/Movies/Edit.cshtml.cs 檔案:

public class EditModel : PageModel
{
    private readonly RazorPagesMovie.Data.RazorPagesMovieContext _context;

    public EditModel(RazorPagesMovie.Data.RazorPagesMovieContext context)
    {
        _context = context;
    }

    [BindProperty]
    public Movie Movie { get; set; } = default!;

    public async Task<IActionResult> OnGetAsync(int? id)
    {
        if (id == null || _context.Movie == null)
        {
            return NotFound();
        }

        var movie =  await _context.Movie.FirstOrDefaultAsync(m => m.ID == id);
        if (movie == null)
        {
            return NotFound();
        }
        Movie = movie;
        return Page();
    }

    // To protect from overposting attacks, enable the specific properties you want to bind to.
    // For more details, see https://aka.ms/RazorPagesCRUD.
    public async Task<IActionResult> OnPostAsync()
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }

        _context.Attach(Movie).State = EntityState.Modified;

        try
        {
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(Movie.ID))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }

        return RedirectToPage("./Index");
    }

    private bool MovieExists(int id)
    {
      return (_context.Movie?.Any(e => e.ID == id)).GetValueOrDefault();
    }

對 Movies/Edit 頁面提出 HTTP GET 要求時 (例如,https://localhost:5001/Movies/Edit/3):

  • OnGetAsync 方法會從資料庫擷取電影,並傳回 Page 方法。
  • Page 方法會轉譯 Pages/Movies/Edit.cshtmlRazor Page。 Pages/Movies/Edit.cshtml 檔案包含模型指示詞 @model RazorPagesMovie.Pages.Movies.EditModel,這會讓電影模型可以在頁面上使用。
  • Edit 表單會顯示來自電影的值。

發佈 Movies/Edit 頁面時:

  • 頁面上的表單值會繫結至 Movie 屬性。 [BindProperty] 屬性可讓模型繫結

    [BindProperty]
    public Movie Movie { get; set; }
    
  • 如果模型狀態中有錯誤 (例如 ReleaseDate 無法轉換為日期),則會以提交的值重新顯示表單。

  • 如果沒有任何模型錯誤,則會儲存電影。

Index、Create 和 Delete Razor 頁面中的 HTTP GET 方法都會依循類似的模式。 Create Razor Page中的 HTTP POST OnPostAsync 方法,會依循與 Edit Razor 頁面中的 OnPostAsync 方法類似的模式。

下一步

Scaffolded 電影應用程式是一個不錯的起點,但其呈現效果卻不理想。 ReleaseDate 應該是 Release Date (兩個字)。

在 Chrome 中開啟的電影應用程式

更新產生的程式碼

開啟 Models/Movie.cs 檔案,然後新增下列程式碼中顯示的醒目提示行:

using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace RazorPagesMovie.Models
{
    public class Movie
    {
        public int ID { get; set; }
        public string Title { get; set; }

        [Display(Name = "Release Date")]
        [DataType(DataType.Date)]
        public DateTime ReleaseDate { get; set; }
        public string Genre { get; set; }

        [Column(TypeName = "decimal(18, 2)")]
        public decimal Price { get; set; }
    }
}

在先前的程式碼中:

  • [Column(TypeName = "decimal(18, 2)")] 資料註解可讓 Entity Framework Core 將 Price 正確對應到資料庫中的貨幣。 如需詳細資訊,請參閱資料類型
  • [Display] 屬性會指定欄位的顯示名稱。 在上述程式碼中,應為「Release Date」,而不是「ReleaseDate」。
  • [DataType] 屬性會指定資料的類型 (Date)。 不會顯示儲存在欄位中的時間資訊。

接下來的教學課程會涵蓋 DataAnnotations

瀏覽至 [Pages/Movies],然後將滑鼠停留在 [編輯] 連結,以查看目標 URL。

滑鼠停留在 Edit 連結並顯示 https://localhost:1234/Movies/Edit/5 的 Url 的瀏覽器視窗

Pages/Movies/Index.cshtml 檔案中的 Core MVC 錨點標籤協助程式會產生 [編輯]、[詳細資料] 和 [刪除] 連結。

@foreach (var item in Model.Movie) {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.Title)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.ReleaseDate)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Genre)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Price)
            </td>
            <td>
                <a asp-page="./Edit" asp-route-id="@item.ID">Edit</a> |
                <a asp-page="./Details" asp-route-id="@item.ID">Details</a> |
                <a asp-page="./Delete" asp-route-id="@item.ID">Delete</a>
            </td>
        </tr>
}
    </tbody>
</table>

標記協助程式可啟用伺服器端程式碼,以參與建立和轉譯 Razor 檔案中的 HTML 元素。

在上述程式碼中,錨點標籤協助程式href會從 Razor Page (路由是相對路由)、asp-page 和路由識別碼 (asp-route-id) 動態產生 HTML 屬性值。 如需詳細資訊,請參閱 Pages 的 URL 產生

從瀏覽器中使用 [檢視原始檔] 來檢查產生的標記。 產生的 HTML 部分如下所示:

<td>
  <a href="/Movies/Edit?id=1">Edit</a> |
  <a href="/Movies/Details?id=1">Details</a> |
  <a href="/Movies/Delete?id=1">Delete</a>
</td>

動態產生的連結會傳遞含有查詢字串的電影識別碼。 例如,https://localhost:5001/Movies/Details?id=1 中的 ?id=1

新增路由範本

更新 [編輯]、[詳細資料] 和 [刪除] Razor 頁面,以使用 {id:int} 路由範本。 將這些頁面每一頁的頁面指示詞從 @page 變更為 @page "{id:int}"。 執行應用程式,然後檢視原始檔。

產生的 HTML 將識別碼新增至 URL 的路徑部分:

<td>
  <a href="/Movies/Edit/1">Edit</a> |
  <a href="/Movies/Details/1">Details</a> |
  <a href="/Movies/Delete/1">Delete</a>
</td>

對使用 {id:int} 路由範本的頁面提出的要求若包含整數,將傳回 HTTP 404 (找不到) 錯誤。 例如,https://localhost:5001/Movies/Details 會傳回 404 錯誤。 若要使識別碼成為選擇性,請將 ? 附加至路由條件約束:

@page "{id:int?}"

測試 @page "{id:int?}" 的行為:

  1. Pages/Movies/Details.cshtml 中的 page 指示詞設定為 @page "{id:int?}"
  2. public async Task<IActionResult> OnGetAsync(int? id)Pages/Movies/Details.cshtml.cs 中設定中斷點。
  3. 瀏覽至 https://localhost:5001/Movies/Details/

使用 @page "{id:int}" 指示詞,永遠不會叫用中斷點。 路由引擎會傳回 HTTP 404。 使用 @page "{id:int?}"OnGetAsync 方法會傳回 NotFound (HTTP 404):

public async Task<IActionResult> OnGetAsync(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    Movie = await _context.Movie.FirstOrDefaultAsync(m => m.ID == id);

    if (Movie == null)
    {
        return NotFound();
    }
    return Page();
}

檢閱並行存取例外狀況處理

檢閱 Pages/Movies/Edit.cshtml.cs 檔案中的 OnPostAsync 方法:

public async Task<IActionResult> OnPostAsync()
{
    if (!ModelState.IsValid)
    {
        return Page();
    }

    _context.Attach(Movie).State = EntityState.Modified;

    try
    {
        await _context.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException)
    {
        if (!MovieExists(Movie.ID))
        {
            return NotFound();
        }
        else
        {
            throw;
        }
    }

    return RedirectToPage("./Index");
}

private bool MovieExists(int id)
{
    return _context.Movie.Any(e => e.ID == id);
}

當一個用戶端刪除電影,而另一個用戶端發佈對電影的變更時,先前的程式碼會偵測並行存取例外狀況。

若要測試 catch 區段:

  1. catch (DbUpdateConcurrencyException) 上設定中斷點。
  2. 針對電影選取 [編輯],進行變更,但不要輸入 [儲存]
  3. 在另一個瀏覽器視窗中,選取相同電影的 Delete 連結,然後刪除電影。
  4. 在先前的瀏覽器視窗中,發佈對電影的變更。

實際執行程式碼可能需要偵測並行存取衝突。 如需詳細資訊,請參閱處理並行存取衝突

發佈和繫結檢閱內容

檢查 Pages/Movies/Edit.cshtml.cs 檔案:

public class EditModel : PageModel
{
    private readonly RazorPagesMovie.Data.RazorPagesMovieContext _context;

    public EditModel(RazorPagesMovie.Data.RazorPagesMovieContext context)
    {
        _context = context;
    }

    [BindProperty]
    public Movie Movie { get; set; }

    public async Task<IActionResult> OnGetAsync(int? id)
    {
        if (id == null)
        {
            return NotFound();
        }

        Movie = await _context.Movie.FirstOrDefaultAsync(m => m.ID == id);

        if (Movie == null)
        {
            return NotFound();
        }
        return Page();
    }

    public async Task<IActionResult> OnPostAsync()
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }

        _context.Attach(Movie).State = EntityState.Modified;

        try
        {
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(Movie.ID))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }

        return RedirectToPage("./Index");
    }

    private bool MovieExists(int id)
    {
        return _context.Movie.Any(e => e.ID == id);
    }

對 Movies/Edit 頁面提出 HTTP GET 要求時 (例如,https://localhost:5001/Movies/Edit/3):

  • OnGetAsync 方法會從資料庫擷取電影,並傳回 Page 方法。
  • Page 方法會轉譯 Pages/Movies/Edit.cshtmlRazor Page。 Pages/Movies/Edit.cshtml 檔案包含模型指示詞 @model RazorPagesMovie.Pages.Movies.EditModel,這會讓電影模型可以在頁面上使用。
  • Edit 表單會顯示來自電影的值。

發佈 Movies/Edit 頁面時:

  • 頁面上的表單值會繫結至 Movie 屬性。 [BindProperty] 屬性可讓模型繫結

    [BindProperty]
    public Movie Movie { get; set; }
    
  • 如果模型狀態中有錯誤 (例如 ReleaseDate 無法轉換為日期),則會以提交的值重新顯示表單。

  • 如果沒有任何模型錯誤,則會儲存電影。

Index、Create 和 Delete Razor 頁面中的 HTTP GET 方法都會依循類似的模式。 Create Razor Page中的 HTTP POST OnPostAsync 方法,會依循與 Edit Razor 頁面中的 OnPostAsync 方法類似的模式。

下一步