BlazingPizza: aplicación Blazor de principio a fin. Parte 2. Agregar un componente

¡Hola todos! Para todos aquellos que quieran aprender un poco más sobre Blazor. Hoy continuaremos creando nuestro sitio para una pizzería, es decir, crearemos un controlador de API web e intentaremos mostrar los datos que provienen de él en el componente Blazor.

Como nuestra aplicación es sobre pizza, sería lógico agregar inmediatamente una clase que represente nuestro producto principal. Llame a

su BasePizza y añadirlo a la BlazingPizza.DomainModels proyecto . En mi opinión, agregar una nueva clase es muy bueno implementado en Rider, aparece un cuadro de diálogo sin bloqueo, ingresamos el nombre de la clase y luego podemos elegir lo que necesitamos crear:



Después de eso, aparecerá un cuadro de diálogo que le pedirá que agregue el archivo a git, le responderemos afirmativamente.

Contenido de clase:

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

Es una plantilla para algún tipo de pizza, luego se puede configurar a nuestro gusto, cambiar el tamaño, agregar ingredientes y más. El nombre de los campos me parece hablar por sí mismo.

En el proyecto BlazingPizza.DomainPizza , tendremos clases que representan el dominio comercial de nuestra aplicación. Es decir, no deben ni sabrán nada sobre cómo se almacenan nuestros datos o cómo se muestran. Solo información sobre el objeto comercial, es decir, pizza.

A continuación, necesitamos algo para llevar estos datos de alguna manera al cliente. Para hacer esto, vaya al proyecto BlazingPizza.Server y agregue el PizzasController a la carpeta Controllers :

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

Necesitamos un método que nos proporcione una lista de todos los conceptos básicos para la pizza.

Además de agregar un método, debe realizar algunos pasos simples:

  1. Marquemos el controlador con el atributo [ApiController] que ofrece algunas ventajas, en particular, el retorno automático del código 400 si el modelo no pasó la validación, sin él es un controlador MVC normal que le da a View.
  2. [Route(«pizzas»)]. Attribute Routing, , Conventional Routing, . “pizzas” ? http{s}://hostName/pizzas/{}
    .
  3. Controller ControllerBase, MVC .

Ok, por ejemplo, hicimos una solicitud localhost : 5000 / pizzas con la esperanza de obtener una lista de todas las pizzas y no pasó nada. Nuevamente, el acuerdo está en los acuerdos.

Si se trataba de una solicitud Get , entonces tenemos que tener un método ( Acción en términos de Asp.Net ) marcado con el atributo [HttpGet] , o, lo que es más obvio, ¡solo un método llamado Get y eso es todo! Todo lo demás .Net y la reflexión harán por nosotros.
Y cambie el nombre del único método de índice para obtener. Cambie el tipo del valor de retorno a IEnumerable <BasePizza>, no olvide agregar el uso necesario. Bueno, inserte temporalmente un código auxiliar que el método no está implementado para compilar de alguna manera el código y asegúrese de que no haya errores.

Como resultado, PizzasController.cs se verá así:

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

En este momento, inicie la aplicación de depuración, un botón con un error verde.



y asegúrese de que las rutas estén configuradas correctamente. El puerto en el que necesita realizar solicitudes se puede ver en la pestaña Consola :



en nuestro caso, es 5000, si realiza una solicitud a lo largo de la ruta localhost : 5000 / pizzas, entramos en la acción Obtener y capturamos una excepción NotImplementedException. Es decir, aunque nuestro controlador no hace nada útil, simplemente acepta solicitudes y falla con un error.

Devolvemos datos del controlador


Es hora de que nuestro código haga algo útil, como devolver pizzas. Hasta ahora, no hemos implementado una capa de datos, por lo que solo devolvemos un par de pizzas de nuestra acción . Para hacer esto, devuelva una matriz que consta de dos objetos BasePizza . El método Get se verá como el siguiente ejemplo:

// 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"
        },
    };
}

El resultado de la solicitud en el navegador será así:



Configurar la página de inicio


La parte visible de la aplicación está en los componentes .razor del proyecto BlazingPizza.Client . Estamos interesados ​​en Index.razor en la carpeta Páginas , ábralo y elimine todo el contenido que heredamos del proyecto predeterminado. Y comencemos a agregar lo que realmente necesitamos.

1. Añadir:página"/" Esta directiva se usa para configurar el enrutamiento del cliente y dice que este control se cargará de forma predeterminada, es decir, si solo vamos a la dirección de la aplicación localhost : 5000 / sin / Index, / Pizzas u otra cosa.

2)inyectarHttpClient HttpClient Usando la Directivainyectar agregue un servicio como HttpClient a nuestra página y llame al objeto HttpClient también . Blazor ya configuró un objeto de tipo HttpClient para nosotros, por lo que podemos hacer las solicitudes que necesitamos. Este tipo de inyección se llama inyección de propiedad , no se admite una implementación más familiar a través del constructor, y como dicen los desarrolladores, es poco probable que aparezca alguna vez, pero ¿se necesita aquí?

3. Agregar una directiva

 @code{

 }

Es específicamente necesario para alojar el código C # del cliente, el mismo que reemplaza a JavaScript. Dentro de este bloque colocamos una colección de objetos de tipo BasePizzaViewModel

IEnumerable<BasePizzaViewModel> PizzaViewModels;

4. Como ya entendió, BasePizzaViewModel no existe, es hora de crearlo, este modelo será completamente análogo al modelo de dominio BasePizza , excepto que tendrá el cuerpo de expresión GetFormattedBasePrice que devuelve el precio de la pizza base en el formato que necesitamos. El modelo se agregará a la raíz del proyecto BlazingPizza.ViewModels archivo 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. Volver a nuestro Index.razor y bloquearcódigo, agregue un código para obtener todas las pizzas disponibles. Colocaremos este código en el método asíncrono OnInitializedAsync :

protected async override Task OnInitializedAsync() {
	
}

Este método se llama después de que el componente se inicializa y en el momento de la llamada, todos sus parámetros ya están inicializados por el componente principal. En él, puede realizar algunas operaciones asincrónicas, después de lo cual se requiere una actualización de estado. Más adelante hablaré sobre esto con más detalle. El método se llama solo una vez cuando se crea el componente.

Finalmente, agregue las pizzas para obtener dentro de este método:

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

pizzas : una ruta relativa que se agrega a la base y que Blazor ya nos ha establecido . Como se deduce de la firma del método, la solicitud de obtención solicita los datos y luego el cliente intenta serializarlos en IEnumerable <BasePizza> .

6. Dado que recibimos datos del tipo incorrecto que queremos mostrar en el componente, necesitamos obtener objetos del tipo BasePizzaViewModel. Para esto, Linq usará su método Select para convertir objetos de la colección entrante en objetos del tipo que planeamos usar. Agregue el método OnInitializedAsync al final :

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

Más adelante mostraré cómo hacerlo sin escribir este código de plantilla, pero por ahora, dejémoslo como está. Parece que tenemos todo lo que necesitamos y podemos proceder a mostrar los datos recibidos.

7. Encima de la directiva del código , agregue el código html , dentro del cual estarán las pizzas mismas:

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

    </ul>
</div>

Como puede ver, la lista ul con el nombre de la clase parlante "tarjetas de pizza" está vacía hasta ahora, solucionaremos este descuido:

@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 la diversión aquí está dentro del circuito. para cada(var {item} en {items})
Este es un marcado típico de Razor que nos permite usar el poder de C # en la misma página que el código html normal . Lo principal es poner el símbolo "@" delante de las palabras clave y variables del lenguaje .

Dentro del bucle, simplemente accedemos a las propiedades del objeto pizza .

Al final, mostramos el precio base formateado de la pizza usando el método GetFormattedBasePrice . Esto, por cierto, es la diferencia entre el modelo de dominio BasePizza y su ViewModel representaciones, ya que este método contiene la lógica más simple para mostrar el precio en el formato requerido, que no necesitamos en el nivel de servicio, donde de alguna manera manipulamos el precio, pero no lo mostramos en ningún lado.

Mostramos los datos recibidos en el navegador.


Tenemos todos los datos necesarios para mostrar. Es hora de lanzar nuestra aplicación y asegurarnos de que todo funcione. Hacemos clic en el botón Depurar (en Rider, el botón Ejecutar solo inicia la aplicación sin capacidad de depuración ).

Y ooh-ho, nada funciona tampoco :) Abra la consola ( F12 ) y vea que está todo rojo, algo obviamente salió mal. Blazor no es tan desesperado en la depuración y toda la pila de llamadas se puede ver en la Consola , y en mi opinión esto se hace aún mejor que en el mismo Angular . No es necesario adivinar por signos indirectos dónde ocurrió el error, solo mire la pila de llamadas:



Se produjo un mensaje NullReferenceException al representar la página . ¿Cómo podría suceder esto? Porque inicializamos la única colección que usamos en el método OnInitializedAsync .

Para comprender un poco mejor, inserte la salida de tiempo en los lugares correctos para ver el marco temporal de lo que sucedió:

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



En la captura de pantalla a continuación, en la consola, lo que sucedió en el momento de la representación de la página. Se puede ver que la página comienza a renderizarse incluso antes de la finalización de los métodos asincrónicos.
En el primer paso, PizzaViewModels aún no se había inicializado y detectamos una NullReferenceException . Luego, como se esperaba después de que la Tarea devolvió el método OnInitializedAsync al estado RanToCompletion , el control fue rediseñado. Lo que es notable, durante la segunda pasada, entramos en un ciclo, como se puede ver en los mensajes en la consola. Pero en este punto, la interfaz de usuario ya no se actualiza y no vemos ningún cambio visible.



De hecho, el problema es muy fácil de resolver, solo necesita ejecutar una verificación nula de forma asincrónica antes de ejecutar el ciclo , luego no ocurrirá una excepción por primera vez y durante la segunda pasada veremos los datos que necesitamos.
@if (PizzaViewModels != null)
{
    @foreach (var pizza in PizzaViewModels)
    {
        ……………………….. //    
    }
}


Parece un poco mejor ahora, no hay más mensajes de error en la consola y puede ver la información que nos llegó del servidor:



es mucho mejor, pero no hay suficientes estilos y recursos, en particular imágenes, reemplace el contenido de la carpeta wwwroot con el contenido de la carpeta "~ / Artículos / Parte2 /BlazingPizza.Client/wwwroot "repositorio (enlace al final del artículo) y ejecute el proyecto nuevamente, mucho mejor. Aunque todavía lejos de ser ideal:



Eventos de vida de componentes


Como ya conocimos uno de los eventos de vida del componente OnInitializedAsync, sería lógico mencionar los otros:
Métodos de inicialización
Inicializado
Se invoca cuando el componente ya está inicializado y sus parámetros ya están establecidos por el componente principal. Durante la vida de un componente, se llama una vez después de su inicialización.
OnInitializedAsync
Versión asíncrona del primer método, después de la ejecución, el componente se vuelve a representar. Por lo tanto, al escribir código, debe tener en cuenta que algunos objetos pueden ser nulos.
Método ejecutable antes de establecer valores de 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


En esta parte, aprendimos cómo recibir datos del controlador y mostrarlos al usuario.
En la siguiente parte, ordenaremos el diseño y agregaremos una capa de acceso a datos para mostrar datos reales en la página principal.

Enlace al repositorio de esta serie de artículos.
Enlace a la fuente original.

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


All Articles