연습 - @page 지시문을 사용하여 Blazor 앱에서의 탐색 변경
Blazor에는 C# 코드가 앱에서 URI를 관리하는 데 도움이 되는 탐색 상태 도우미가 있습니다.
요소의 드롭인 대체 항목으로 <a> 구성 요소도 존재합니다. NavLink의 기능 중 하나는 앱 메뉴에 대한 HTML 링크에 활성 클래스를 추가하는 것입니다.
팀에서 Blazing Pizza 앱을 시작했으며 피자 및 주문을 나타내는 Blazor 구성 요소를 빌드했습니다. 이제 앱에 체크 아웃 및 기타 주문 관련 페이지를 추가해야 합니다.
이 연습에서는 새 체크 아웃 페이지를 추가하고, 앱에 위쪽 탐색을 추가한 다음, Blazor NavLink 구성 요소를 사용하여 코드를 개선합니다.
팀의 기존 앱 복제
Note
이 모듈에서는 로컬 개발에 .NET CLI(명령줄 인터페이스) 및 Visual Studio Code를 사용합니다. 이 모듈을 완료한 후 Visual Studio(Windows) 또는 Mac용 Visual Studio(macOS)를 사용하여 개념을 적용할 수 있습니다. 지속적인 개발의 경우 Windows, Linux 및 macOS에 Visual Studio Code를 사용합니다.
이 모듈에서는 .NET 9.0 SDK를 사용합니다. 기본 설정 터미널에서 다음 명령을 실행하여 .NET 9.0이 설치되어 있는지 확인합니다.
dotnet --list-sdks
다음 예제와 유사한 출력이 표시됩니다.
8.0.100 [C:\Program Files\dotnet\sdk]
9.0.100 [C:\Program Files\dotnet\sdk]
9으로 시작하는 버전이 나열되어 있는지 확인합니다. 나열된 항목이 없거나 명령을 찾을 수 없는 경우 최신 .NET 9.0 SDK를 설치합니다.
첫 번째 Blazor 앱을 만드는 경우 Blazor에 대한 설정 지침에 따라 올바른 버전의 .NET을 설치하고 컴퓨터가 올바르게 설정되었는지 확인합니다. 앱 만들기 단계에서 중지합니다.
Visual Studio Code를 엽니다.
보기를 선택하여 Visual Studio Code에서 통합 터미널을 엽니다. 그런 다음, 주 메뉴에서 터미널을 선택합니다.
터미널에서 프로젝트를 만들려는 위치로 이동합니다.
GitHub에서 앱을 복제합니다.
git clone https://github.com/MicrosoftDocs/mslearn-blazor-navigation.git BlazingPizza파일을 선택한 다음 폴더 열기를 선택합니다.
열기 대화 상자에서 BlazingPizza 폴더로 이동하여 폴더 선택을 선택합니다.
Visual Studio Code에서 해결되지 않은 종속성에 대한 메시지가 표시될 수 있습니다. 복원을 선택합니다.
앱을 실행하여 모든 것이 제대로 작동하는지 확인합니다.
Visual Studio Code에서 F5를 선택합니다. 또는 실행 메뉴에서 디버깅 시작을 선택합니다.
일부 피자를 구성하고 주문에 추가합니다. 페이지 아래쪽에서 순서 > 를 선택합니다. 체크 아웃 페이지가 없으므로 "죄송합니다. 이 주소에는 아무것도 없습니다."라는 기본 메시지가 나타납니다.
앱을 중지하려면 Shift + F5를 선택합니다.
체크 아웃 페이지 추가
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>코드 블록은 고객이 존재하지 않는 페이지로 이동하려고 하면 표시되는 내용입니다.파일 탐색기에서 페이지를 확장하고 폴더를 마우스 오른쪽 단추로 클릭한 다음 새 파일을 선택합니다.
새 파일의 이름을 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는 앱의 새 머리글 탐색입니다. 인덱스 페이지에 추가해 보겠습니다.파일 탐색기에서 Pages를 확장한 다음 index.razor를 선택합니다.
<div class="main">클래스 위에top-barhtml을 추가합니다.<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>이 페이지에 있는 경우 링크를 강조 표시하여 고객을 표시하는 것이 좋습니다. 팀은 이미
activecss 클래스를 만들었으므로active스타일이 이미 포함된class특성에nav-tab를 추가합니다.<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>Visual Studio Code에서 F5를 선택합니다. 또는 실행 메뉴에서 디버깅 시작을 선택합니다.
이제 앱의 위쪽에 회사 로고를 포함한 멋진 메뉴 모음이 있습니다. 일부 피자를 추가하고 주문 단추를 선택하여 체크 아웃 페이지로 진행합니다. 피자가 나열되고 메뉴에 없는 활성 표시기가 표시됩니다.
앱을 중지하려면 Shift + F5를 선택합니다.
고객이 주문을 제출하도록 허용
현재 체크 아웃 페이지에서는 고객이 주문을 제출할 수 없습니다. 앱의 논리는 주방으로 보낼 주문을 저장해야 합니다. 주문이 전송된 후 고객을 다시 홈페이지로 리디렉션해 보겠습니다.
파일 탐색기에서 페이지를 확장하고 Checkout.razor를 선택합니다.
단추 요소를 수정하여
PlaceOrder메서드를 호출합니다. 다음과 같이@onclick특성 및disabled특성을 추가합니다.<button class="checkout-button btn btn-warning" @onclick="PlaceOrder" disabled=@isSubmitting> Place order </button>고객이 중복 주문을 하는 것을 원하지 않으므로 주문이 처리될 때까지 주문 배치 단추를 사용하지 않도록 설정합니다.
@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("/"); }위의 코드는 주문 배치 단추를 사용하지 않도록 설정하고, JSON을 게시하고, pizza.db 추가하고, 주문을 지우고, 고객을 홈페이지로 리디렉션하는 데 사용합니다
NavigationManager.주문을 처리하는 코드를 추가해야 합니다. 이 작업에 대한 OrderController 클래스를 추가합니다. PizzaStoreContext.cs 살펴보면 에 대한
PizzaSpecials엔터티 프레임워크 데이터베이스 지원만 표시됩니다. 먼저 이 문제를 해결해 보겠습니다.
주문 및 피자에 대한 엔터티 프레임워크 지원 추가
파일 탐색기에서 PizzaStoreContext.cs 선택합니다.
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(); } }이 코드는 앱의 주문 및 피자 클래스에 대한 엔터티 프레임워크 지원을 추가합니다.
Visual Studio Code의 메뉴에서 새 파일 파일을> 선택합니다.
파일 이름으로 OrderController.cs 입력합니다. 파일을 OrderState.cs 동일한 디렉터리에 저장해야 합니다.
다음 코드를 추가합니다.
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 요청을 처리할 수 있습니다.Ctrl+S를 사용하여 변경 내용을 저장합니다.
파일 탐색기에서 OrderState.cs 선택합니다.
클래스 맨 밑의
RemoveConfiguredPizza메서드 아래에ResetOrder()를 수정하여 주문을 다시 설정합니다.public void ResetOrder() { Order = new Order(); }
체크 아웃 기능 테스트
Visual Studio Code에서 F5를 선택합니다. 또는 실행 메뉴에서 디버깅 시작을 선택합니다.
앱이 컴파일되어야 하지만 주문을 만들고 체크 아웃을 시도하면 런타임 오류가 표시됩니다. 이 오류는 주문 및 피자에 대한 지원이 있기 전에 pizza.db SQLLite 데이터베이스가 만들어졌기 때문에 발생합니다. 새 데이터베이스를 올바르게 만들 수 있도록 파일을 삭제해야 합니다.
앱을 중지하려면 Shift + F5를 선택합니다.
파일 탐색기에서 pizza.db 파일을 삭제합니다.
F5 선택합니다. 또는 실행 메뉴에서 디버깅 시작을 선택합니다.
테스트로 피자를 추가하고 체크 아웃으로 이동하여 주문을 제출합니다. 홈페이지로 리디렉션되고 주문이 비어 있는 것을 볼 수 있습니다.
앱을 중지하려면 Shift + F5를 선택합니다.
앱이 개선되고 있습니다. 피자 구성 및 체크 아웃이 있습니다. 고객이 주문을 제출한 후 피자 주문 상태를 볼 수 있도록 하려고 합니다.
주문 페이지 추가
파일 탐색기에서 페이지를 확장하고 폴더를 마우스 오른쪽 단추로 클릭한 다음 새 파일을 선택합니다.
새 파일 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 > </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>요소를 사용하면activecss 클래스를 추가하여 수동으로 활성 상태가 되는 페이지를 관리할 수 있습니다. 대신 NavLink 구성 요소를 사용하도록 모든 탐색을 업데이트해 보겠습니다.탐색(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 구성 요소에 의해 페이지에 자동으로 추가됩니다. 탐색이 있는 각 페이지에서 이 작업을 수행할 필요가 없습니다.마지막 단계는 주문이 제출된 후
NavigationManager페이지로 리디렉션하도록myorders를 변경하는 것입니다. 파일 탐색기에서 페이지를 확장한 다음 Checkout.razor를 선택합니다.다음과 같이
PlaceOrder를/myorders로 전달하여NavigationManager.NavigateTo()메서드가 올바른 페이지로 리디렉션하도록 변경합니다.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"); }Visual Studio Code에서 F5를 선택합니다. 또는 실행 메뉴에서 디버깅 시작을 선택합니다.
피자를 주문한 다음 데이터베이스에서 현재 주문을 볼 수 있어야 합니다.
Shift + F5를 선택하여 앱을 중지합니다.