Exercício – Alterar a navegação no aplicativo Blazor usando a diretiva @page

Concluído

O Blazor tem um auxiliar de estado de navegação que ajuda o código C# a gerenciar URIs de um aplicativo. Há também um componente NavLink que é uma substituição drop-in do elemento <a>. Um dos recursos do NavLink é adicionar uma classe ativa a links HTML para os menus de um aplicativo.

Sua equipe começou a trabalhar no aplicativo Blazing Pizza e criou componentes Blazor para representar pizzas e pedidos. Agora o aplicativo precisa adicionar páginas de checkout e outras páginas relacionadas a pedidos.

Nesse exercício, você adiciona uma nova página de checkout, adiciona uma navegação superior ao aplicativo e, em seguida, usa um componente Blazor NavLink para melhorar seu código.

Clonar o aplicativo existente de sua equipe

Observação

Este módulo usa a CLI (interface de linha de comando) do .NET e o Visual Studio Code para o desenvolvimento local. Depois de concluir este módulo, você poderá aplicar os conceitos usando o Visual Studio (Windows) ou o Visual Studio para Mac (macOS). Para o desenvolvimento contínuo, use o Visual Studio Code para Windows, Linux e macOS.

Este módulo usa o SDK do .NET 6.0. Verifique se tem o .NET 6.0 instalado, executando o seguinte comando em seu terminal preferido:

dotnet --list-sdks

Saída semelhante à seguinte exibida:

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

Verifique se uma versão que começa com 6 está listada. Se nenhum estiver listado ou o comando não for encontrado, instale o SDK do .NET 6.0 mais recente.

Se você ainda não criou um aplicativo Blazor, siga as instruções de instalação do Blazor para instalar a versão correta do .NET e verificar se o computador está configurado corretamente. Pare na etapa Criar seu aplicativo.

  1. Abra o Visual Studio Code.

  2. Abra o terminal integrado no Visual Studio Code selecionando Exibir. Em seguida, no menu principal, escolha Terminal.

  3. No terminal, acesse o local em que deseja criar o projeto.

  4. Clone o aplicativo do GitHub.

    git clone https://github.com/MicrosoftDocs/mslearn-blazor-navigation.git BlazingPizza
    
  5. Selecione Arquivo e Abrir pasta.

  6. Na caixa de diálogo Abrir, acesse a pasta BlazingPizza e escolha Selecionar Pasta.

    O Visual Studio Code pode solicitar as dependências não resolvidas. Selecione Restaurar.

  7. Execute o aplicativo para verificar se tudo está funcionando corretamente.

  8. No Visual Studio Code, selecione F5. Ou, então, no menu Executar, selecione Iniciar Depuração.

    Captura de tela que mostra a versão clonada do aplicativo Blazing Pizza.

    Configure algumas pizzas e adicione-as ao seu pedido. Selecione Pedido > na parte inferior da página. A mensagem padrão "404 não encontrado" aparece porque ainda não há uma página de checkout.

  9. Para parar o aplicativo, selecione Shift + F5.

Adicionar uma página de check-out

  1. No Visual Studio Code, no gerenciador de arquivos, selecione 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>
    

    O bloco de código <NotFound> é o que os clientes veem se tentarem acessar uma página que não existe.

  2. No explorador de arquivos, expanda Páginas, clique com o botão direito do mouse na pasta e escolha Novo Arquivo.

  3. Dê ao novo arquivo o nome Checkout.razor. Nesse arquivo, escreva o seguinte código:

    @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;
    }
    

    Essa página é criada no aplicativo atual e usa o estado do aplicativo salvo em OrderState. O primeiro div é a nova navegação de cabeçalho do aplicativo. Vamos adicioná-lo à página de índice.

  4. No explorador de arquivos, expanda Páginas e selecione index.razor.

  5. Acima da classe <div class="main">, adicione o HTML top-bar.

    <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>
    

    Quando estivermos nessa página, será interessante mostrar isso aos clientes realçando o link. A equipe já criou uma classe active css, então adicione active ao atributo class que já contém o estilo 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>
    
  6. No Visual Studio Code, selecione F5. Ou, então, no menu Executar, selecione Iniciar Depuração.

    O aplicativo agora tem uma barra de menus interessante na parte superior, que inclui o logotipo da empresa. Adicione algumas pizzas e avance o pedido até a página de check-out. Você vê as pizzas listadas e o indicador ativo faltando no menu.

    Captura de tela que mostra a página de check-out com algumas pizzas.

  7. Para parar o aplicativo, selecione Shift + F5.

Permitir que os clientes façam um pedido

No momento, a página de check-out não permite que os clientes façam pedidos. A lógica do aplicativo precisa armazenar o pedido a fim de enviá-lo para a cozinha. Depois que o pedido é enviado, vamos redirecionar os clientes novamente para a home page.

  1. No explorador de arquivos, expanda Páginas e selecione Checkout.razor.

  2. Modifique o elemento do botão com uma chamada ao método PlaceOrder. Adicione os atributos @onclick e disabled, conforme mostrado abaixo:

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

    Para que os clientes não façam pedidos duplicados, o botão Fazer um pedido foi desabilitado até a conclusão do processamento do pedido.

  3. No bloco @code, adicione este código abaixo do código 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("/");
    }
    

    O código anterior desabilita o botão Fazer pedido, publica JSON e o adiciona a pizza.db, limpa o pedido e usa NavigationManager para redirecionar os clientes para a página inicial.

    Você precisa adicionar o código para tratar da ordem. Adicione uma classe OrderController para essa tarefa. Se você olhar para PizzaStoreContext.cs, verá apenas suporte ao banco de dados do entity framework para PizzaSpecials. Vamos corrigir isso primeiro.

Adicionar suporte do Entity Framework a pedidos e pizzas

  1. No explorador de arquivos, selecione PizzaStoreContext.cs.

  2. Substitua a classe PizzaStoreContext por este código:

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

    Esse código adiciona suporte do Entity Framework às classes order e pizza do aplicativo.

  3. No menu do Visual Studio Code, escolha Arquivo>Novo arquivo de texto.

  4. Selecione a linguagem C# e insira este código:

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

    O código anterior permite que nosso aplicativo obtenha todos os pedidos atuais e faça um pedido. O atributo [Route("orders")] do Blazor permite que essa classe manipule solicitações HTTP de entrada para /orders e /orders/{orderId}.

  5. Salve as alterações com Ctrl+S.

  6. Como nome de arquivo, use OrderController.cs. Salve o arquivo no mesmo diretório como OrderState.cs.

  7. No explorador de arquivos, selecione OrderState.cs.

  8. Na parte inferior da classe no método RemoveConfiguredPizza, modifique ResetOrder() para redefinir a ordem:

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

Testar a funcionalidade de check-out

  1. No Visual Studio Code, selecione F5. Ou, então, no menu Executar, selecione Iniciar Depuração.

    O aplicativo deve compilar, mas se você criar um pedido e tentar finalizar a compra, verá um erro de tempo de execução. O erro ocorre porque o banco de dados SQLLite pizza.db foi criado antes do suporte para pedidos e pizzas. Precisamos excluir o arquivo para que um banco de dados possa ser criado corretamente.

  2. Para parar o aplicativo, selecione Shift + F5.

  3. No explorador de arquivos, exclua o arquivo pizza.db.

  4. Selecione F5. Ou, então, no menu Executar, selecione Iniciar Depuração.

    Como um teste, adicione pizzas, vá para o check-out e faça um pedido. Você será redirecionado para a página inicial e poderá ver que o pedido agora está vazio.

  5. Para parar o aplicativo, selecione Shift + F5.

O aplicativo está melhorando. Temos a configuração de uma pizza e um check-out. Queremos permitir que os clientes vejam o status do pedido de pizza após o pedido.

Adicionar uma página de pedidos

  1. No explorador de arquivos, expanda Páginas, clique com o botão direito do mouse na pasta e escolha Novo Arquivo.

  2. Dê ao novo arquivo o nome MyOrders.razor. Nesse arquivo, escreva o seguinte código:

    @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");
        }
    }
    

    A navegação precisa ser alterada em todas as páginas. Agora precisamos incluir um link para a nova página Meus pedidos. Abra Checkout.razor e Index.razor e substitua a navegação por este código:

    <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>
    

    Usando elementos <a>, precisamos gerenciar qual é a página ativa manualmente adicionando a classe CSS active. Vamos atualizar toda a navegação para usar um componente NavLink.

  3. Nas três páginas com navegação (Index.razor, Checkout.razor e MyOrders.razor), use o mesmo código do Blazor para a navegação:

    <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>
    

    A classe CSS active agora é adicionada automaticamente às páginas pelo componente NavLink. Você não precisa se lembrar de fazer isso em cada página em que há navegação.

  4. O último passo é alterar NavigationManager para redirecionar para a página myorders após um pedido ser feito. No explorador de arquivos, expanda Páginas e selecione Checkout.razor.

  5. Altere o método PlaceOrder a fim de redirecionar para a página correta transmitindo /myorders para 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");
    } 
    
  6. No Visual Studio Code, selecione F5. Ou, então, no menu Executar, selecione Iniciar Depuração.

    Captura de tela que mostra a página do pedido.

    Você deve conseguir pedir algumas pizzas e ver os pedidos atualmente no banco de dados.

  7. Pare o aplicativo selecionando Shift + F5.