練習 - 使用 @page 指示詞,變更 Blazor 應用程式中的導覽方式

已完成

Blazor 具有導覽狀態協助程式,可協助 C# 程式碼管理應用程式的 URI。 另外也有 NavLink 元件,是 <a> 元素的插入式取代。 NavLink 的其中一項功能是將作用中類別新增至應用程式功能表的 HTML 連結。

您的小組已經開始使用 Blazing Pizza 應用程式,並且已建置 Blazor 元件來代表披薩與訂單。 此應用程式現在需要結帳與其他訂單的相關頁面。

在此練習中,您將新增結帳頁面、將上方導覽新增至應用程式,然後使用 Blazor NavLink 元件來改善程式碼。

複製小組現有的應用程式

注意

本課程模組使用 .NET 命令列介面 (CLI) 和 Visual Studio Code 進行本機開發。 完成本課程模組之後,您可以使用 Visual Studio (Windows) 或 Visual Studio for Mac (macOS) 來套用概念。 若要繼續開發,請使用適用於 Windows、Linux 與 macOS 的 Visual Studio Code。

本課程模組使用 .NET 6.0 SDK。 確認您已在慣用終端中執行下列命令來安裝 .NET 6.0:

dotnet --list-sdks

您會看到類似以下的輸出:

3.1.100 [C:\program files\dotnet\sdk]
5.0.100 [C:\program files\dotnet\sdk]
6.0.100 [C:\program files\dotnet\sdk]

確定已列出開頭為 6 的版本。 如果未列出任何項目或找不到命令,則請安裝最新的 .NET 6.0 SDK

若您之前尚未建立 Blazor 應用程式,請遵循 Blazor 的安裝指示來安裝正確的 .NET 版本,並檢查您的電腦是否已正確設定。 在 [建立您的應用程式] 步驟停止。

  1. 開啟 Visual Studio Code。

  2. 選取 [檢視],從 Visual Studio Code 開啟整合式終端。 然後在主功能表上,選取 [終端]

  3. 在終端中,移至您想要建立專案的位置。

  4. 從 GitHub 複製應用程式。

    git clone https://github.com/MicrosoftDocs/mslearn-blazor-navigation.git BlazingPizza
    
  5. 選取 [檔案],然後選取 [開啟資料夾]

  6. 在 [開啟] 對話方塊中,移至 [BlazingPizza] 資料夾,然後選擇 [選取資料夾]

    Visual Studio Code 可能會提示您相關的未解決相依性。 選取還原

  7. 執行應用程式以確認一切正常運作。

  8. 在 Visual Studio Code 中,選取 F5。 或在 [執行] 功能表上,選取 [開始偵錯]

    螢幕擷取畫面,其中顯示 Blazing Pizza 應用程式的複製版本。

    設定一些披薩,然後新增至訂單。 選取頁面底部的 [訂單>]。 您會看到預設的「404 找不到」訊息,因為小組尚未建立結帳頁面。

  9. 選取 Shift + F5 以停止應用程式。

新增結帳頁面

  1. 在 Visual Studio Code 的檔案總管中,選取 [App.razor]

    <Router AppAssembly="@typeof(Program).Assembly" PreferExactMatches="@true">
        <Found Context="routeData">
            <RouteView RouteData="@routeData" />
        </Found>
        <NotFound>
            <LayoutView>
                <p>Sorry, there's nothing at this address.</p>
            </LayoutView>
        </NotFound>
    </Router>
    

    <NotFound> 程式碼區塊是客戶嘗試前往不存在的頁面時會看到的訊息。

  2. 在檔案總管中,展開 [頁面],以滑鼠右鍵按一下資料夾,然後選取 [新增檔案]

  3. 將新檔案命名為 Checkout.razor。 在此檔案中,寫入下列程式碼:

    @page "/checkout"
    @inject OrderState OrderState
    @inject HttpClient HttpClient
    @inject NavigationManager NavigationManager
    
    <div class="top-bar">
        <a class="logo" href="">
            <img src="img/logo.svg" />
        </a>
    
        <a href="" class="nav-tab">
            <img src="img/pizza-slice.svg" />
            <div>Get Pizza</div>
        </a>
    
    </div>
    
    <div class="main">
        <div class="checkout-cols">
            <div class="checkout-order-details">
                <h4>Review order</h4>
                @foreach (var pizza in Order.Pizzas)
                {
                    <p>
                        <strong>
                            @(pizza.Size)"
                            @pizza.Special.Name
                            (£@pizza.GetFormattedTotalPrice())
                        </strong>
                    </p>
                }
    
                <p>
                    <strong>
                        Total price:
                        £@Order.GetFormattedTotalPrice()
                    </strong>
                </p>
            </div>
        </div>
    
        <button class="checkout-button btn btn-warning">
            Place order
        </button>
    </div>
    
    @code {
        Order Order => OrderState.Order;
    }
    

    此頁面是以目前的應用程式為基礎所建置,並使用以 OrderState 儲存的應用程式狀態。 第一個 div 是應用程式的新標頭導覽。 請將其新增至索引頁面。

  4. 在檔案總管中展開 [頁面],然後選取 [index.razor]

  5. <div class="main"> 類別上方,新增 top-bar HTML。

    <div class="top-bar">
        <a class="logo" href="">
            <img src="img/logo.svg" />
        </a>
    
        <a href="" class="nav-tab" >
            <img src="img/pizza-slice.svg" />
            <div>Get Pizza</div>
        </a>
    
    </div>
    

    當我們在此頁面時,藉由醒目提示連結來顯示客戶是個不錯的做法。 小組已建立 active css 類別,因此請將 active 新增至已包含 nav-tab 樣式的 class 屬性。

    <div class="top-bar">
        <a class="logo" href="">
            <img src="img/logo.svg" />
        </a>
    
        <a href="" class="nav-tab active" >
            <img src="img/pizza-slice.svg" />
            <div>Get Pizza</div>
        </a>
    
    </div>
    
  6. 在 Visual Studio Code 中,選取 F5。 或在 [執行] 功能表上,選取 [開始偵錯]

    應用程式的頂端現在有很不錯的功能表列,其中包含公司的標誌。 新增一些披薩,並將訂單進度推進到結帳頁面。 您會看到列出的披薩,以及功能表遺漏的作用中指標。

    螢幕擷取畫面,其中顯示具有一些披薩的結帳頁面。

  7. 選取 Shift + F5 以停止應用程式。

允許客戶下單

目前,結帳頁面不允許客戶下單。 應用程式的邏輯必須先儲存訂單,才能傳送至廚房。 傳送訂單之後,請將客戶重新導向回到首頁。

  1. 在檔案總管中展開 [頁面],然後選取 [Checkout.razor]

  2. 呼叫 PlaceOrder 方法來修改按鈕元素。 新增 @onclickdisabled 屬性,如下所示:

    <button class="checkout-button btn btn-warning" @onclick="PlaceOrder" disabled=@isSubmitting>
      Place order
    </button>
    

    我們不希望客戶重複下單,因此請在系統處理好訂單之前,停用 [下單] 按鈕。

  3. @code 區塊的 Order Order => OrderState.Order; 程式碼下方新增此程式碼。

    bool isSubmitting;
    
    async Task PlaceOrder()
    {
        isSubmitting = true;
        var response = await HttpClient.PostAsJsonAsync(NavigationManager.BaseUri + "orders", OrderState.Order);
        var newOrderId= await response.Content.ReadFromJsonAsync<int>();
        OrderState.ResetOrder();
        NavigationManager.NavigateTo("/");
    }
    

    上述程式碼會停用 [下單] 按鈕、發佈將新增至 pizza.db 的 JSON、清除訂單,以及使用 NavigationManager 將客戶重新導向至首頁。

    您需要新增可處理訂單的程式碼。 您將為此工作新增 OrderController 類別。 若您查看 PizzaStoreContext.cs,則會看到只有 PizzaSpecials 的 Entity Framework 資料庫支援。 讓我們先修正此問題。

新增訂單與披薩的 Entity Framework 支援

  1. 在檔案總管中,選取 [PizzaStoreContext.cs]

  2. 以此程式碼取代 PizzaStoreContext 類別:

      public class PizzaStoreContext : DbContext
      {
            public PizzaStoreContext(
                DbContextOptions options) : base(options)
            {
            }
    
            public DbSet<Order> Orders { get; set; }
    
            public DbSet<Pizza> Pizzas { get; set; }
    
            public DbSet<PizzaSpecial> Specials { get; set; }
    
            public DbSet<Topping> Toppings { get; set; }
    
            protected override void OnModelCreating(ModelBuilder modelBuilder)
            {
                base.OnModelCreating(modelBuilder);
    
                // Configuring a many-to-many special -> topping relationship that is friendly for serialization
                modelBuilder.Entity<PizzaTopping>().HasKey(pst => new { pst.PizzaId, pst.ToppingId });
                modelBuilder.Entity<PizzaTopping>().HasOne<Pizza>().WithMany(ps => ps.Toppings);
                modelBuilder.Entity<PizzaTopping>().HasOne(pst => pst.Topping).WithMany();
            }
    
      }
    

    此程式碼會新增應用程式訂單與披薩類別的 Entity Framework 支援。

  3. 在 Visual Studio Code 的功能表中,依序選取 [檔案]> [新增文字檔]

  4. 選取 C# 語言,然後輸入下列程式碼:

    using Microsoft.AspNetCore.Mvc;
    using Microsoft.EntityFrameworkCore;
    
    namespace BlazingPizza;
    
    [Route("orders")]
    [ApiController]
    public class OrdersController : Controller
    {
        private readonly PizzaStoreContext _db;
    
        public OrdersController(PizzaStoreContext db)
        {
            _db = db;
        }
    
        [HttpGet]
        public async Task<ActionResult<List<OrderWithStatus>>> GetOrders()
        {
            var orders = await _db.Orders
     	    .Include(o => o.Pizzas).ThenInclude(p => p.Special)
     	    .Include(o => o.Pizzas).ThenInclude(p => p.Toppings).ThenInclude(t => t.Topping)
     	    .OrderByDescending(o => o.CreatedTime)
     	    .ToListAsync();
    
            return orders.Select(o => OrderWithStatus.FromOrder(o)).ToList();
        }
    
        [HttpPost]
        public async Task<ActionResult<int>> PlaceOrder(Order order)
        {
            order.CreatedTime = DateTime.Now;
    
            // Enforce existence of Pizza.SpecialId and Topping.ToppingId
            // in the database - prevent the submitter from making up
            // new specials and toppings
            foreach (var pizza in order.Pizzas)
            {
                pizza.SpecialId = pizza.Special.Id;
                pizza.Special = null;
            }
    
            _db.Orders.Attach(order);
            await _db.SaveChangesAsync();
    
            return order.OrderId;
        }
    }
    

    上述程式碼可讓應用程式取得目前的所有訂單,並且下單。 [Route("orders")] Blazor 屬性可讓此類別處理 /orders/orders/{orderId} 的傳入 HTTP 要求。

  5. 使用 Ctrl+S 儲存變更。

  6. 在檔案名稱部分,請使用 OrderController.cs。 請務必將檔案儲存在與 OrderState.cs 相同的目錄中。

  7. 在檔案總管中,選取 [OrderState.cs]

  8. RemoveConfiguredPizza 方法下方的類別底部,修改 ResetOrder() 以重設訂單:

    public void ResetOrder()
    {
        Order = new Order();
    }
    

測試結帳功能

  1. 在 Visual Studio Code 中,選取 F5。 或在 [執行] 功能表上,選取 [開始偵錯]

    應用程式應該會進行編譯,但如果您建立訂單並嘗試結帳,您會看到執行階段錯誤。 發生錯誤的原因是,我們的 pizza.db SQLLite 資料庫是在支援訂單與披薩之前所建立。 我們必須先刪除檔案,才能正確建立新的資料庫。

  2. 選取 Shift + F5 以停止應用程式。

  3. 在檔案總管中,刪除 pizza.db 檔案。

  4. 選取 F5。 或在 [執行] 功能表上,選取 [開始偵錯]

    作為測試,請新增披薩、移至結帳,然後下單。 您將會重新導向至首頁,並看到現在的訂單為空白。

  5. 選取 Shift + F5 以停止應用程式。

應用程式正在不斷改善。 我們有披薩設定與結帳。 我們想要讓客戶在下單後看到其披薩訂單的狀態。

新增訂單頁面

  1. 在檔案總管中,展開 [頁面],以滑鼠右鍵按一下資料夾,然後選取 [新增檔案]

  2. 將新檔案命名為 MyOrders.razor。 在此檔案中,寫入下列程式碼:

    @page "/myorders"
    @inject HttpClient HttpClient
    @inject NavigationManager NavigationManager
    
    <div class="top-bar">
        <a class="logo" href="">
            <img src="img/logo.svg" />
        </a>
    
        <a href="" class="nav-tab">
            <img src="img/pizza-slice.svg" />
            <div>Get Pizza</div>
        </a>
    
        <a href="myorders" class="nav-tab active">
            <img src="img/bike.svg" />
            <div>My Orders</div>
        </a>
    </div>
    
    <div class="main">
        @if (ordersWithStatus == null)
        {
            <text>Loading...</text>
        }
        else if (!ordersWithStatus.Any())
        {
            <h2>No orders placed</h2>
            <a class="btn btn-success" href="">Order some pizza</a>
        }
        else
        {
            <div class="list-group orders-list">
                @foreach (var item in ordersWithStatus)
                {
                    <div class="list-group-item">
                        <div class="col">
                            <h5>@item.Order.CreatedTime.ToLongDateString()</h5>
                            Items:
                            <strong>@item.Order.Pizzas.Count()</strong>;
                            Total price:
                            <strong>£@item.Order.GetFormattedTotalPrice()</strong>
                        </div>
                        <div class="col">
                            Status: <strong>@item.StatusText</strong>
                        </div>
                        @if (@item.StatusText != "Delivered")
                        {
                            <div class="col flex-grow-0">
                                <a href="myorders/" class="btn btn-success">
                                    Track &gt;
                                </a>
                            </div>
                        }
                    </div>
                }
            </div>
        }
    </div>
    
    @code {
        List<OrderWithStatus> ordersWithStatus = new List<OrderWithStatus>();
    
        protected override async Task OnParametersSetAsync()
        {
          ordersWithStatus = await HttpClient.GetFromJsonAsync<List<OrderWithStatus>>(
              $"{NavigationManager.BaseUri}orders");
        }
    }
    

    必須在所有現有的頁面上變更導覽,以包括 [我的訂單] 新頁面的連結。 開啟 Checkout.razorIndex.razor,然後以下列程式碼取代導覽:

    <div class="top-bar">
        <a class="logo" href="">
            <img src="img/logo.svg" />
        </a>
    
        <a href="" class="nav-tab active" >
            <img src="img/pizza-slice.svg" />
            <div>Get Pizza</div>
        </a>
    
        <a href="myorders" class="nav-tab" >
            <img src="img/bike.svg" />
            <div>My orders</div>
        </a>
    
    </div>
    

    使用 <a> 元素,我們可以藉由新增 active CSS 類別,以手動方式管理哪一個是作用中頁面。 讓我們更新所有導覽以改用 NavLink 元件。

  3. 在導覽 (Index.razorCheckout.razorMyOrders.razor) 的這三個頁面上,使用相同的 Blazor 程式碼進行瀏覽:

    <div class="top-bar">
        <a class="logo" href="">
            <img src="img/logo.svg" />
        </a>
    
        <NavLink href="" class="nav-tab" Match="NavLinkMatch.All">
            <img src="img/pizza-slice.svg" />
            <div>Get Pizza</div>
        </NavLink>
    
        <NavLink href="myorders" class="nav-tab">
            <img src="img/bike.svg" />
            <div>My Orders</div>
        </NavLink>
    </div>
    

    active CSS 類別現在已由 NavLink 元件自動新增至頁面。 您不需要記住要在導覽所在的每個頁面上執行此動作。

  4. 最後一個步驟是變更 NavigationManager,以在下單之後重新導向至 [我的訂單] 頁面。 在檔案總管中展開 [頁面],然後選取 [Checkout.razor]

  5. 透過將 /myorders 傳遞至 NavigationManager.NavigateTo(),以變更 PlaceOrder 方法來重新導向至正確的頁面:

    async Task PlaceOrder()
    {
        isSubmitting = true;
        var response = await HttpClient.PostAsJsonAsync($"{NavigationManager.BaseUri}orders", OrderState.Order);
        var newOrderId = await response.Content.ReadFromJsonAsync<int>();
        OrderState.ResetOrder();
        NavigationManager.NavigateTo("/myorders");
    } 
    
  6. 在 Visual Studio Code 中,選取 F5。 或在 [執行] 功能表上,選取 [開始偵錯]

    顯示訂單頁面的螢幕擷取畫面。

    您應該能夠訂購一些披薩,然後查看目前資料庫中的訂單。

  7. 選取 Shift + F5 以停止應用程式。