연습 - @page 지시문을 사용하여 Blazor 앱에서의 탐색 변경
Blazor에는 C# 코드가 앱의 URI를 관리하는 데 도움이 되는 탐색 상태 도우미가 있습니다. <a>
요소를 드롭인으로 대체하는 NavLink 구성 요소도 있습니다. NavLink의 기능 중 하나는 앱 메뉴에 대한 HTML 링크에 활성 클래스를 추가하는 것입니다.
팀에서 Blazing Pizza 앱을 시작했으며 피자 및 주문을 나타내는 Blazor 구성 요소를 빌드했습니다. 이제 앱에 체크 아웃 및 기타 주문 관련 페이지를 추가해야 합니다.
이 연습에서는 새 체크 아웃 페이지를 추가하고, 앱에 위쪽 탐색을 추가한 다음, Blazor NavLink 구성 요소를 사용하여 코드를 개선합니다.
팀의 기존 앱 복제
참고
이 모듈에서는 로컬 개발에 .NET CLI(명령줄 인터페이스) 및 Visual Studio Code를 사용합니다. 이 모듈을 완료한 후 Visual Studio(Windows) 또는 Mac용 Visual Studio(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을 설치하고 머신이 올바르게 설정되었는지 확인합니다. 앱 만들기 단계에서 중지합니다.
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 키를 선택합니다. 또는 실행 메뉴에서 디버깅 시작을 선택합니다.
일부 피자를 구성하고 주문에 추가합니다. 페이지 하단에서 주문 >을 선택합니다. 체크 아웃 페이지가 없으므로 기본 "404를 찾을 수 없음" 메시지가 나타납니다.
앱을 중지하려면 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
는 앱의 새 머리글 탐색입니다. 인덱스 페이지에 추가해 보겠습니다.파일 탐색기에서 페이지를 확장한 다음, index.razor를 선택합니다.
<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 클래스를 만들었으므로nav-tab
스타일이 이미 포함된class
특성에active
를 추가합니다.<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의 메뉴에서 파일>새 텍스트 파일을 선택합니다.
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 요청을 처리할 수 있습니다.Ctrl+S를 사용하여 변경 내용을 저장합니다.
파일 이름에 OrderController.cs를사용합니다. 파일을 OrderState.cs와 동일한 디렉터리에 저장해야 합니다.
파일 탐색기에서 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>
요소를 사용하면active
css 클래스를 추가하여 수동으로 활성 상태가 되는 페이지를 관리할 수 있습니다. NavLink 구성 요소를 대신 사용하도록 모든 탐색을 업데이트해 보겠습니다.탐색이 있는 세 페이지(Index.razorCheckout.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를 선택합니다.다음과 같이
/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"); }
Visual Studio Code에서 F5 키를 선택합니다. 또는 실행 메뉴에서 디버깅 시작을 선택합니다.
피자를 주문한 다음 데이터베이스에서 현재 주문을 볼 수 있어야 합니다.
Shift + F5를 선택하여 앱을 중지합니다.