练习 - 使用 @page 指令更改 Blazor 应用中的导航

已完成

Blazor 具有导航状态帮助程序,可帮助 C# 代码管理应用的 URI。 还有一个恰好用于替换 <a> 元素的 NavLink 组件。 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 具有实体框架数据库支持。 让我们首先解决此问题。

为订单和披萨添加实体框架支持

  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();
            }
    
      }
    

    此代码将为应用的 order 和 pizza 类添加实体框架支持。

  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.razor”和“Index.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.razor、Checkout.razor 和 MyOrders.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,以便在下订单后重定向到 myorders 页。 在文件资源管理器中,展开“页面”,然后选择“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 以停止应用。