BlazingPizza: aplicativo Blazor do início ao fim. Parte 2. Adicione um componente

Olá pessoal! Para todos aqueles que querem aprender um pouco mais sobre Blazor. Hoje, continuaremos a criar nosso site para uma pizzaria, ou seja, criaremos um controlador de API da Web e tentaremos exibir os dados que vêm dele no componente Blazor.

Como nosso aplicativo é sobre pizza, seria lógico adicionar imediatamente uma classe representando nosso produto principal. Chame

-o de BasePizza e adicione-o ao projeto BlazingPizza.DomainModels . Na minha opinião, adicionar uma nova classe é muito legal implementado no Rider, uma caixa de diálogo sem bloqueio é exibida, insira o nome da classe e podemos escolher o que exatamente precisamos criar:



Depois disso, uma caixa de diálogo será exibida com uma solicitação para adicionar um arquivo ao git, responderemos afirmativamente.

Conteúdo da turma:

public class BasePizza
{
  public int Id { get; set; }
    
    public string Name { get; set; }
    
    public decimal BasePrice { get; set; }
    
    public string Description { get; set; }
    
    public string ImageUrl { get; set; }
}

É um modelo para algum tipo de pizza, depois pode ser configurado como gostamos, redimensionamos, adicionamos coberturas e muito mais. O nome dos campos parece-me falar por si.

No projeto BlazingPizza.DomainPizza , teremos classes representando o domínio comercial do nosso aplicativo. Ou seja, eles não devem e não saberão nada sobre como nossos dados são armazenados ou como são exibidos. Somente informações sobre o objeto de negócios, ou seja, pizza.

Em seguida, precisamos de algo para, de alguma forma, obter esses dados para o cliente. Para fazer isso, vá para o projeto BlazingPizza.Server e adicione o PizzasController à pasta Controllers :

public class PizzasController : Controller
{
    // GET
    public IActionResult Index()
    {
        return View();
    }
}

Precisamos de um método que nos forneça uma lista de todos os princípios básicos da pizza.

Além de adicionar um método, você precisa executar algumas etapas simples:

  1. Vamos marcar o controlador com o atributo [ApiController], que oferece algumas vantagens, em particular, o retorno automático do código 400 se o modelo não passou na validação, sem ele é um controlador MVC normal que fornece o View.
  2. [Route(«pizzas»)]. Attribute Routing, , Conventional Routing, . “pizzas” ? http{s}://hostName/pizzas/{}
    .
  3. Controller ControllerBase, MVC .

Ok, por exemplo, fizemos uma solicitação localhost : 5000 / pizzas na esperança de obter uma lista de todas as pizzas e nada aconteceu. Novamente, o acordo está nos acordos.

Se fosse uma solicitação Get , é necessário ter um método ( Action em termos de Asp.Net ) marcado com o atributo [HttpGet] ou, ainda mais óbvio, apenas um método chamado Get e é isso! Tudo o mais. Net e reflexão farão por nós.
E renomeie o único método Index para Get. Altere o tipo do valor de retorno para IEnumerable <BasePizza>, não esqueça de adicionar o uso necessário. Bem, insira temporariamente um stub que o método não está implementado para, de alguma forma, compilar o código e garantir que não haja erros.

Como resultado, o PizzasController.cs ficará assim:

using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using BlazingPizza.DomainModels;

namespace BlazingPizza.Server.Controllers
{
    [ApiController]
    [Route("pizzas")]
    public class PizzasController : ControllerBase
    {
        // GET
        public IEnumerable<BasePizza>  Get()
        {
            throw new NotImplementedException();
        }
    }
}

No momento, inicie o aplicativo de depuração, um botão com um bug verde.



e verifique se as rotas estão configuradas corretamente. A porta na qual você precisa fazer solicitações pode ser vista na guia Console :



no nosso caso, é 5000, se você fizer uma solicitação no caminho localhost : 5000 / pizzas, entraremos na ação Get e capturaremos uma NotImplementedException. Ou seja, enquanto nosso controlador não faz nada útil, ele simplesmente aceita solicitações e falha com um erro.

Retornamos dados do controlador


É hora de fazer com que nosso código faça algo útil, como devolver pizzas. Até o momento, não implementamos uma camada de dados, apenas devolvemos algumas pizzas de nossa ação . Para fazer isso, retorne uma matriz que consiste em dois objetos BasePizza . O método Get será semelhante ao exemplo abaixo:

// GET
public IEnumerable<BasePizza>  Get()
{
    return new[]
    {
        new BasePizza()
        {
            BasePrice = 500,
            Description = "     ",
            Id = 0,
            ImageUrl = "img/pizzas/pepperoni.jpg"
        },
        new BasePizza()
        {
            BasePrice = 400,
            Description = "   ",
            Id = 1,
            ImageUrl = "img/pizzas/meaty.jpg"
        },
    };
}

O resultado da solicitação no navegador será assim:



Configurar a página inicial


A parte visível do aplicativo está nos componentes .razor no projeto BlazingPizza.Client . Estamos interessados ​​no Index.razor na pasta Pages , abra-o e exclua todo o conteúdo que herdamos do projeto padrão. E vamos começar a adicionar o que realmente precisamos.

1. Adicione:página"/" Esta diretiva é usada para configurar o roteamento do cliente e diz que esse controle será carregado por padrão, ou seja, se for apenas para o endereço do aplicativo localhost : 5000 / sem nenhum / Index, / Pizzas ou qualquer outra coisa.

2)injetarHttpClient HttpClient Usando a diretivainjetar adicione um serviço como HttpClient à nossa página e chame o objeto HttpClient também . Um objeto do tipo HttpClientestá configurado para nós pela estrutura do Blazor, para que possamos fazer as solicitações necessárias. Esse tipo de injeção é chamado de Injeção de propriedades , uma implementação mais familiar por meio do construtor não é suportada e, como dizem os desenvolvedores, é improvável que isso apareça, mas é necessário aqui?

3. Adicione uma diretiva

 @code{

 }

É especificamente necessário para hospedar o código C # do cliente, o mesmo que substitui o JavaScript. Dentro deste bloco, colocamos uma coleção de objetos do tipo BasePizzaViewModel

IEnumerable<BasePizzaViewModel> PizzaViewModels;

4. Como você já entendeu, BasePizzaViewModel não existe, é hora de criá-lo, este modelo será completamente análogo ao modelo de domínio BasePizza , exceto que ele terá o corpo de expressão GetFormattedBasePrice que retorna o preço da pizza base no formato que precisamos. O modelo irá adicionar à raiz do projeto BlazingPizza.ViewModels arquivo BasePizzaViewModel.cs :

public class BasePizzaViewModel
{
    public int Id { get; set; }
    
    public string Name { get; set; }
    
    public decimal BasePrice { get; set; }
    
    public string Description { get; set; }
    
    public string ImageUrl { get; set; }
    
    public string GetFormattedBasePrice() => BasePrice.ToString("0.00");
}

5. Voltar ao nosso Index.razor e bloquearcódigo, adicione um código para obter todas as pizzas disponíveis. Vamos colocar esse código no método assíncrono OnInitializedAsync :

protected async override Task OnInitializedAsync() {
	
}

Este método é chamado depois que o componente é inicializado e, no momento da chamada, todos os seus parâmetros já foram inicializados pelo componente pai. Nele, você pode executar algumas operações assíncronas, após as quais é necessária uma atualização de estado. Mais tarde vou falar sobre isso em mais detalhes. O método é chamado apenas uma vez quando o componente é criado.

Por fim, adicione as pizzas que ficam dentro deste método:

var queryResult = await HttpClient.GetJsonAsync<IEnumerable<BasePizza>>("pizzas");

pizzas - um caminho relativo que é adicionado à base e já foi definido para nós pela Blazor . Como segue da assinatura do método, os dados são solicitados pela solicitação get e, em seguida, o cliente tenta serializá-los em IEnumerable <BasePizza> .

6. Como recebemos dados do tipo errado que queremos exibir no componente, precisamos obter objetos do tipo BasePizzaViewModel , Linq e seu método Select os usarão para converter objetos da coleção recebida em objetos do tipo que planejamos usar. Adicione o método OnInitializedAsync ao final :

PizzaViewModels = queryResult.Select(i => new BasePizzaViewModel()
{
    BasePrice = i.BasePrice,
    Description = i.Description,
    Id = i.Id,
    ImageUrl = i.ImageUrl,
    Name = i.Name
});

Mais tarde mostrarei como fazer sem escrever esse código de modelo, mas, por enquanto, vamos deixar como está. Parece que temos tudo o que precisamos e podemos continuar exibindo os dados recebidos.

7. Acima da diretiva code , adicione o código html , dentro do qual estarão as próprias pizzas:

<div class="main">
    <ul class="pizza-cards">

    </ul>
</div>

Como você pode ver, a lista ul com o nome da classe falante "cartões de pizza" está vazia até agora;

@foreach (var pizza in PizzaViewModels)
{
    <li style="background-image: url('@pizza.ImageUrl')">
        <div class="pizza-info">
            <span class="title">@pizza.Name</span>
                @pizza.Description
            <span class="price">@pizza.GetFormattedBasePrice()</span>                    
        </div>
    </li>
}

Toda a diversão aqui está dentro do circuito. para cada(var {item} in {items})
Essa é uma marcação típica do Razor que nos permite usar o poder do C # na mesma página do código html comum . O principal é colocar o símbolo "@" na frente das palavras-chave e variáveis ​​do idioma .

Dentro do loop, simplesmente acessamos as propriedades do objeto de pizza .

No final, exibimos o preço base formatado da pizza usando o método GetFormattedBasePrice . A propósito, essa é a diferença entre o modelo de domínio BasePizza e seu ViewModel representações, pois esse método contém a lógica mais simples para exibir o preço no formato exigido, o que não precisamos no nível de serviço, onde de alguma forma manipulamos o preço, mas não o mostramos em nenhum lugar.

Exibimos os dados recebidos no navegador


Temos todos os dados necessários para exibir. É hora de iniciar nosso aplicativo e garantir que tudo funcione. Clicamos no botão Debug (no Rider, o botão Run apenas inicia o aplicativo sem o recurso Debug ).

E ooh-ho, nada funciona também :) Abra o console ( F12 ) e veja que está tudo vermelho, algo obviamente deu errado. O Blazor não é tão inútil na depuração e toda a pilha de chamadas pode ser vista no console , e na minha opinião isso é feito ainda melhor do que no mesmo angular . Não há necessidade de adivinhar por sinais indiretos onde ocorreu o erro, basta olhar para a pilha de chamadas:



Ocorreu uma mensagem NullReferenceException ao renderizar a página . Como isso pôde acontecer, porque inicializamos a única coleção que usamos no método OnInitializedAsync .

Para entender um pouco melhor, insira a saída de tempo nos lugares certos para ver o prazo do que aconteceu:

  1. Console.WriteLine($"Time from markup block: {DateTime.Now.ToString()}:{DateTime.Now.Millisecond.ToString()}");
  2. Console.WriteLine($"Time from cycle: {DateTime.Now.ToString()}:{DateTime.Now.Millisecond.ToString()}");
  3. Console.WriteLine($"Time from code block, before await: {DateTime.Now.ToString()}:{DateTime.Now.Millisecond.ToString()}");
  4. Console.WriteLine($"Time from code block, after await: {DateTime.Now.ToString()}:{DateTime.Now.Millisecond.ToString()}"); 



Na captura de tela abaixo, no console, o que aconteceu no momento da renderização da página. Pode-se observar que a página começa a renderizar mesmo antes da conclusão dos métodos assíncronos.
Na primeira passagem, o PizzaViewModels ainda não havia sido inicializado e capturamos uma NullReferenceException . Em seguida, conforme o esperado após a tarefa retornar o método OnInitializedAsync ao status RanToCompletion , o controle foi reprojetado. O que é digno de nota, durante a segunda passagem, entramos em um ciclo, como pode ser visto nas mensagens no console. Mas, neste momento, a interface do usuário não está mais sendo atualizada e não vemos nenhuma alteração visível.



De fato, o problema é muito fácil de resolver, você só precisa executar uma verificação nula de forma assíncrona antes de executar o loop ; uma exceção não ocorrerá pela primeira vez e, durante a segunda passagem, veremos os dados necessários.
@if (PizzaViewModels != null)
{
    @foreach (var pizza in PizzaViewModels)
    {
        ……………………….. //    
    }
}


Parece um pouco melhor agora, não há mais mensagens de erro no console e você pode ver as informações que chegaram do servidor:



É muito melhor, mas não há estilos e recursos suficientes, em particular fotos, substituindo o conteúdo da pasta wwwroot pelo conteúdo da pasta "~ / Articles / Part2 /BlazingPizza.Client/wwwroot "(link no final do artigo) e execute o projeto novamente, muito melhor. Embora ainda esteja longe do ideal:



Eventos de vida útil dos componentes


Como já conhecemos um dos eventos de vida útil do componente OnInitializedAsync, seria lógico mencionar os outros:
Métodos de inicialização
Inicializado
Chamado quando o componente já está inicializado e seus parâmetros já estão definidos pelo componente pai. Durante a vida de um componente, ele é chamado uma vez após sua inicialização.
OnInitializedAsync
Versão assíncrona do primeiro método, após a execução, o componente é renderizado novamente. Portanto, ao escrever o código, é necessário considerar que alguns objetos podem ser nulos.
Método executável antes de definir os valores dos parâmetros
SetParametersAsync
, . ParameterView .

[Parameter] [CascadingParameter] ParameterView. , . , )

OnParametersSet
. ,

— .
— , .
OnParametersSetAsyncOnParametersSet
OnAfterRender. . JavaScript DOM . bool firstRender true .
OnAfterRenderAsync
, Task , - .

ShouldRender
UI, - . . .
StateHasChanged
, Blazor .
Dispose
, UI. StateHasChanged Dispose . Dispose IDisposable, @implements IDisposable


Nesta parte, aprendemos como receber dados do controlador e exibi-los para o usuário.
Na próxima parte, organizaremos o Layout e adicionaremos uma camada de acesso a dados para exibir dados reais na página principal.

Link para o repositório desta série de artigos.
Link para a fonte original.

Source: https://habr.com/ru/post/undefined/


All Articles