Componentes de Razor de ASP.NET Core
Las aplicaciones de Blazor se crean usando componentes de Razor . Un componente es una parte independiente de la interfaz de usuario (UI) con lógica de procesamiento para habilitar el comportamiento dinámico. Los componentes se pueden anidar, reutilizar, compartir entre proyectos y usarse en aplicaciones MVC y de Razor Pages.
Clases de componentes
Los componentes se implementan en archivos de componentes de Razor con una extensión de archivo .razor mediante una combinación de C# y marcado HTML.
Sintaxis de Razor
Los componentes usan la sintaxis de Razor. Los componentes, las directivas y los atributos de directiva usan ampliamente dos características de Razor. Estas son palabras clave reservadas con el prefijo @ que aparecen en el marcado de Razor:
- Directivas: cambian el modo en que se analiza o funciona un marcado de componente. Por ejemplo, la directiva
@pageespecifica un componente enrutable con una plantilla de ruta, y se puede acceder directamente a esta mediante la solicitud de un usuario en el explorador en una dirección URL específica. - Atributos de directiva: cambian el modo en que se analiza o funciona un elemento de componente. Por ejemplo, el atributo de directiva
@bindde un elemento<input>enlaza los datos al valor del elemento.
Las directivas y los atributos de directiva que se usan en los componentes se explican más adelante en este artículo y en otros artículos del conjunto de documentación de Blazor. Para obtener información general sobre la sintaxis de Razor, vea Referencia sobre la sintaxis de Razor para ASP.NET Core.
Nombres
El nombre de un componente debe empezar por mayúsculas:
ProductDetail.razores válido.productDetail.razorno es válido.
Entre las convenciones de nomenclatura comunes de Blazor que se usan en toda la documentación de Blazor se incluyen:
- Las rutas de archivo de componente usan Pascal Case† y aparecen antes de mostrar ejemplos de código de componente. Las rutas indican ubicaciones de carpeta típicas. Por ejemplo,
Pages/ProductDetail.razorindica que el componenteProductDetailtiene el nombre de archivoProductDetail.razory reside en la carpetaPagesde la aplicación. - Las rutas de archivo de componente para los componentes enrutables hacen coincidir sus direcciones URL con guiones que aparecen para espacios entre palabras en la plantilla de ruta de un componente. Por ejemplo, se solicita un componente
ProductDetailcon una plantilla de ruta de/product-detail(@page "/product-detail") en un explorador en la dirección URL/product-detailrelativa.
†Pascal Case (mayúscula y minúscula camel) es una convención de nomenclatura sin espacios y signos de puntuación y con la primera letra de cada palabra en mayúsculas, incluida la primera palabra.
Enrutamiento
El enrutamiento en Blazor se consigue proporcionando una plantilla de ruta a cada componente accesible en la aplicación con una directiva @page. Cuando se compila un archivo de Razor con una directiva @page, la clase generada recibe un elemento RouteAttribute que especifica la plantilla de ruta. En tiempo de ejecución, el enrutador busca las clases de componentes con un atributo RouteAttribute y representa el componente que tenga una plantilla de ruta que coincida con la dirección URL solicitada.
El componente HelloWorld siguiente usa una plantilla de ruta de /hello-world. La página web que se representa para el componente se encuentra en la dirección URL /hello-world relativa. Al ejecutar una aplicación de Blazor localmente con el protocolo, el host y el puerto predeterminados, el componente HelloWorld se solicita en el explorador en https://localhost:5001/hello-world. Los componentes que generan páginas web suelen residir en la carpeta Pages, pero puede usar cualquier carpeta para contener componentes, incluidas las carpetas anidadas.
Pages/HelloWorld.razor:
@page "/hello-world"
<h1>Hello World!</h1>
El componente anterior se carga en el explorador en /hello-world independientemente de si agrega o no el componente a la navegación de la interfaz de usuario de la aplicación. De manera opcional, se pueden agregar componentes al componente NavMenu para que aparezca un vínculo al componente en la navegación basada en la interfaz de usuario de la aplicación.
Para el componente HelloWorld anterior, agregue el componente NavLink siguiente al componente NavMenu. Agregue el componente NavLink en un nuevo elemento de lista (<li>...</li>) entre las etiquetas de lista desordenadas (<ul>...</ul>).
Shared/NavMenu.razor:
<li class="nav-item px-3">
<NavLink class="nav-link" href="hello-world">
<span class="oi oi-list-rich" aria-hidden="true"></span> Hello World!
</NavLink>
</li>
Para obtener más información, incluidas las descripciones de los componentes NavLink y NavMenu, vea Enrutamiento de Blazor de ASP.NET Core.
marcado
La interfaz de usuario de un componente se define mediante la sintaxis de Razor, que consta de marcado de Razor, C# y HTML. Cuando una aplicación se compila, el marcado HTML y la lógica de representación de C# se convierten en una clase de componente. El nombre de la clase generada coincide con el nombre del archivo.
Los miembros de la clase de componente se definen en uno o varios bloques @code. En los bloques @code, el estado del componente se especifica y se procesa con C#:
- Inicializadores de propiedad y campo.
- Valores de parámetro de argumentos pasados por componentes primarios y parámetros de ruta.
- Métodos para el control de eventos de usuario, eventos de ciclo de vida y lógica de componentes personalizados.
Los miembros de componente se usan en la lógica de representación mediante expresiones de C# que comienzan por el símbolo @. Por ejemplo, un campo de C# se representa anteponiendo el prefijo @ al nombre del campo. En el componente Markup siguiente se evalúa y representa lo siguiente:
headingFontStylepara el valor de la propiedad CSSfont-styledel elemento de encabezado.headingTextpara el contenido del elemento de encabezado.
Pages/Markup.razor:
@page "/markup"
<h1 style="font-style:@headingFontStyle">@headingText</h1>
@code {
private string headingFontStyle = "italic";
private string headingText = "Put on your new Blazor!";
}
Nota
En los ejemplos de la documentación de Blazor se especifica el modificador de acceso private para los miembros privados. Los miembros privados tienen como ámbito la clase de un componente. Sin embargo, C# asume el modificador de acceso private cuando no hay ningún modificador de acceso presente, por lo que marcar explícitamente los miembros como "private" en su propio código es opcional. Para obtener más información sobre los modificadores de acceso, vea Modificadores de acceso (Guía de programación de C#).
El marco de Blazor procesa un componente internamente como un árbol de representación, que es la combinación del Document Object Model (DOM) y Cascading Style Sheet Object Model (CSSOM) de un componente. Una vez que el componente se ha representado inicialmente, se vuelve a generar su árbol de representación en respuesta a eventos. Blazor compara el nuevo árbol de representación con el anterior y aplica las modificaciones necesarias al DOM del explorador para su presentación. Para obtener más información, vea Representación de componentes de Blazor de ASP.NET Core.
Los componentes son clases de C# normales, y se pueden colocar en cualquier parte dentro de un proyecto. Los componentes que generan páginas web suelen residir en la carpeta Pages. Los componentes que no son de página se colocan con frecuencia en la carpeta Shared o en una carpeta personalizada agregada al proyecto.
Componentes anidados
Para que los componentes puedan incluir otros componentes, hay que declararlos usando la sintaxis HTML. El marcado para utilizar un componente se parece a una etiqueta HTML en la que el nombre de la etiqueta es el tipo de componente.
Tenga en cuenta el componente Heading siguiente, que pueden usar otros componentes para mostrar un encabezado.
Shared/Heading.razor:
<h1 style="font-style:@headingFontStyle">Heading Example</h1>
@code {
private string headingFontStyle = "italic";
}
El marcado siguiente del componente HeadingExample representa el componente Heading anterior en la ubicación donde aparece la etiqueta <Heading />.
Pages/HeadingExample.razor:
@page "/heading-example"
<Heading />
Si un componente contiene un elemento HTML cuyo nombre empieza por mayúsculas y no coincide con un nombre de componente con el mismo espacio de nombres, se emite una advertencia que indica que el elemento tiene un nombre inesperado. Al agregar una directiva @using relativa al espacio de nombres del componente, se permite que el componente esté disponible, lo que resuelve la advertencia. Para obtener más información, vea la sección Espacios de nombres.
El ejemplo de componente Heading que se muestra en esta sección no tiene una directiva @page, por lo que el componente Heading no es accesible directamente para un usuario por medio de una solicitud directa en el explorador. Sin embargo, cualquier componente con una directiva @page se puede anidar en otro componente. Si el componente Heading era accesible directamente mediante la inclusión de @page "/heading" en la parte superior de su archivo de Razor, el componente se representaría para las solicitudes del explorador en /heading y /heading-example.
Espacios de nombres
Por lo general, el espacio de nombres de un componente se deriva del espacio de nombres raíz de la aplicación y de la ubicación del componente (carpeta) dentro de la aplicación. Así, si el espacio de nombres raíz de la aplicación es BlazorSample y el componente Counter reside en la carpeta Pages:
- El espacio de nombres del componente
CounteresBlazorSample.Pages. - El nombre de tipo completo del componente es
BlazorSample.Pages.Counter.
En el caso de las carpetas personalizadas que contienen componentes, agregue una directiva @using al componente primario o al archivo _Imports.razor de la aplicación. En el ejemplo siguiente se ponen a disposición los componentes de la carpeta Components:
@using BlazorSample.Components
Nota
Las directivas @using del archivo _Imports.razor solo se aplican a los archivos Razor (.razor), por lo que no se aplican a los archivos C# (.cs).
También se puede hacer referencia a los componentes mediante sus nombres completos, lo cual no requiere el uso de la directiva @using. En el ejemplo siguiente se hace referencia directamente al componente ProductDetail de la carpeta Components de la aplicación:
<BlazorSample.Components.ProductDetail />
El espacio de nombres de un componente creado con Razor se basa en lo siguiente (por orden de prioridad):
- Directiva
@namespaceen el marcado del archivo de Razor (por ejemplo,@namespace BlazorSample.CustomNamespace). - Elemento
RootNamespacedel proyecto en el archivo de proyecto (por ejemplo,<RootNamespace>BlazorSample</RootNamespace>). - Nombre del proyecto, tomado del nombre de archivo del archivo de proyecto (
.csproj) y la ruta de acceso de la raíz del proyecto al componente. Por ejemplo, el marco de trabajo resuelve{PROJECT ROOT}/Pages/Index.razorcon un espacio de nombres de proyecto deBlazorSample(BlazorSample.csproj) en el espacio de nombresBlazorSample.Pagesdel componenteIndex.{PROJECT ROOT}es la ruta raíz del proyecto. Los componentes de C# siguen las reglas de los enlaces de nombres. En el caso del componenteIndexde este ejemplo, los componentes en el ámbito serían todos los componentes:- Ejecute
Pagesen la misma carpeta. - Los componentes en la raíz del proyecto que no especifiquen explícitamente un espacio de nombres diferente.
- Ejecute
No se admite lo siguiente:
- La calificación
global::. - La importación de componentes con instrucciones
usingcon alias. Por ejemplo,@using Foo = Barno es compatible. - Nombres parcialmente completos. Por ejemplo, no puede agregar
@using BlazorSamplea un componente y, después, hacer referencia al componenteNavMenuen la carpetaSharedde la aplicación (Shared/NavMenu.razor) con<Shared.NavMenu></Shared.NavMenu>.
Compatibilidad parcial de clases
Los componentes se generan como clases parciales de C# y se crean mediante cualquiera de los siguientes métodos:
- Un solo archivo contiene código de C# definido en uno o varios bloques
@code, marcado HTML y marcado de Razor. Las plantillas de proyecto de Blazor definen sus componentes con este método de archivo único. - El marcado HTML y de Razor se colocan en un archivo de Razor (
.razor). El código de C# se coloca en un archivo de código subyacente definido como una clase parcial (.cs).
Nota
Una hoja de estilos de componente que define estilos específicos del componente es un archivo independiente (.css). El aislamiento de CSS de Blazor se describe más adelante en Aislamiento de CSS de Blazor de ASP.NET Core.
En el siguiente ejemplo se muestra el componente Counter predeterminado con un bloque @code en una aplicación generada a partir de una plantilla de proyecto de Blazor. El marcado y el código de C# se encuentran en el mismo archivo. Este es el método más común que se aplica en la creación de componentes.
Pages/Counter.razor:
@page "/counter"
<PageTitle>Counter</PageTitle>
<h1>Counter</h1>
<p role="status">Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
private int currentCount = 0;
private void IncrementCount()
{
currentCount++;
}
}
El componente Counter siguiente divide el marcado HTML y de Razor del código de C# mediante un archivo de código subyacente con una clase parcial:
Pages/CounterPartialClass.razor:
@page "/counter-partial-class"
<h1>Counter</h1>
<p>Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
Pages/CounterPartialClass.razor.cs:
namespace BlazorSample.Pages
{
public partial class CounterPartialClass
{
private int currentCount = 0;
void IncrementCount()
{
currentCount++;
}
}
}
Las directivas @using del archivo _Imports.razor solo se aplican a los archivos Razor (.razor), por lo que no se aplican a los archivos C# (.cs). Agregue los espacios de nombres que sean necesarios a un archivo de clase parcial.
Espacios de nombres típicos usados por los componentes:
using System.Net.Http;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.Forms;
using Microsoft.AspNetCore.Components.Routing;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.Web.Virtualization;
using Microsoft.JSInterop;
Los espacios de nombres típicos también incluyen el espacio de nombres de la aplicación y el espacio de nombres correspondiente a la carpeta Shared de la aplicación:
using BlazorSample;
using BlazorSample.Shared;
Especificación de una clase base
La directiva @inherits se usa para especificar la clase base de un componente. En el siguiente ejemplo se muestra cómo un componente puede heredar una clase base para proporcionar las propiedades y los métodos del componente. La clase base BlazorRocksBase deriva de ComponentBase.
Pages/BlazorRocks.razor:
@page "/blazor-rocks"
@inherits BlazorRocksBase
<h1>@BlazorRocksText</h1>
BlazorRocksBase.cs:
using Microsoft.AspNetCore.Components;
namespace BlazorSample
{
public class BlazorRocksBase : ComponentBase
{
public string BlazorRocksText { get; set; } =
"Blazor rocks the browser!";
}
}
Parámetros del componente
Los parámetros del componente pasan los datos a componentes y se definen por medio de propiedades de C# públicas en la clase del componente con el atributo [Parameter]. En el ejemplo siguiente, un tipo de referencia integrado (System.String) y un tipo de referencia definido por el usuario (PanelBody) se pasan como parámetros del componente.
PanelBody.cs:
public class PanelBody
{
public string? Text { get; set; }
public string? Style { get; set; }
}
Shared/ParameterChild.razor:
<div class="card w-25" style="margin-bottom:15px">
<div class="card-header font-weight-bold">@Title</div>
<div class="card-body" style="font-style:@Body.Style">
@Body.Text
</div>
</div>
@code {
[Parameter]
public string Title { get; set; } = "Set By Child";
[Parameter]
public PanelBody Body { get; set; } =
new()
{
Text = "Set by child.",
Style = "normal"
};
}
Advertencia
Se admite el suministro de valores iniciales para los parámetros del componente, pero no cree un componente que escriba en sus propios parámetros después de que el componente se represente por primera vez. Para obtener más información, vea la sección Parámetros sobrescritos de este artículo.
Los parámetros del componente Title y Body del componente ParameterChild se establecen mediante argumentos en la etiqueta HTML que representa la instancia del componente. El componente ParameterParent siguiente representa dos componentes ParameterChild:
- El primer componente
ParameterChildse representa sin proporcionar argumentos de parámetro. - El segundo componente
ParameterChildrecibe valores paraTitleyBodydel componenteParameterParent, que usa una expresión de C# explícita para establecer los valores de las propiedades dePanelBody.
Pages/ParameterParent.razor:
@page "/parameter-parent"
<h1>Child component (without attribute values)</h1>
<ParameterChild />
<h1>Child component (with attribute values)</h1>
<ParameterChild Title="Set by Parent"
Body="@(new PanelBody() { Text = "Set by parent.", Style = "italic" })" />
El siguiente marcado HTML representado del componente ParameterParent muestra los valores predeterminados del componente ParameterChild cuando el componente ParameterParent no proporciona valores de parámetros del componente. Cuando el componente ParameterParent proporciona valores de parámetros del componente, estos reemplazan los valores predeterminados del componente ParameterChild.
Nota
A modo de aclaración, las clases de estilo CSS representadas no se muestran en el siguiente marcado HTML representado.
<h1>Child component (without attribute values)</h1>
<div>
<div>Set By Child</div>
<div>Set by child.</div>
</div>
<h1>Child component (with attribute values)</h1>
<div>
<div>Set by Parent</div>
<div>Set by parent.</div>
</div>
Asigne un campo, propiedad o resultado de C# de un método a un parámetro del componente como un valor de atributo HTML mediante el símbolo Razor reservado de @. El componente ParameterParent2 siguiente muestra cuatro instancias del componente ParameterChild anterior y establece sus valores de parámetro Title en:
- El valor del campo
title. - El resultado del método
GetTitlede C#. - La fecha local actual en formato largo con ToLongDateString, que usa una expresión implícita de C#.
- La propiedad
Titledel objetopanelData.
Pages/ParameterParent2.razor:
@page "/parameter-parent-2"
<ParameterChild Title="@title" />
<ParameterChild Title="@GetTitle()" />
<ParameterChild Title="@DateTime.Now.ToLongDateString()" />
<ParameterChild Title="@panelData.Title" />
@code {
private string title = "From Parent field";
private PanelData panelData = new();
private string GetTitle()
{
return "From Parent method";
}
private class PanelData
{
public string Title { get; set; } = "From Parent object";
}
}
Nota
Al asignar un miembro de C# a un parámetro del componente, agregue un prefijo al miembro con el símbolo @ y nunca agregue un prefijo al atributo HTML del parámetro.
Correcto:
<ParameterChild Title="@title" />
Incorrecto:
<ParameterChild @Title="title" />
A diferencia de lo que ocurre en Razor Pages (.cshtml), Blazor no puede realizar el trabajo asincrónico en una expresión de Razor mientras se representa un componente. Esto se debe a que Blazor está diseñado para representar interfaces de usuario interactivas. En una interfaz de usuario interactiva, la pantalla debe mostrar siempre algo, por lo que no tiene sentido bloquear el flujo de representación. En su lugar, el trabajo asincrónico se realiza durante uno de los eventos asincrónicos del ciclo de vida. Después de cada evento asincrónico del ciclo de vida, el componente puede representarse de nuevo. La sintaxis de Razor siguiente no se admite:
<ParameterChild Title="@await ..." />
El código del ejemplo anterior genera un error del compilador si se compila la aplicación:
El operador "await" solo se puede usar dentro de un método asincrónico. Marque este método con el modificador "Async" y cambie su tipo de valor devuelto a "Task".
Para obtener un valor para el parámetro Title en el ejemplo anterior de manera asincrónica, el componente puede usar el evento del ciclo de vida OnInitializedAsync , como se muestra en el ejemplo siguiente:
<ParameterChild Title="@title" />
@code {
private string title;
protected override async Task OnInitializedAsync()
{
title = await ...;
}
}
Para obtener más información, vea Ciclo de vida de los componentes de ASP.NET Core Razor.
RazorNo se admite el uso de una expresión de explícita para concatenar texto con el resultado de una expresión para la asignación a un parámetro. En el ejemplo siguiente se busca concatenar el texto "Set by " con el valor de propiedad de un objeto. Aunque esta sintaxis se admite en una página de Razor (.cshtml), no es válida para la asignación al parámetro Title del elemento secundario de un componente. La sintaxis de Razor siguiente no se admite:
<ParameterChild Title="Set by @(panelData.Title)" />
El código del ejemplo anterior genera un error del compilador si se compila la aplicación:
Los atributos del componente no admiten contenido complejo (C# y marcado combinado).
Para admitir la asignación de un valor compuesto, utilice un método, un campo o una propiedad. En el ejemplo siguiente se realiza la concatenación de "Set by " y el valor de propiedad de un objeto en el método de C# GetTitle:
Pages/ParameterParent3.razor:
@page "/parameter-parent-3"
<ParameterChild Title="@GetTitle()" />
@code {
private PanelData panelData = new();
private string GetTitle() => $"Set by {panelData.Title}";
private class PanelData
{
public string Title { get; set; } = "Parent";
}
}
Para obtener más información, vea Referencia sobre la sintaxis de Razor para ASP.NET Core.
Advertencia
Se admite el suministro de valores iniciales para los parámetros del componente, pero no cree un componente que escriba en sus propios parámetros después de que el componente se represente por primera vez. Para obtener más información, vea la sección Parámetros sobrescritos de este artículo.
Los parámetros del componente se deben declarar como propiedades automáticas, lo que significa que no deben contener lógica personalizada en sus descriptores de acceso get o set. Por ejemplo, la siguiente propiedad StartData es una propiedad automática:
[Parameter]
public DateTime StartData { get; set; }
No coloque la lógica personalizada en el descriptor de acceso get o set porque los parámetros del componente están pensados exclusivamente para su uso como canal para que un componente primario envíe información a un componente secundario. Si un descriptor de acceso set de una propiedad de componente secundario contiene lógica que provoca la repetición de la representación del componente primario, se produce un bucle de representación infinito.
Para transformar un valor de parámetro recibido:
- Deje la propiedad del parámetro como propiedad automática para representar los datos sin procesar proporcionados.
- Cree otra propiedad o método que proporcione los datos transformados en función de la propiedad del parámetro.
Invalide OnParametersSetAsync para transformar un parámetro recibido cada vez que se reciban nuevos datos.
Se admite la escritura de un valor inicial en un parámetro de componente porque las asignaciones de valores iniciales no interfieren con la representación automática del componente de Blazor. La siguiente asignación de la configuración local DateTime actual de DateTime.Now a StartData es una sintaxis válida en un componente:
[Parameter]
public DateTime StartData { get; set; } = DateTime.Now;
Después de la asignación inicial de DateTime.Now, no asigne un valor a StartData en el código para desarrolladores. Para obtener más información, vea la sección Parámetros sobrescritos de este artículo.
Aplique el atributo [EditorRequired] para especificar un parámetro de componente necesario. Si no se proporciona un valor de parámetro, los editores o las herramientas de compilación pueden mostrar advertencias al usuario. Este atributo solo es válido para las propiedades marcadas también con el atributo [Parameter]. El atributo [EditorRequired] se aplica en tiempo de diseño y cuando se crea la aplicación. El atributo no se aplica en tiempo de ejecución y no garantiza un valor de parámetro distinto a null.
[Parameter]
[EditorRequired]
public string Title { get; set; }
También se admiten listas de atributos de una sola línea:
[Parameter, EditorRequired]
public string Title { get; set; }
Parámetros de ruta
Los componentes pueden especificar parámetros de ruta en la plantilla de ruta de la directiva @page. El enrutador de Blazor usa parámetros de ruta para rellenar los parámetros de componente correspondientes con el mismo nombre.
Se admiten parámetros de ruta opcionales. En el ejemplo siguiente, el parámetro opcional text asigna el valor del segmento de ruta a la propiedad Text del componente. Si el segmento no está presente, el valor de Text se establece en "fantastic" en el método de ciclo de vida OnInitialized.
Pages/RouteParameter.razor:
@page "/route-parameter/{text?}"
<h1>Blazor is @Text!</h1>
@code {
[Parameter]
public string? Text { get; set; }
protected override void OnInitialized()
{
Text = Text ?? "fantastic";
}
}
Para obtener información sobre los parámetros de ruta comodín ({*pageRoute}), que capturan rutas de acceso en varios límites de carpeta, vea Enrutamiento de Blazor de ASP.NET Core.
Parámetros sobrescritos
El marco Blazor impone con carácter general una asignación de parámetro de componentes primarios a secundarios segura:
- Los parámetros no se sobrescriben de forma inesperada.
- Los efectos secundarios se minimizan. Por ejemplo,se evitan representaciones adicionales, ya que pueden crear bucles de representación infinitos.
Un componente secundario recibe nuevos valores de parámetro que posiblemente sobrescriban los valores existentes cuando el componente primario vuelva a representarse. Sobrescribir valores de parámetro en un componente secundario de forma accidental suele producirse al desarrollar el componente con uno o varios parámetros enlazados a datos y cuando el desarrollador escribe directamente en un parámetro del elemento secundario:
- El componente secundario se representa con uno o varios valores de parámetro del componente primario.
- El elemento secundario escribe directamente en el valor de un parámetro.
- El componente primario vuelve a representar el valor del parámetro del elemento secundario y lo sobrescribe.
La posibilidad de sobrescribir valores de parámetro se extiende también a los descriptores de acceso set de propiedades del componente secundario.
Importante
Nuestra orientación general es no crear componentes que escriban directamente en sus propios parámetros después de que el componente se represente por primera vez.
Considere el componente Expander erróneo siguiente que:
- Representa el contenido secundario.
- Alterna la visualización del contenido secundario con un parámetro de componente (
Expanded). - El componente escribe directamente en el parámetro
Expanded, que muestra el problema con los parámetros sobrescritos y debe evitarse.
Después de que el componente Expander siguiente muestre el método incorrecto para este escenario, se muestra un componente Expander modificado para mostrar el método correcto. Los ejemplos siguientes se pueden colocar en una aplicación de ejemplo local para experimentar los comportamientos descritos.
Shared/Expander.razor:
<div @onclick="Toggle" class="card bg-light mb-3" style="width:30rem">
<div class="card-body">
<h2 class="card-title">Toggle (<code>Expanded</code> = @Expanded)</h2>
@if (Expanded)
{
<p class="card-text">@ChildContent</p>
}
</div>
</div>
@code {
[Parameter]
public bool Expanded { get; set; }
[Parameter]
public RenderFragment? ChildContent { get; set; }
private void Toggle()
{
Expanded = !Expanded;
}
}
El componente Expander se agrega al siguiente componente primario ExpanderExample que podría llamar a StateHasChanged:
- Al llamar a StateHasChanged en el código de desarrollador se notifica a un componente que su estado ha cambiado y normalmente desencadena la repetición de la representación del componente para actualizar la interfaz de usuario. StateHasChanged se trata detalladamente más adelante en Ciclo de vida de los componentes de ASP.NET Core Razor y Representación de componentes de Blazor de ASP.NET Core.
- El atributo de directiva
@onclickdel botón asocia un controlador de eventos al eventoonclickdel botón. El control de eventos se trata detalladamente más adelante en Control de eventos de Blazor en ASP.NET Core.
Pages/ExpanderExample.razor:
@page "/expander-example"
<Expander Expanded="true">
Expander 1 content
</Expander>
<Expander Expanded="true" />
<button @onclick="StateHasChanged">
Call StateHasChanged
</button>
Al principio, los componentes Expander se comportan de manera independiente cuando se alternan sus propiedades de Expanded. Los componentes secundarios mantienen sus estados según lo previsto. Cuando se llama a StateHasChanged en el componente principal, el parámetro Expanded del primer componente secundario se restablece nuevamente en su valor inicial (true). No se restablece el valor Expanded del segundo componente de Expander, porque no se representa ningún contenido secundario en el segundo componente.
Para mantener el estado en el escenario anterior, use un campo privado en el componente Expander para mantener su estado de alternancia.
El siguiente componente Expander revisado:
- Acepta el valor del parámetro de componente
Expandeddel componente principal. - Asigna el valor del parámetro de componente a un campo privado (
expanded) en el eventoOnInitialized. - Usa el campo privado para mantener su estado de alternancia interno, que muestra cómo evitar escribir directamente en un parámetro.
Nota
Los consejos de esta sección se aplican a una lógica similar en los descriptores de acceso set de parámetros de componente, lo que puede dar lugar a efectos secundarios no deseados similares.
Shared/Expander.razor:
<div @onclick="Toggle" class="card bg-light mb-3" style="width:30rem">
<div class="card-body">
<h2 class="card-title">Toggle (<code>expanded</code> = @expanded)</h2>
@if (expanded)
{
<p class="card-text">@ChildContent</p>
}
</div>
</div>
@code {
private bool expanded;
[Parameter]
public bool Expanded { get; set; }
[Parameter]
public RenderFragment? ChildContent { get; set; }
protected override void OnInitialized()
{
expanded = Expanded;
}
private void Toggle()
{
expanded = !expanded;
}
}
Para obtener ejemplos de enlace de elementos primarios y secundarios de dos vías, consulte Enlace de datos de ASP.NET Core Blazor. Para obtener información adicional, consulte Error de enlace bidireccional de Blazor (dotnet/aspnetcore #24599).
Contenido secundario
Los componentes pueden definir el contenido de otro componente. El componente de asignación proporciona el contenido entre las etiquetas de apertura y cierre del componente secundario.
En el siguiente ejemplo, el componente RenderFragmentChild tiene una propiedad ChildContent que representa un segmento de la interfaz de usuario que se va a representar como RenderFragment. La posición de ChildContent en el marcado de Razor del componente es donde el contenido se representa en la salida HTML final.
Shared/RenderFragmentChild.razor:
<div class="card w-25" style="margin-bottom:15px">
<div class="card-header font-weight-bold">Child content</div>
<div class="card-body">@ChildContent</div>
</div>
@code {
[Parameter]
public RenderFragment? ChildContent { get; set; }
}
Importante
La propiedad que recibe el contenido de RenderFragment debe denominarse ChildContent por convención.
El componente RenderFragmentParent siguiente proporciona contenido para representar el componente RenderFragmentChild mediante la colocación del contenido dentro de las etiquetas de apertura y cierre del componente secundario.
Pages/RenderFragmentParent.razor:
@page "/render-fragment-parent"
<h1>Render child content</h1>
<RenderFragmentChild>
Content of the child component is supplied
by the parent component.
</RenderFragmentChild>
Debido a la forma en que Blazor representa el contenido secundario, la representación de componentes dentro de un bucle for requiere una variable de índice local si se usa la variable de bucle incremental en el contenido del componente RenderFragmentChild. El ejemplo siguiente se puede agregar al componente RenderFragmentParent anterior:
<h1>Three children with an index variable</h1>
@for (int c = 0; c < 3; c++)
{
var current = c;
<RenderFragmentChild>
Count: @current
</RenderFragmentChild>
}
Como alternativa, use un bucle foreach con Enumerable.Range en lugar de un bucle for. El ejemplo siguiente se puede agregar al componente RenderFragmentParent anterior:
<h1>Second example of three children with an index variable</h1>
@foreach (var c in Enumerable.Range(0,3))
{
<RenderFragmentChild>
Count: @c
</RenderFragmentChild>
}
Para obtener información sobre cómo se puede usar un componente RenderFragment como plantilla para la interfaz de usuario del componente, consulte los siguientes artículos:
- Componentes con plantilla de Blazor en ASP.NET Core
- Procedimientos recomendados de rendimiento de Blazor en ASP.NET Core
Expansión de atributos y parámetros arbitrarios
Los componentes pueden capturar y representar más atributos aparte de los parámetros declarados del componente. Los atributos adicionales se pueden capturar en un diccionario y luego expandirse en un elemento cuando el componente se representa por medio del atributo de directiva de @attributesRazor. Este escenario es útil para definir un componente que genera un elemento de marcado que admite diversas personalizaciones. Por ejemplo, definir atributos por separado para un elemento <input> que admite muchos parámetros puede ser un engorro.
En el componente Splat siguiente:
- El primer elemento
<input>(id="useIndividualParams") usa parámetros de componente individuales. - El segundo elemento
<input>(id="useAttributesDict") usa la expansión de atributos.
Pages/Splat.razor:
@page "/splat"
<input id="useIndividualParams"
maxlength="@maxlength"
placeholder="@placeholder"
required="@required"
size="@size" />
<input id="useAttributesDict"
@attributes="InputAttributes" />
@code {
private string maxlength = "10";
private string placeholder = "Input placeholder text";
private string required = "required";
private string size = "50";
private Dictionary<string, object> InputAttributes { get; set; } =
new()
{
{ "maxlength", "10" },
{ "placeholder", "Input placeholder text" },
{ "required", "required" },
{ "size", "50" }
};
}
Los elementos <input> representados en la página web son idénticos:
<input id="useIndividualParams"
maxlength="10"
placeholder="Input placeholder text"
required="required"
size="50">
<input id="useAttributesDict"
maxlength="10"
placeholder="Input placeholder text"
required="required"
size="50">
Para aceptar atributos arbitrarios, defina un parámetro de componente con la propiedad CaptureUnmatchedValues establecida en true:
@code {
[Parameter(CaptureUnmatchedValues = true)]
public Dictionary<string, object> InputAttributes { get; set; }
}
La propiedad CaptureUnmatchedValues en [Parameter] permite que el parámetro coincida con todos los atributos que no coinciden con ningún otro parámetro. Un componente solamente puede definir un parámetro con CaptureUnmatchedValues. El tipo de propiedad que se usa con CaptureUnmatchedValues se debe poder asignar desde Dictionary<string, object> con claves de cadena. El uso de IEnumerable<KeyValuePair<string, object>> o IReadOnlyDictionary<string, object> también son opciones en este escenario.
La posición de @attributes relativa a la posición de los atributos de elemento es importante. Cuando @attributes se expande en el elemento, los atributos se procesan de derecha a izquierda (del último al primero). Veamos el siguiente ejemplo de un componente primario que usa un componente secundario:
Shared/AttributeOrderChild1.razor:
<div @attributes="AdditionalAttributes" extra="5" />
@code {
[Parameter(CaptureUnmatchedValues = true)]
public IDictionary<string, object>? AdditionalAttributes { get; set; }
}
Pages/AttributeOrderParent1.razor:
@page "/attribute-order-parent-1"
<AttributeOrderChild1 extra="10" />
El atributo extra del componente AttributeOrderChild1 se establece a la derecha de @attributes. El elemento <div> representado del componente AttributeOrderParent1 contiene extra="5" cuando se pasa a través del atributo adicional, ya que los atributos se procesan de derecha a izquierda (de último a primero):
<div extra="5" />
En el ejemplo siguiente, el orden de extra y @attributes se invierte en el elemento <div> del componente secundario:
Shared/AttributeOrderChild2.razor:
<div extra="5" @attributes="AdditionalAttributes" />
@code {
[Parameter(CaptureUnmatchedValues = true)]
public IDictionary<string, object>? AdditionalAttributes { get; set; }
}
Pages/AttributeOrderParent2.razor:
@page "/attribute-order-parent-2"
<AttributeOrderChild2 extra="10" />
El elemento <div> en la página web representada del componente primario contiene extra="10" cuando se pasa por el atributo adicional:
<div extra="10" />
Captura de referencias a componentes
Las referencias de componentes proporcionan una manera de hacer referencia a una instancia de componente para emitir comandos. Para capturar una referencia de componente:
- Agregue un atributo
@refal componente secundario. - Defina un campo con el mismo tipo que el componente secundario.
Cuando el componente se representa, el campo se rellena con la instancia del componente. Tras ello, se pueden invocar métodos de .NET en la instancia.
Tenga en cuenta el componente ReferenceChild siguiente, que registra un mensaje cuando se llama a su método ChildMethod.
Shared/ReferenceChild.razor:
@using Microsoft.Extensions.Logging
@inject ILogger<ReferenceChild> logger
@code {
public void ChildMethod(int value)
{
logger.LogInformation("Received {Value} in ChildMethod", value);
}
}
Una referencia de componente solo se rellena después de que el componente se represente, y su salida incluye el elemento de ReferenceChild. Hasta que se represente el componente, no hay nada a lo que hacer referencia.
Para manipular las referencias de componente una vez finalizada la representación del componente, use los métodos OnAfterRender o OnAfterRenderAsync.
Para usar una variable de referencia con un controlador de eventos, use una expresión lambda o asigne el delegado de controlador de eventos en los métodos OnAfterRender o OnAfterRenderAsync. Esto garantiza que la variable de referencia se asigna antes de que se asigne el controlador de eventos.
El siguiente método lambda usa el componente ReferenceChild anterior.
Pages/ReferenceParent1.razor:
@page "/reference-parent-1"
<button @onclick="@(() => childComponent?.ChildMethod(5))">
Call <code>ReferenceChild.ChildMethod</code> with an argument of 5
</button>
<ReferenceChild @ref="childComponent" />
@code {
private ReferenceChild? childComponent;
}
El siguiente método de delegado usa el componente ReferenceChild anterior.
Pages/ReferenceParent2.razor:
@page "/reference-parent-2"
<button @onclick="@(() => callChildMethod?.Invoke())">
Call <code>ReferenceChild.ChildMethod</code> with an argument of 5
</button>
<ReferenceChild @ref="childComponent" />
@code {
private ReferenceChild? childComponent;
private Action? callChildMethod;
protected override void OnAfterRender(bool firstRender)
{
if (firstRender)
{
callChildMethod = CallChildMethod;
}
}
private void CallChildMethod()
{
childComponent?.ChildMethod(5);
}
}
Use una colección para hacer referencia a componentes de un bucle. En el ejemplo siguiente:
- Los componentes se agregan a List<T>.
- Se crea un botón para cada componente que desencadena el método
ChildMethoddel componente correspondiente por su índice de componente en List<T>.
Pages/ReferenceParent3.razor mediante el componente ReferenceChild anterior:
@page "/reference-parent-3"
<ul>
@for (int i = 0; i < 5; i++)
{
var index = i;
var v = r.Next(1000);
<li>
<ReferenceChild @ref="childComponent" />
<button @onclick="@(() => callChildMethod?.Invoke(index, v))">
Component index @index: Call <code>ReferenceChild.ChildMethod(@v)</code>
</button>
</li>
}
</ul>
@code {
private Random r = new();
private List<ReferenceChild> components = new();
private Action<int, int>? callChildMethod;
private ReferenceChild childComponent
{
set => components.Add(value);
}
protected override void OnAfterRender(bool firstRender)
{
if (firstRender)
{
callChildMethod = CallChildMethod;
}
}
private void CallChildMethod(int index, int value)
{
components.ElementAt(index).ChildMethod(value);
}
}
Si bien la captura de referencias de componentes emplea una sintaxis similar a la de la captura de referencias de elementos, esta no es una característica de interoperabilidad de JavaScript. Las referencias de componente no se pasan al código JavaScript. Solo se usan en código .NET.
Importante
No use referencias de componentes para mutar el estado de los componentes secundarios. En su lugar, use parámetros de componente declarativos normales para pasar datos a componentes secundarios. El uso de parámetros de componente da como resultado componentes secundarios que se volverán a representar automáticamente justo cuando corresponda. Para obtener más información, consulte la sección de parámetros del componente y el artículo Enlace de datos de ASP.NET Core Blazor.
Contexto de sincronización
Blazor usa un contexto de sincronización (SynchronizationContext) para aplicar un único subproceso lógico de ejecución. Los métodos de ciclo de vida de un componente y las devoluciones de llamada de eventos que Blazor genere se ejecutan en el contexto de sincronización.
El contexto de sincronización de Blazor Server intenta emular un entorno de un solo subproceso para que coincida estrechamente con el modelo WebAssembly en el explorador, que es de un solo subproceso. En todo momento, el trabajo se realiza en exactamente un subproceso, lo que da la impresión de un único subproceso lógico. Nunca se ejecutan dos operaciones simultáneamente.
Evitar las llamadas de bloqueo de subprocesos
Por lo general, no llame a los métodos siguientes en componentes. Los métodos siguientes bloquean el subproceso de ejecución y, por tanto, impiden que la aplicación reanude el trabajo hasta que se complete Task:
Nota
Los ejemplos de documentación de Blazor que usan los métodos de bloqueo de subprocesos mencionados en esta sección solo usan los métodos con fines de demostración, no como guía de codificación recomendada. Por ejemplo, algunas demostraciones de código de componentes simulan un proceso de ejecución larga mediante una llamada a Thread.Sleep.
Invocación de métodos de componentes externamente para actualizar el estado
En caso de que un componente deba actualizarse en función de un evento externo, como un temporizador u otras notificaciones, use el método InvokeAsync, que envía la ejecución de código al contexto de sincronización de Blazor. Consideremos, por ejemplo, el siguiente servicio de notificador capaz de notificar el estado actualizado a cualquier componente de escucha. Se puede llamar al método Update desde cualquier lugar de la aplicación.
TimerService.cs:
using System;
using Microsoft.Extensions.Logging;
public class TimerService : IDisposable
{
private int elapsedCount;
private readonly ILogger<TimerService> logger;
private readonly NotifierService notifier;
private System.Timers.Timer? timer;
public TimerService(NotifierService notifier,
ILogger<TimerService> logger)
{
this.notifier = notifier;
this.logger = logger;
}
public void Start()
{
if (timer is null)
{
timer = new();
timer.AutoReset = true;
timer.Interval = 10000;
timer.Elapsed += HandleTimer;
timer.Enabled = true;
logger.LogInformation("Started");
}
}
private async void HandleTimer(object? source,
System.Timers.ElapsedEventArgs e)
{
elapsedCount += 1;
await notifier.Update("elapsedCount", elapsedCount);
logger.LogInformation($"elapsedCount: {elapsedCount}");
}
public void Dispose()
{
timer?.Dispose();
}
}
NotifierService.cs:
using System;
using System.Threading.Tasks;
public class NotifierService
{
public async Task Update(string key, int value)
{
if (Notify != null)
{
await Notify.Invoke(key, value);
}
}
public event Func<string, int, Task>? Notify;
}
Registre los servicios:
En una aplicación Blazor WebAssembly, registre los servicios como singletons en
Program.cs:builder.Services.AddSingleton<NotifierService>(); builder.Services.AddSingleton<TimerService>();En una aplicación Blazor Server, registre los servicios como con ámbito en
Program.cs:builder.Services.AddScoped<NotifierService>(); builder.Services.AddScoped<TimerService>();
Use el elemento NotifierService para actualizar un componente.
Pages/ReceiveNotifications.razor:
@page "/receive-notifications"
@implements IDisposable
@inject NotifierService Notifier
@inject TimerService Timer
<h1>Receive Notifications</h1>
<h2>Timer Service</h2>
<button @onclick="StartTimer">Start Timer</button>
<h2>Notifications</h2>
<p>
Status:
@if (lastNotification.key is not null)
{
<span>@lastNotification.key = @lastNotification.value</span>
}
else
{
<span>Awaiting first notification</span>
}
</p>
@code {
private (string key, int value) lastNotification;
protected override void OnInitialized()
{
Notifier.Notify += OnNotify;
}
public async Task OnNotify(string key, int value)
{
await InvokeAsync(() =>
{
lastNotification = (key, value);
StateHasChanged();
});
}
private void StartTimer()
{
Timer.Start();
}
public void Dispose()
{
Notifier.Notify -= OnNotify;
}
}
En el ejemplo anterior:
NotifierServiceinvoca el métodoOnNotifydel componente fuera del contexto de sincronización de Blazor.InvokeAsyncse utiliza para cambiar al contexto correcto y poner una representación en cola. Para obtener más información, vea Representación de componentes de Blazor de ASP.NET Core.- El componente implementa IDisposable. El elemento delegado
OnNotifyanula su inscripción en el métodoDispose, al que llama el marco cuando se desecha el componente. Para obtener más información, vea Ciclo de vida de los componentes de ASP.NET Core Razor.
Uso de @key para controlar la conservación de elementos y componentes
Cuando se representa una lista de elementos o componentes, y esos elementos o componentes cambian posteriormente, Blazor debe decidir cuáles de los elementos o componentes anteriores se pueden conservar y cómo asignar objetos de modelo a ellos. Normalmente, este proceso es automático y se puede pasar por alto, pero hay casos en los que puede que queramos controlarlo.
Considere los componentes Details y People siguientes:
- El componente
Detailsrecibe datos (Data) del componente primarioPeople, que se muestra en un elemento<input>. Cualquier elemento<input>determinado que se muestra puede recibir el foco de la página del usuario cuando selecciona uno de los elementos<input>. - El componente
Peoplecrea una lista de objetos Person para mostrar mediante el componenteDetails. Cada tres segundos, se agrega una nueva persona a la colección.
Esta demostración le permite:
- Seleccionar un elemento
<input>de entre varios componentesDetailsrepresentados. - Estudiar el comportamiento del foco de la página a medida que crece automáticamente la colección People.
Shared/Details.razor:
<input value="@Data" />
@code {
[Parameter]
public string? Data { get; set; }
}
En el componente People siguiente, cada iteración de agregar a una persona en OnTimerCallback da lugar a la recompilación de Blazor de toda la colección. El foco de la página permanece en la misma posición de índice de los elementos <input>, por lo que el foco cambia cada vez que se agrega a una persona. Desplazar el foco fuera de lo que seleccionó el usuario no es un comportamiento deseable. Tras demostrar el comportamiento deficiente con el componente siguiente, el atributo de directiva @key se usa para mejorar la experiencia del usuario.
Pages/People.razor:
@page "/people"
@using System.Timers
@implements IDisposable
@foreach (var person in people)
{
<Details Data="@person.Data" />
}
@code {
private Timer timer = new Timer(3000);
public List<Person> people =
new()
{
{ new Person { Data = "Person 1" } },
{ new Person { Data = "Person 2" } },
{ new Person { Data = "Person 3" } }
};
protected override void OnInitialized()
{
timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
timer.Start();
}
private void OnTimerCallback()
{
_ = InvokeAsync(() =>
{
people.Insert(0,
new Person
{
Data = $"INSERTED {DateTime.Now.ToString("hh:mm:ss tt")}"
});
StateHasChanged();
});
}
public void Dispose() => timer.Dispose();
public class Person
{
public string? Data { get; set; }
}
}
El contenido de la colección people cambia porque se inserten, eliminen o reordenen entradas. La nueva representación puede dar lugar a diferencias de comportamiento visibles. Cada vez que se inserta a una persona en la colección people, el elemento anterior del elemento actualmente centrado recibe el foco. Se pierde el foco del usuario.
El proceso de asignación de elementos o componentes a una colección se puede controlar con el atributo de directiva @key. El uso de @key garantiza la conservación de elementos o componentes en función del valor de la clave. Si el componente Details del ejemplo anterior se codifica en el elemento person, Blazor omite la nueva representación de componentes Details que no han cambiado.
Para modificar el componente People para usar el atributo de directiva @key con la colección people, actualice el elemento <Details> a lo siguiente:
<Details @key="person" Data="@person.Data" />
Cuando la colección people cambia, la asociación entre las instancias de Details y las de person se mantiene. Cuando se inserta un elemento Person al principio de la colección, se inserta una nueva instancia Details en la posición correspondiente. Las demás instancias permanecerán inalteradas. Por lo tanto, el foco del usuario no se pierde a medida que se agregan personas a la colección.
Otras actualizaciones de colección muestran el mismo comportamiento cuando se usa el atributo de directiva @key:
- Si una instancia se elimina de la colección, solo se quitará de la interfaz de usuario la instancia de componente correspondiente. Las demás instancias permanecerán inalteradas.
- Si las entradas de colección se reordenan, las instancias de componente correspondientes se conservarán y reordenarán en la interfaz de usuario.
Importante
Las claves son locales de cada componente o elemento contenedor. Las claves no se comparan globalmente en todo el documento.
Cuándo debe usarse @key
Normalmente, usar @key tiene sentido cada vez que una lista se represente (por ejemplo, en un bloque foreach) y haya un valor adecuado para definir el elemento @key.
También puede usar @key para conservar un subárbol de elemento o componente cuando un objeto no cambia, como se muestra en los ejemplos siguientes.
Ejemplo 1:
<li @key="person">
<input value="@person.Data" />
</li>
Ejemplo 2:
<div @key="person">
@* other HTML elements *@
</div>
Si una instancia person cambia, la directiva de atributo @key fuerza a Blazor a:
- Descartar los elementos
<li>o<div>enteros y sus descendientes. - Volver a generar el subárbol dentro de la interfaz de usuario con nuevos elementos y componentes.
Esto es útil para garantizar que no se conserva ningún estado de la interfaz de usuario cuando la colección cambia dentro de un subárbol.
Ámbito de @key
La directiva de atributo @key tiene como ámbito los otros elementos de su elemento primario.
Considere el ejemplo siguiente. Las claves first y second se comparan entre sí en el mismo ámbito del elemento externo <div>:
<div>
<div @key="first">...</div>
<div @key="second">...</div>
</div>
En el ejemplo siguiente se muestran las claves first y second en sus propios ámbitos, no relacionadas entre sí y sin que una influya en la otra. Cada ámbito @key solo se aplica a su elemento <div> primario en lugar de en los elementos <div> primarios:
<div>
<div @key="first">...</div>
</div>
<div>
<div @key="second">...</div>
</div>
Para el componente Details mostrado anteriormente, los ejemplos siguientes representan datos de person dentro del mismo ámbito @key y muestran casos de uso típicos para @key:
<div>
@foreach (var person in people)
{
<Details @key="person" Data="@person.Data" />
}
</div>
@foreach (var person in people)
{
<div @key="person">
<Details Data="@person.Data" />
</div>
}
<ol>
@foreach (var person in people)
{
<li @key="person">
<Details Data="@person.Data" />
</li>
}
</ol>
Los ejemplos siguientes solo tienen como ámbito de @key el elemento <div> o <li> que rodea cada instancia del componente Details. Por lo tanto, los datos de person para cada miembro de la colección people no tienen clave en cada instancia de person en los componentes Details representados. Al usar @key, evite los patrones siguientes:
@foreach (var person in people)
{
<div>
<Details @key="person" Data="@person.Data" />
</div>
}
<ol>
@foreach (var person in people)
{
<li>
<Details @key="person" Data="@person.Data" />
</li>
}
</ol>
Cuándo no debe usarse @key
Las representaciones con @key repercuten en el rendimiento. El rendimiento no se ve especialmente afectado, pero pese a ello debemos especificar @key únicamente cuando mantener los componentes o elementos suponga un beneficio para la aplicación.
Aun cuando @key no se use, Blazor conserva las instancias de componentes y elementos secundarios lo máximo posible. La única ventaja de utilizar @key es el control sobre cómo se asignan instancias de modelo a las instancias de componente conservadas, en lugar de Blazor, que selecciona la asignación.
Valores que se pueden usar para @key
Por lo general, lo lógico es proporcionar uno de los siguientes valores en @key:
- Instancias de objeto de modelo. Por ejemplo, la instancia
Person(person) se usó en el ejemplo anterior. Esto garantiza la conservación en función de la igualdad de las referencias de objetos. - Identificadores únicos. Por ejemplo, los identificadores únicos pueden basarse en valores de clave principal de tipo
int,stringoGuid.
Asegúrese de que los valores usados en @key no entran en conflicto. Si se detectan valores en conflicto en el mismo elemento primario, Blazor produce una excepción porque no puede asignar de forma determinista elementos o componentes antiguos a nuevos elementos o componentes. Use exclusivamente valores distintos, como instancias de objeto o valores de clave principal.
Aplicación de un atributo
En los componentes se pueden aplicar atributos con la directiva @attribute. En el siguiente ejemplo se aplica el atributo [Authorize] a la clase del componente:
@page "/"
@attribute [Authorize]
Atributos de elementos HTML condicionales
Las propiedades de atributos de elementos HTML se establecen condicionalmente en función del valor de .NET. Si el valor es false o null, no se establece la propiedad. Si el valor es true, se establece la propiedad.
En el siguiente ejemplo, IsCompleted determina si se establece la propiedad <input> del elemento checked.
Pages/ConditionalAttribute.razor:
@page "/conditional-attribute"
<label>
<input type="checkbox" checked="@IsCompleted" />
Is Completed?
</label>
<button @onclick="@(() => IsCompleted = !IsCompleted)">
Change IsCompleted
</button>
@code {
[Parameter]
public bool IsCompleted { get; set; }
}
Para obtener más información, vea Referencia sobre la sintaxis de Razor para ASP.NET Core.
Advertencia
Algunos atributos HTML, como aria-pressed, no funcionan correctamente cuando el tipo de .NET es bool. En esos casos, use un tipo string en lugar de bool.
HTML sin formato
Normalmente, las cadenas se representan mediante nodos de texto DOM, lo que significa que cualquier marcado que esas cadenas puedan contener se omite y se trata como texto literal. Para representar HTML sin formato, encapsule el contenido HTML en un valor MarkupString. El valor se analiza como HTML o SVG y se inserta en el DOM.
Advertencia
La representación de HTML sin formato creado a partir de un origen que no es de confianza entraña un riesgo de seguridad y debe evitarse siempre.
En el siguiente ejemplo se describe cómo usar el tipo MarkupString para agregar un bloque de contenido HTML estático a la salida representada de un componente.
Pages/MarkupStringExample.razor:
@page "/markup-string-example"
@((MarkupString)myMarkup)
@code {
private string myMarkup =
"<p class=\"text-danger\">This is a dangerous <em>markup string</em>.</p>";
}
Plantillas de Razor
Los fragmentos de representación se pueden definir mediante la sintaxis de plantilla de Razor para definir un fragmento de interfaz de usuario. Las plantillas de Razor utilizan el siguiente formato:
@<{HTML tag}>...</{HTML tag}>
En el siguiente ejemplo se muestra cómo especificar los valores RenderFragment y RenderFragment<TValue> y las plantillas de representación directamente en un componente. Los fragmentos de representación también se pueden pasar como argumentos a componentes con plantilla.
Pages/RazorTemplate.razor:
@page "/razor-template"
@timeTemplate
@petTemplate(new Pet { Name = "Nutty Rex" })
@code {
private RenderFragment timeTemplate = @<p>The time is @DateTime.Now.</p>;
private RenderFragment<Pet> petTemplate = (pet) => @<p>Pet: @pet.Name</p>;
private class Pet
{
public string? Name { get; set; }
}
}
Salida representada del código anterior:
<p>The time is 4/19/2021 8:54:46 AM.</p>
<p>Pet: Nutty Rex</p>
Recursos estáticos
Blazor sigue la convención de las aplicaciones de ASP.NET Core para los recursos estáticos. Los recursos estáticos se encuentran en la carpeta web root (wwwroot) del proyecto o en la carpeta wwwroot.
Use una ruta de acceso relativa base (/) para hacer referencia a la raíz web de un activo estático. En el ejemplo siguiente, logo.png se encuentra físicamente en la carpeta {PROJECT ROOT}/wwwroot/images. {PROJECT ROOT} es la raíz del proyecto de la aplicación.
<img alt="Company logo" src="/images/logo.png" />
Los componentes no admiten la notación de virgulilla-barra diagonal (~/).
Para más información sobre cómo establecer la ruta de acceso base de una aplicación, vea Hospedaje e implementación de ASP.NET Core Blazor.
Las aplicaciones auxiliares de etiquetas no se admiten en los componentes
Tag Helpers no se admiten en los componentes. Para proporcionar una funcionalidad similar a la de las aplicaciones auxiliares de etiquetas en Blazor, cree un componente con la misma funcionalidad que la aplicación auxiliar de etiquetas y use el componente en su lugar.
Imágenes con formato Scalable Vector Graphics (SVG)
Como Blazor representa HTML, se pueden usar imágenes compatibles con el explorador, incluidas imágenes con formato Scalable Vector Graphics (SVG) (.svg), mediante la etiqueta <img>:
<img alt="Example image" src="image.svg" />
Del mismo modo, se pueden usar imágenes SVG en las reglas de CSS de un archivo de hoja de estilos (.css):
.element-class {
background-image: url("image.svg");
}
Blazor admite el elemento <foreignObject> para visualizar HTML arbitrario dentro de un archivo SVG. El marcado puede representar HTML arbitrario, un elemento RenderFragment o un componente Razor.
En el siguiente ejemplo se muestra:
- Visualización de un elemento
string(@message). - Enlace bidireccional con un elemento
<input>y un campovalue. - Un componente
Robot.
<svg width="200" height="200" xmlns="http://www.w3.org/2000/svg">
<rect x="0" y="0" rx="10" ry="10" width="200" height="200" stroke="black"
fill="none" />
<foreignObject x="20" y="20" width="160" height="160">
<p>@message</p>
</foreignObject>
</svg>
<svg xmlns="http://www.w3.org/2000/svg">
<foreignObject width="200" height="200">
<label>
Two-way binding:
<input @bind="value" @bind:event="oninput" />
</label>
</foreignObject>
</svg>
<svg xmlns="http://www.w3.org/2000/svg">
<foreignObject>
<Robot />
</foreignObject>
</svg>
@code {
private string message = "Lorem ipsum dolor sit amet, consectetur adipiscing " +
"elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.";
private string value;
}
Comportamiento de la representación de espacios en blanco
A menos que se use la directiva @preservewhitespace con un valor de true, el espacio en blanco adicional se quita de forma predeterminada si:
- Está delante o detrás de un elemento.
- Está delante o detrás de un parámetro RenderFragment/RenderFragment<TValue> (por ejemplo, el contenido secundario pasado a otro componente).
- Precede o sigue a un bloque de código de C#, como
@ifo@foreach.
La eliminación de espacios en blanco puede afectar a la salida representada cuando se usa una regla de CSS, como white-space: pre. Para deshabilitar esta optimización de rendimiento y conservar el espacio en blanco, realice una de las siguientes acciones:
- Agregue la directiva
@preservewhitespace trueen la parte superior del archivo de Razor (.razor) para aplicar las preferencias a un componente específico. - Agregue la directiva
@preservewhitespace truedentro de un archivo_Imports.razorpara aplicar la preferencia a un subdirectorio o a todo el proyecto.
En la mayoría de los casos, no se requiere ninguna acción, ya que las aplicaciones normalmente seguirán funcionando con normalidad (pero más rápido). Si la eliminación de espacios en blanco provoca un problema de representación en un componente determinado, use @preservewhitespace true en ese componente para deshabilitar esta optimización.
Compatibilidad con parámetro de tipo genérico
La directiva @typeparam declara un parámetro de tipo genérico para la clase de componente generada:
@typeparam TItem
Se admite la sintaxis de C# con restricciones de tipo where:
@typeparam TEntity where TEntity : IEntity
Para más información, consulte los siguientes artículos.
- Referencia sobre la sintaxis de Razor para ASP.NET Core
- Componentes con plantilla de Blazor en ASP.NET Core
Representación de componentes Razor desde JavaScript
Los componentes Razor se pueden representar dinámicamente desde JavaScript (JS) para las aplicaciones de JS existentes.
Para representar un componente Razor desde JS, registre el componente como componente raíz para la representación de JS y asigne un identificador al componente:
En una aplicación Blazor Server, modifique la llamada a AddServerSideBlazor en
Program.cs:builder.Services.AddServerSideBlazor(options => { options.RootComponents.RegisterForJavaScript<Counter>(identifier: "counter"); });Nota
El ejemplo de código anterior requiere un espacio de nombres para los componentes de la aplicación (por ejemplo,
using BlazorSample.Pages;) en el archivoProgram.cs.En una aplicación Blazor WebAssembly, llame a
RegisterForJavaScripten RootComponents enProgram.cs:builder.RootComponents.RegisterForJavaScript<Counter>(identifier: "counter");Nota
El ejemplo de código anterior requiere un espacio de nombres para los componentes de la aplicación (por ejemplo,
using BlazorSample.Pages;) en el archivoProgram.cs.
Cargue Blazor en la aplicación de JS (blazor.server.js o blazor.webassembly.js). Represente el componente desde JS en un elemento contenedor mediante el identificador registrado y pase los parámetros del componente según sea necesario:
let containerElement = document.getElementById('my-counter');
await Blazor.rootComponents.add(containerElement, 'counter', { incrementAmount: 10 });
Elementos Blazor personalizados
La compatibilidad experimental está disponible para crear elementos personalizados mediante el Microsoft.AspNetCore.Components.CustomElementspaquete NuGet. Los elementos personalizados usan interfaces HTML estándar para implementar elementos HTML personalizados.
Registre un componente raíz como un elemento personalizado:
En una aplicación Blazor Server, modifique la llamada a AddServerSideBlazor en
Program.cs:builder.Services.AddServerSideBlazor(options => { options.RootComponents.RegisterAsCustomElement<Counter>("my-counter"); });Nota
El ejemplo de código anterior requiere un espacio de nombres para los componentes de la aplicación (por ejemplo,
using BlazorSample.Pages;) en el archivoProgram.cs.En una aplicación Blazor WebAssembly, llame a
RegisterAsCustomElementen RootComponents enProgram.cs:builder.RootComponents.RegisterAsCustomElement<Counter>("my-counter");Nota
El ejemplo de código anterior requiere un espacio de nombres para los componentes de la aplicación (por ejemplo,
using BlazorSample.Pages;) en el archivoProgram.cs.
Use el elemento personalizado con cualquier marco web. Por ejemplo, el elemento personalizado del contador anterior se usa en una aplicación React con el siguiente marcado:
<my-counter increment-amount={incrementAmount}></my-counter>
Para obtener un ejemplo completo de cómo crear elementos personalizados con Blazor, vea el proyecto de ejemplo de elementos Blazor.
Advertencia
La característica de elementos personalizados es actualmente experimental, no tiene soporte técnico y está sujeta a cambios o podría retirarse en cualquier momento. Agradecemos sus comentarios sobre la forma en que este enfoque concreto cumple sus requisitos.
Generación de componentes Angular y React
Genere componentes de JavaScript (JS) específicos del marco de trabajo a partir de componentes Razor para marcos web, como Angular o React. Esta funcionalidad no se incluye con .NET 6, pero la admite la nueva compatibilidad para representar componentes Razor de JS. El ejemplo de generación de componentes de JS de GitHub muestra cómo generar componentes Angular y React a partir de componentes Razor. Consulte el archivo README.md de la aplicación de ejemplo de GitHub para obtener información adicional.
Advertencia
Las características de los componentes Angular y React son actualmente experimentales, no tienen soporte técnico y están sujeta a cambios o podrían retirarse en cualquier momento. Agradecemos sus comentarios sobre la forma en que este enfoque concreto cumple sus requisitos.
Las aplicaciones de Blazor se crean usando componentes de Razor . Un componente es una parte independiente de la interfaz de usuario (UI) con lógica de procesamiento para habilitar el comportamiento dinámico. Los componentes se pueden anidar, reutilizar, compartir entre proyectos y usarse en aplicaciones MVC y de Razor Pages.
Clases de componentes
Los componentes se implementan en archivos de componentes de Razor con una extensión de archivo .razor mediante una combinación de C# y marcado HTML.
Sintaxis de Razor
Los componentes usan la sintaxis de Razor. Los componentes, las directivas y los atributos de directiva usan ampliamente dos características de Razor. Estas son palabras clave reservadas con el prefijo @ que aparecen en el marcado de Razor:
- Directivas: cambian el modo en que se analiza o funciona un marcado de componente. Por ejemplo, la directiva
@pageespecifica un componente enrutable con una plantilla de ruta, y se puede acceder directamente a esta mediante la solicitud de un usuario en el explorador en una dirección URL específica. - Atributos de directiva: cambian el modo en que se analiza o funciona un elemento de componente. Por ejemplo, el atributo de directiva
@bindde un elemento<input>enlaza los datos al valor del elemento.
Las directivas y los atributos de directiva que se usan en los componentes se explican más adelante en este artículo y en otros artículos del conjunto de documentación de Blazor. Para obtener información general sobre la sintaxis de Razor, vea Referencia sobre la sintaxis de Razor para ASP.NET Core.
Nombres
El nombre de un componente debe empezar por mayúsculas:
ProductDetail.razores válido.productDetail.razorno es válido.
Entre las convenciones de nomenclatura comunes de Blazor que se usan en toda la documentación de Blazor se incluyen:
- Las rutas de archivo de componente usan Pascal Case† y aparecen antes de mostrar ejemplos de código de componente. Las rutas indican ubicaciones de carpeta típicas. Por ejemplo,
Pages/ProductDetail.razorindica que el componenteProductDetailtiene el nombre de archivoProductDetail.razory reside en la carpetaPagesde la aplicación. - Las rutas de archivo de componente para los componentes enrutables hacen coincidir sus direcciones URL con guiones que aparecen para espacios entre palabras en la plantilla de ruta de un componente. Por ejemplo, se solicita un componente
ProductDetailcon una plantilla de ruta de/product-detail(@page "/product-detail") en un explorador en la dirección URL/product-detailrelativa.
†Pascal Case (mayúscula y minúscula camel) es una convención de nomenclatura sin espacios y signos de puntuación y con la primera letra de cada palabra en mayúsculas, incluida la primera palabra.
Enrutamiento
El enrutamiento en Blazor se consigue proporcionando una plantilla de ruta a cada componente accesible en la aplicación con una directiva @page. Cuando se compila un archivo de Razor con una directiva @page, la clase generada recibe un elemento RouteAttribute que especifica la plantilla de ruta. En tiempo de ejecución, el enrutador busca las clases de componentes con un atributo RouteAttribute y representa el componente que tenga una plantilla de ruta que coincida con la dirección URL solicitada.
El componente HelloWorld siguiente usa una plantilla de ruta de /hello-world. La página web que se representa para el componente se encuentra en la dirección URL /hello-world relativa. Al ejecutar una aplicación de Blazor localmente con el protocolo, el host y el puerto predeterminados, el componente HelloWorld se solicita en el explorador en https://localhost:5001/hello-world. Los componentes que generan páginas web suelen residir en la carpeta Pages, pero puede usar cualquier carpeta para contener componentes, incluidas las carpetas anidadas.
Pages/HelloWorld.razor:
@page "/hello-world"
<h1>Hello World!</h1>
El componente anterior se carga en el explorador en /hello-world independientemente de si agrega o no el componente a la navegación de la interfaz de usuario de la aplicación. De manera opcional, se pueden agregar componentes al componente NavMenu para que aparezca un vínculo al componente en la navegación basada en la interfaz de usuario de la aplicación.
Para el componente HelloWorld anterior, agregue el componente NavLink siguiente al componente NavMenu. Agregue el componente NavLink en un nuevo elemento de lista (<li>...</li>) entre las etiquetas de lista desordenadas (<ul>...</ul>).
Shared/NavMenu.razor:
<li class="nav-item px-3">
<NavLink class="nav-link" href="hello-world">
<span class="oi oi-list-rich" aria-hidden="true"></span> Hello World!
</NavLink>
</li>
Para obtener más información, incluidas las descripciones de los componentes NavLink y NavMenu, vea Enrutamiento de Blazor de ASP.NET Core.
marcado
La interfaz de usuario de un componente se define mediante la sintaxis de Razor, que consta de marcado de Razor, C# y HTML. Cuando una aplicación se compila, el marcado HTML y la lógica de representación de C# se convierten en una clase de componente. El nombre de la clase generada coincide con el nombre del archivo.
Los miembros de la clase de componente se definen en uno o varios bloques @code. En los bloques @code, el estado del componente se especifica y se procesa con C#:
- Inicializadores de propiedad y campo.
- Valores de parámetro de argumentos pasados por componentes primarios y parámetros de ruta.
- Métodos para el control de eventos de usuario, eventos de ciclo de vida y lógica de componentes personalizados.
Los miembros de componente se usan en la lógica de representación mediante expresiones de C# que comienzan por el símbolo @. Por ejemplo, un campo de C# se representa anteponiendo el prefijo @ al nombre del campo. En el componente Markup siguiente se evalúa y representa lo siguiente:
headingFontStylepara el valor de la propiedad CSSfont-styledel elemento de encabezado.headingTextpara el contenido del elemento de encabezado.
Pages/Markup.razor:
@page "/markup"
<h1 style="font-style:@headingFontStyle">@headingText</h1>
@code {
private string headingFontStyle = "italic";
private string headingText = "Put on your new Blazor!";
}
Nota
En los ejemplos de la documentación de Blazor se especifica el modificador de acceso private para los miembros privados. Los miembros privados tienen como ámbito la clase de un componente. Sin embargo, C# asume el modificador de acceso private cuando no hay ningún modificador de acceso presente, por lo que marcar explícitamente los miembros como "private" en su propio código es opcional. Para obtener más información sobre los modificadores de acceso, vea Modificadores de acceso (Guía de programación de C#).
El marco de Blazor procesa un componente internamente como un árbol de representación, que es la combinación del Document Object Model (DOM) y Cascading Style Sheet Object Model (CSSOM) de un componente. Una vez que el componente se ha representado inicialmente, se vuelve a generar su árbol de representación en respuesta a eventos. Blazor compara el nuevo árbol de representación con el anterior y aplica las modificaciones necesarias al DOM del explorador para su presentación. Para obtener más información, vea Representación de componentes de Blazor de ASP.NET Core.
Los componentes son clases de C# normales, y se pueden colocar en cualquier parte dentro de un proyecto. Los componentes que generan páginas web suelen residir en la carpeta Pages. Los componentes que no son de página se colocan con frecuencia en la carpeta Shared o en una carpeta personalizada agregada al proyecto.
Componentes anidados
Para que los componentes puedan incluir otros componentes, hay que declararlos usando la sintaxis HTML. El marcado para utilizar un componente se parece a una etiqueta HTML en la que el nombre de la etiqueta es el tipo de componente.
Tenga en cuenta el componente Heading siguiente, que pueden usar otros componentes para mostrar un encabezado.
Shared/Heading.razor:
<h1 style="font-style:@headingFontStyle">Heading Example</h1>
@code {
private string headingFontStyle = "italic";
}
El marcado siguiente del componente HeadingExample representa el componente Heading anterior en la ubicación donde aparece la etiqueta <Heading />.
Pages/HeadingExample.razor:
@page "/heading-example"
<Heading />
Si un componente contiene un elemento HTML cuyo nombre empieza por mayúsculas y no coincide con un nombre de componente con el mismo espacio de nombres, se emite una advertencia que indica que el elemento tiene un nombre inesperado. Al agregar una directiva @using relativa al espacio de nombres del componente, se permite que el componente esté disponible, lo que resuelve la advertencia. Para obtener más información, vea la sección Espacios de nombres.
El ejemplo de componente Heading que se muestra en esta sección no tiene una directiva @page, por lo que el componente Heading no es accesible directamente para un usuario por medio de una solicitud directa en el explorador. Sin embargo, cualquier componente con una directiva @page se puede anidar en otro componente. Si el componente Heading era accesible directamente mediante la inclusión de @page "/heading" en la parte superior de su archivo de Razor, el componente se representaría para las solicitudes del explorador en /heading y /heading-example.
Espacios de nombres
Por lo general, el espacio de nombres de un componente se deriva del espacio de nombres raíz de la aplicación y de la ubicación del componente (carpeta) dentro de la aplicación. Así, si el espacio de nombres raíz de la aplicación es BlazorSample y el componente Counter reside en la carpeta Pages:
- El espacio de nombres del componente
CounteresBlazorSample.Pages. - El nombre de tipo completo del componente es
BlazorSample.Pages.Counter.
En el caso de las carpetas personalizadas que contienen componentes, agregue una directiva @using al componente primario o al archivo _Imports.razor de la aplicación. En el ejemplo siguiente se ponen a disposición los componentes de la carpeta Components:
@using BlazorSample.Components
Nota
Las directivas @using del archivo _Imports.razor solo se aplican a los archivos Razor (.razor), por lo que no se aplican a los archivos C# (.cs).
También se puede hacer referencia a los componentes mediante sus nombres completos, lo cual no requiere el uso de la directiva @using. En el ejemplo siguiente se hace referencia directamente al componente ProductDetail de la carpeta Components de la aplicación:
<BlazorSample.Components.ProductDetail />
El espacio de nombres de un componente creado con Razor se basa en lo siguiente (por orden de prioridad):
- Directiva
@namespaceen el marcado del archivo de Razor (por ejemplo,@namespace BlazorSample.CustomNamespace). - Elemento
RootNamespacedel proyecto en el archivo de proyecto (por ejemplo,<RootNamespace>BlazorSample</RootNamespace>). - Nombre del proyecto, tomado del nombre de archivo del archivo de proyecto (
.csproj) y la ruta de acceso de la raíz del proyecto al componente. Por ejemplo, el marco de trabajo resuelve{PROJECT ROOT}/Pages/Index.razorcon un espacio de nombres de proyecto deBlazorSample(BlazorSample.csproj) en el espacio de nombresBlazorSample.Pagesdel componenteIndex.{PROJECT ROOT}es la ruta raíz del proyecto. Los componentes de C# siguen las reglas de los enlaces de nombres. En el caso del componenteIndexde este ejemplo, los componentes en el ámbito serían todos los componentes:- Ejecute
Pagesen la misma carpeta. - Los componentes en la raíz del proyecto que no especifiquen explícitamente un espacio de nombres diferente.
- Ejecute
No se admite lo siguiente:
- La calificación
global::. - La importación de componentes con instrucciones
usingcon alias. Por ejemplo,@using Foo = Barno es compatible. - Nombres parcialmente completos. Por ejemplo, no puede agregar
@using BlazorSamplea un componente y, después, hacer referencia al componenteNavMenuen la carpetaSharedde la aplicación (Shared/NavMenu.razor) con<Shared.NavMenu></Shared.NavMenu>.
Compatibilidad parcial de clases
Los componentes se generan como clases parciales de C# y se crean mediante cualquiera de los siguientes métodos:
- Un solo archivo contiene código de C# definido en uno o varios bloques
@code, marcado HTML y marcado de Razor. Las plantillas de proyecto de Blazor definen sus componentes con este método de archivo único. - El marcado HTML y de Razor se colocan en un archivo de Razor (
.razor). El código de C# se coloca en un archivo de código subyacente definido como una clase parcial (.cs).
Nota
Una hoja de estilos de componente que define estilos específicos del componente es un archivo independiente (.css). El aislamiento de CSS de Blazor se describe más adelante en Aislamiento de CSS de Blazor de ASP.NET Core.
En el siguiente ejemplo se muestra el componente Counter predeterminado con un bloque @code en una aplicación generada a partir de una plantilla de proyecto de Blazor. El marcado y el código de C# se encuentran en el mismo archivo. Este es el método más común que se aplica en la creación de componentes.
Pages/Counter.razor:
@page "/counter"
<h1>Counter</h1>
<p>Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
private int currentCount = 0;
private void IncrementCount()
{
currentCount++;
}
}
El componente Counter siguiente divide el marcado HTML y de Razor del código de C# mediante un archivo de código subyacente con una clase parcial:
Pages/CounterPartialClass.razor:
@page "/counter-partial-class"
<h1>Counter</h1>
<p>Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
Pages/CounterPartialClass.razor.cs:
namespace BlazorSample.Pages
{
public partial class CounterPartialClass
{
private int currentCount = 0;
void IncrementCount()
{
currentCount++;
}
}
}
Las directivas @using del archivo _Imports.razor solo se aplican a los archivos Razor (.razor), por lo que no se aplican a los archivos C# (.cs). Agregue los espacios de nombres que sean necesarios a un archivo de clase parcial.
Espacios de nombres típicos usados por los componentes:
using System.Net.Http;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.Forms;
using Microsoft.AspNetCore.Components.Routing;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.Web.Virtualization;
using Microsoft.JSInterop;
Los espacios de nombres típicos también incluyen el espacio de nombres de la aplicación y el espacio de nombres correspondiente a la carpeta Shared de la aplicación:
using BlazorSample;
using BlazorSample.Shared;
Especificación de una clase base
La directiva @inherits se usa para especificar la clase base de un componente. En el siguiente ejemplo se muestra cómo un componente puede heredar una clase base para proporcionar las propiedades y los métodos del componente. La clase base BlazorRocksBase deriva de ComponentBase.
Pages/BlazorRocks.razor:
@page "/blazor-rocks"
@inherits BlazorRocksBase
<h1>@BlazorRocksText</h1>
BlazorRocksBase.cs:
using Microsoft.AspNetCore.Components;
namespace BlazorSample
{
public class BlazorRocksBase : ComponentBase
{
public string BlazorRocksText { get; set; } =
"Blazor rocks the browser!";
}
}
Parámetros del componente
Los parámetros del componente pasan los datos a componentes y se definen por medio de propiedades de C# públicas en la clase del componente con el atributo [Parameter]. En el ejemplo siguiente, un tipo de referencia integrado (System.String) y un tipo de referencia definido por el usuario (PanelBody) se pasan como parámetros del componente.
PanelBody.cs:
public class PanelBody
{
public string Text { get; set; }
public string Style { get; set; }
}
Shared/ParameterChild.razor:
<div class="card w-25" style="margin-bottom:15px">
<div class="card-header font-weight-bold">@Title</div>
<div class="card-body" style="font-style:@Body.Style">
@Body.Text
</div>
</div>
@code {
[Parameter]
public string Title { get; set; } = "Set By Child";
[Parameter]
public PanelBody Body { get; set; } =
new()
{
Text = "Set by child.",
Style = "normal"
};
}
Advertencia
Se admite el suministro de valores iniciales para los parámetros del componente, pero no cree un componente que escriba en sus propios parámetros después de que el componente se represente por primera vez. Para obtener más información, vea la sección Parámetros sobrescritos de este artículo.
Los parámetros del componente Title y Body del componente ParameterChild se establecen mediante argumentos en la etiqueta HTML que representa la instancia del componente. El componente ParameterParent siguiente representa dos componentes ParameterChild:
- El primer componente
ParameterChildse representa sin proporcionar argumentos de parámetro. - El segundo componente
ParameterChildrecibe valores paraTitleyBodydel componenteParameterParent, que usa una expresión de C# explícita para establecer los valores de las propiedades dePanelBody.
Pages/ParameterParent.razor:
@page "/parameter-parent"
<h1>Child component (without attribute values)</h1>
<ParameterChild />
<h1>Child component (with attribute values)</h1>
<ParameterChild Title="Set by Parent"
Body="@(new PanelBody() { Text = "Set by parent.", Style = "italic" })" />
El siguiente marcado HTML representado del componente ParameterParent muestra los valores predeterminados del componente ParameterChild cuando el componente ParameterParent no proporciona valores de parámetros del componente. Cuando el componente ParameterParent proporciona valores de parámetros del componente, estos reemplazan los valores predeterminados del componente ParameterChild.
Nota
A modo de aclaración, las clases de estilo CSS representadas no se muestran en el siguiente marcado HTML representado.
<h1>Child component (without attribute values)</h1>
<div>
<div>Set By Child</div>
<div>Set by child.</div>
</div>
<h1>Child component (with attribute values)</h1>
<div>
<div>Set by Parent</div>
<div>Set by parent.</div>
</div>
Asigne un campo, propiedad o resultado de C# de un método a un parámetro del componente como un valor de atributo HTML mediante el símbolo Razor reservado de @. El componente ParameterParent2 siguiente muestra cuatro instancias del componente ParameterChild anterior y establece sus valores de parámetro Title en:
- El valor del campo
title. - El resultado del método
GetTitlede C#. - La fecha local actual en formato largo con ToLongDateString, que usa una expresión implícita de C#.
- La propiedad
Titledel objetopanelData.
Pages/ParameterParent2.razor:
@page "/parameter-parent-2"
<ParameterChild Title="@title" />
<ParameterChild Title="@GetTitle()" />
<ParameterChild Title="@DateTime.Now.ToLongDateString()" />
<ParameterChild Title="@panelData.Title" />
@code {
private string title = "From Parent field";
private PanelData panelData = new();
private string GetTitle()
{
return "From Parent method";
}
private class PanelData
{
public string Title { get; set; } = "From Parent object";
}
}
Nota
Al asignar un miembro de C# a un parámetro del componente, agregue un prefijo al miembro con el símbolo @ y nunca agregue un prefijo al atributo HTML del parámetro.
Correcto:
<ParameterChild Title="@title" />
Incorrecto:
<ParameterChild @Title="title" />
A diferencia de lo que ocurre en Razor Pages (.cshtml), Blazor no puede realizar el trabajo asincrónico en una expresión de Razor mientras se representa un componente. Esto se debe a que Blazor está diseñado para representar interfaces de usuario interactivas. En una interfaz de usuario interactiva, la pantalla debe mostrar siempre algo, por lo que no tiene sentido bloquear el flujo de representación. En su lugar, el trabajo asincrónico se realiza durante uno de los eventos asincrónicos del ciclo de vida. Después de cada evento asincrónico del ciclo de vida, el componente puede representarse de nuevo. La sintaxis de Razor siguiente no se admite:
<ParameterChild Title="@await ..." />
El código del ejemplo anterior genera un error del compilador si se compila la aplicación:
El operador "await" solo se puede usar dentro de un método asincrónico. Marque este método con el modificador "Async" y cambie su tipo de valor devuelto a "Task".
Para obtener un valor para el parámetro Title en el ejemplo anterior de manera asincrónica, el componente puede usar el evento del ciclo de vida OnInitializedAsync , como se muestra en el ejemplo siguiente:
<ParameterChild Title="@title" />
@code {
private string title;
protected override async Task OnInitializedAsync()
{
title = await ...;
}
}
Para obtener más información, vea Ciclo de vida de los componentes de ASP.NET Core Razor.
RazorNo se admite el uso de una expresión de explícita para concatenar texto con el resultado de una expresión para la asignación a un parámetro. En el ejemplo siguiente se busca concatenar el texto "Set by " con el valor de propiedad de un objeto. Aunque esta sintaxis se admite en una página de Razor (.cshtml), no es válida para la asignación al parámetro Title del elemento secundario de un componente. La sintaxis de Razor siguiente no se admite:
<ParameterChild Title="Set by @(panelData.Title)" />
El código del ejemplo anterior genera un error del compilador si se compila la aplicación:
Los atributos del componente no admiten contenido complejo (C# y marcado combinado).
Para admitir la asignación de un valor compuesto, utilice un método, un campo o una propiedad. En el ejemplo siguiente se realiza la concatenación de "Set by " y el valor de propiedad de un objeto en el método de C# GetTitle:
Pages/ParameterParent3.razor:
@page "/parameter-parent-3"
<ParameterChild Title="@GetTitle()" />
@code {
private PanelData panelData = new();
private string GetTitle() => $"Set by {panelData.Title}";
private class PanelData
{
public string Title { get; set; } = "Parent";
}
}
Para obtener más información, vea Referencia sobre la sintaxis de Razor para ASP.NET Core.
Advertencia
Se admite el suministro de valores iniciales para los parámetros del componente, pero no cree un componente que escriba en sus propios parámetros después de que el componente se represente por primera vez. Para obtener más información, vea la sección Parámetros sobrescritos de este artículo.
Los parámetros del componente se deben declarar como propiedades automáticas, lo que significa que no deben contener lógica personalizada en sus descriptores de acceso get o set. Por ejemplo, la siguiente propiedad StartData es una propiedad automática:
[Parameter]
public DateTime StartData { get; set; }
No coloque la lógica personalizada en el descriptor de acceso get o set porque los parámetros del componente están pensados exclusivamente para su uso como canal para que un componente primario envíe información a un componente secundario. Si un descriptor de acceso set de una propiedad de componente secundario contiene lógica que provoca la repetición de la representación del componente primario, se produce un bucle de representación infinito.
Para transformar un valor de parámetro recibido:
- Deje la propiedad del parámetro como propiedad automática para representar los datos sin procesar proporcionados.
- Cree otra propiedad o método que proporcione los datos transformados en función de la propiedad del parámetro.
Invalide OnParametersSetAsync para transformar un parámetro recibido cada vez que se reciban nuevos datos.
Se admite la escritura de un valor inicial en un parámetro de componente porque las asignaciones de valores iniciales no interfieren con la representación automática del componente de Blazor. La siguiente asignación de la configuración local DateTime actual de DateTime.Now a StartData es una sintaxis válida en un componente:
[Parameter]
public DateTime StartData { get; set; } = DateTime.Now;
Después de la asignación inicial de DateTime.Now, no asigne un valor a StartData en el código para desarrolladores. Para obtener más información, vea la sección Parámetros sobrescritos de este artículo.
Parámetros de ruta
Los componentes pueden especificar parámetros de ruta en la plantilla de ruta de la directiva @page. El enrutador de Blazor usa parámetros de ruta para rellenar los parámetros de componente correspondientes con el mismo nombre.
Se admiten parámetros de ruta opcionales. En el ejemplo siguiente, el parámetro opcional text asigna el valor del segmento de ruta a la propiedad Text del componente. Si el segmento no está presente, el valor de Text se establece en "fantastic" en el método de ciclo de vida OnInitialized.
Pages/RouteParameter.razor:
@page "/route-parameter/{text?}"
<h1>Blazor is @Text!</h1>
@code {
[Parameter]
public string Text { get; set; }
protected override void OnInitialized()
{
Text = Text ?? "fantastic";
}
}
Para obtener información sobre los parámetros de ruta comodín ({*pageRoute}), que capturan rutas de acceso en varios límites de carpeta, vea Enrutamiento de Blazor de ASP.NET Core.
Parámetros sobrescritos
El marco Blazor impone con carácter general una asignación de parámetro de componentes primarios a secundarios segura:
- Los parámetros no se sobrescriben de forma inesperada.
- Los efectos secundarios se minimizan. Por ejemplo,se evitan representaciones adicionales, ya que pueden crear bucles de representación infinitos.
Un componente secundario recibe nuevos valores de parámetro que posiblemente sobrescriban los valores existentes cuando el componente primario vuelva a representarse. Sobrescribir valores de parámetro en un componente secundario de forma accidental suele producirse al desarrollar el componente con uno o varios parámetros enlazados a datos y cuando el desarrollador escribe directamente en un parámetro del elemento secundario:
- El componente secundario se representa con uno o varios valores de parámetro del componente primario.
- El elemento secundario escribe directamente en el valor de un parámetro.
- El componente primario vuelve a representar el valor del parámetro del elemento secundario y lo sobrescribe.
La posibilidad de sobrescribir valores de parámetro se extiende también a los descriptores de acceso set de propiedades del componente secundario.
Importante
Nuestra orientación general es no crear componentes que escriban directamente en sus propios parámetros después de que el componente se represente por primera vez.
Considere el componente Expander erróneo siguiente que:
- Representa el contenido secundario.
- Alterna la visualización del contenido secundario con un parámetro de componente (
Expanded). - El componente escribe directamente en el parámetro
Expanded, que muestra el problema con los parámetros sobrescritos y debe evitarse.
Después de que el componente Expander siguiente muestre el método incorrecto para este escenario, se muestra un componente Expander modificado para mostrar el método correcto. Los ejemplos siguientes se pueden colocar en una aplicación de ejemplo local para experimentar los comportamientos descritos.
Shared/Expander.razor:
<div @onclick="Toggle" class="card bg-light mb-3" style="width:30rem">
<div class="card-body">
<h2 class="card-title">Toggle (<code>Expanded</code> = @Expanded)</h2>
@if (Expanded)
{
<p class="card-text">@ChildContent</p>
}
</div>
</div>
@code {
[Parameter]
public bool Expanded { get; set; }
[Parameter]
public RenderFragment ChildContent { get; set; }
private void Toggle()
{
Expanded = !Expanded;
}
}
El componente Expander se agrega al siguiente componente primario ExpanderExample que podría llamar a StateHasChanged:
- Al llamar a StateHasChanged en el código de desarrollador se notifica a un componente que su estado ha cambiado y normalmente desencadena la repetición de la representación del componente para actualizar la interfaz de usuario. StateHasChanged se trata detalladamente más adelante en Ciclo de vida de los componentes de ASP.NET Core Razor y Representación de componentes de Blazor de ASP.NET Core.
- El atributo de directiva
@onclickdel botón asocia un controlador de eventos al eventoonclickdel botón. El control de eventos se trata detalladamente más adelante en Control de eventos de Blazor en ASP.NET Core.
Pages/ExpanderExample.razor:
@page "/expander-example"
<Expander Expanded="true">
Expander 1 content
</Expander>
<Expander Expanded="true" />
<button @onclick="StateHasChanged">
Call StateHasChanged
</button>
Al principio, los componentes Expander se comportan de manera independiente cuando se alternan sus propiedades de Expanded. Los componentes secundarios mantienen sus estados según lo previsto. Cuando se llama a StateHasChanged en el componente principal, el parámetro Expanded del primer componente secundario se restablece nuevamente en su valor inicial (true). No se restablece el valor Expanded del segundo componente de Expander, porque no se representa ningún contenido secundario en el segundo componente.
Para mantener el estado en el escenario anterior, use un campo privado en el componente Expander para mantener su estado de alternancia.
El siguiente componente Expander revisado:
- Acepta el valor del parámetro de componente
Expandeddel componente principal. - Asigna el valor del parámetro de componente a un campo privado (
expanded) en el eventoOnInitialized. - Usa el campo privado para mantener su estado de alternancia interno, que muestra cómo evitar escribir directamente en un parámetro.
Nota
Los consejos de esta sección se aplican a una lógica similar en los descriptores de acceso set de parámetros de componente, lo que puede dar lugar a efectos secundarios no deseados similares.
Shared/Expander.razor:
<div @onclick="Toggle" class="card bg-light mb-3" style="width:30rem">
<div class="card-body">
<h2 class="card-title">Toggle (<code>expanded</code> = @expanded)</h2>
@if (expanded)
{
<p class="card-text">@ChildContent</p>
}
</div>
</div>
@code {
private bool expanded;
[Parameter]
public bool Expanded { get; set; }
[Parameter]
public RenderFragment ChildContent { get; set; }
protected override void OnInitialized()
{
expanded = Expanded;
}
private void Toggle()
{
expanded = !expanded;
}
}
Para obtener información adicional, consulte Error de enlace bidireccional de Blazor (dotnet/aspnetcore #24599).
Contenido secundario
Los componentes pueden definir el contenido de otro componente. El componente de asignación proporciona el contenido entre las etiquetas de apertura y cierre del componente secundario.
En el siguiente ejemplo, el componente RenderFragmentChild tiene una propiedad ChildContent que representa un segmento de la interfaz de usuario que se va a representar como RenderFragment. La posición de ChildContent en el marcado de Razor del componente es donde el contenido se representa en la salida HTML final.
Shared/RenderFragmentChild.razor:
<div class="card w-25" style="margin-bottom:15px">
<div class="card-header font-weight-bold">Child content</div>
<div class="card-body">@ChildContent</div>
</div>
@code {
[Parameter]
public RenderFragment ChildContent { get; set; }
}
Importante
La propiedad que recibe el contenido de RenderFragment debe denominarse ChildContent por convención.
El componente RenderFragmentParent siguiente proporciona contenido para representar el componente RenderFragmentChild mediante la colocación del contenido dentro de las etiquetas de apertura y cierre del componente secundario.
Pages/RenderFragmentParent.razor:
@page "/render-fragment-parent"
<h1>Render child content</h1>
<RenderFragmentChild>
Content of the child component is supplied
by the parent component.
</RenderFragmentChild>
Debido a la forma en que Blazor representa el contenido secundario, la representación de componentes dentro de un bucle for requiere una variable de índice local si se usa la variable de bucle incremental en el contenido del componente RenderFragmentChild. El ejemplo siguiente se puede agregar al componente RenderFragmentParent anterior:
<h1>Three children with an index variable</h1>
@for (int c = 0; c < 3; c++)
{
var current = c;
<RenderFragmentChild>
Count: @current
</RenderFragmentChild>
}
Como alternativa, use un bucle foreach con Enumerable.Range en lugar de un bucle for. El ejemplo siguiente se puede agregar al componente RenderFragmentParent anterior:
<h1>Second example of three children with an index variable</h1>
@foreach (var c in Enumerable.Range(0,3))
{
<RenderFragmentChild>
Count: @c
</RenderFragmentChild>
}
Para obtener información sobre cómo se puede usar un componente RenderFragment como plantilla para la interfaz de usuario del componente, consulte los siguientes artículos:
- Componentes con plantilla de Blazor en ASP.NET Core
- Procedimientos recomendados de rendimiento de Blazor en ASP.NET Core
Expansión de atributos y parámetros arbitrarios
Los componentes pueden capturar y representar más atributos aparte de los parámetros declarados del componente. Los atributos adicionales se pueden capturar en un diccionario y luego expandirse en un elemento cuando el componente se representa por medio del atributo de directiva de @attributesRazor. Este escenario es útil para definir un componente que genera un elemento de marcado que admite diversas personalizaciones. Por ejemplo, definir atributos por separado para un elemento <input> que admite muchos parámetros puede ser un engorro.
En el componente Splat siguiente:
- El primer elemento
<input>(id="useIndividualParams") usa parámetros de componente individuales. - El segundo elemento
<input>(id="useAttributesDict") usa la expansión de atributos.
Pages/Splat.razor:
@page "/splat"
<input id="useIndividualParams"
maxlength="@maxlength"
placeholder="@placeholder"
required="@required"
size="@size" />
<input id="useAttributesDict"
@attributes="InputAttributes" />
@code {
private string maxlength = "10";
private string placeholder = "Input placeholder text";
private string required = "required";
private string size = "50";
private Dictionary<string, object> InputAttributes { get; set; } =
new()
{
{ "maxlength", "10" },
{ "placeholder", "Input placeholder text" },
{ "required", "required" },
{ "size", "50" }
};
}
Los elementos <input> representados en la página web son idénticos:
<input id="useIndividualParams"
maxlength="10"
placeholder="Input placeholder text"
required="required"
size="50">
<input id="useAttributesDict"
maxlength="10"
placeholder="Input placeholder text"
required="required"
size="50">
Para aceptar atributos arbitrarios, defina un parámetro de componente con la propiedad CaptureUnmatchedValues establecida en true:
@code {
[Parameter(CaptureUnmatchedValues = true)]
public Dictionary<string, object> InputAttributes { get; set; }
}
La propiedad CaptureUnmatchedValues en [Parameter] permite que el parámetro coincida con todos los atributos que no coinciden con ningún otro parámetro. Un componente solamente puede definir un parámetro con CaptureUnmatchedValues. El tipo de propiedad que se usa con CaptureUnmatchedValues se debe poder asignar desde Dictionary<string, object> con claves de cadena. El uso de IEnumerable<KeyValuePair<string, object>> o IReadOnlyDictionary<string, object> también son opciones en este escenario.
La posición de @attributes relativa a la posición de los atributos de elemento es importante. Cuando @attributes se expande en el elemento, los atributos se procesan de derecha a izquierda (del último al primero). Veamos el siguiente ejemplo de un componente primario que usa un componente secundario:
Shared/AttributeOrderChild1.razor:
<div @attributes="AdditionalAttributes" extra="5" />
@code {
[Parameter(CaptureUnmatchedValues = true)]
public IDictionary<string, object> AdditionalAttributes { get; set; }
}
Pages/AttributeOrderParent1.razor:
@page "/attribute-order-parent-1"
<AttributeOrderChild1 extra="10" />
El atributo extra del componente AttributeOrderChild1 se establece a la derecha de @attributes. El elemento <div> representado del componente AttributeOrderParent1 contiene extra="5" cuando se pasa a través del atributo adicional, ya que los atributos se procesan de derecha a izquierda (de último a primero):
<div extra="5" />
En el ejemplo siguiente, el orden de extra y @attributes se invierte en el elemento <div> del componente secundario:
Shared/AttributeOrderChild2.razor:
<div extra="5" @attributes="AdditionalAttributes" />
@code {
[Parameter(CaptureUnmatchedValues = true)]
public IDictionary<string, object> AdditionalAttributes { get; set; }
}
Pages/AttributeOrderParent2.razor:
@page "/attribute-order-parent-2"
<AttributeOrderChild2 extra="10" />
El elemento <div> en la página web representada del componente primario contiene extra="10" cuando se pasa por el atributo adicional:
<div extra="10" />
Captura de referencias a componentes
Las referencias de componentes proporcionan una manera de hacer referencia a una instancia de componente para emitir comandos. Para capturar una referencia de componente:
- Agregue un atributo
@refal componente secundario. - Defina un campo con el mismo tipo que el componente secundario.
Cuando el componente se representa, el campo se rellena con la instancia del componente. Tras ello, se pueden invocar métodos de .NET en la instancia.
Tenga en cuenta el componente ReferenceChild siguiente, que registra un mensaje cuando se llama a su método ChildMethod.
Shared/ReferenceChild.razor:
@using Microsoft.Extensions.Logging
@inject ILogger<ReferenceChild> logger
@code {
public void ChildMethod(int value)
{
logger.LogInformation("Received {Value} in ChildMethod", value);
}
}
Una referencia de componente solo se rellena después de que el componente se represente, y su salida incluye el elemento de ReferenceChild. Hasta que se represente el componente, no hay nada a lo que hacer referencia.
Para manipular las referencias de componente una vez finalizada la representación del componente, use los métodos OnAfterRender o OnAfterRenderAsync.
Para usar una variable de referencia con un controlador de eventos, use una expresión lambda o asigne el delegado de controlador de eventos en los métodos OnAfterRender o OnAfterRenderAsync. Esto garantiza que la variable de referencia se asigna antes de que se asigne el controlador de eventos.
El siguiente método lambda usa el componente ReferenceChild anterior.
Pages/ReferenceParent1.razor:
@page "/reference-parent-1"
<button @onclick="@(() => childComponent.ChildMethod(5))">
Call <code>ReferenceChild.ChildMethod</code> with an argument of 5
</button>
<ReferenceChild @ref="childComponent" />
@code {
private ReferenceChild childComponent;
}
El siguiente método de delegado usa el componente ReferenceChild anterior.
Pages/ReferenceParent2.razor:
@page "/reference-parent-2"
<button @onclick="callChildMethod">
Call <code>ReferenceChild.ChildMethod</code> with an argument of 5
</button>
<ReferenceChild @ref="childComponent" />
@code {
private ReferenceChild childComponent;
private Action callChildMethod;
protected override void OnAfterRender(bool firstRender)
{
if (firstRender)
{
callChildMethod = CallChildMethod;
}
}
private void CallChildMethod()
{
childComponent.ChildMethod(5);
}
}
Use una colección para hacer referencia a componentes de un bucle. En el ejemplo siguiente:
- Los componentes se agregan a List<T>.
- Se crea un botón para cada componente que desencadena el método
ChildMethoddel componente correspondiente por su índice de componente en List<T>.
Pages/ReferenceParent3.razor mediante el componente ReferenceChild anterior:
@page "/reference-parent-3"
<ul>
@for (int i = 0; i < 5; i++)
{
var index = i;
var v = r.Next(1000);
<li>
<ReferenceChild @ref="childComponent" />
<button @onclick="@(() => callChildMethod(index, v))">
Component index @index: Call <code>ReferenceChild.ChildMethod(@v)</code>
</button>
</li>
}
</ul>
@code {
private Random r = new();
private List<ReferenceChild> components = new();
private Action<int, int> callChildMethod;
private ReferenceChild childComponent
{
set => components.Add(value);
}
protected override void OnAfterRender(bool firstRender)
{
if (firstRender)
{
callChildMethod = CallChildMethod;
}
}
private void CallChildMethod(int index, int value)
{
components.ElementAt(index).ChildMethod(value);
}
}
Si bien la captura de referencias de componentes emplea una sintaxis similar a la de la captura de referencias de elementos, esta no es una característica de interoperabilidad de JavaScript. Las referencias de componente no se pasan al código JavaScript. Solo se usan en código .NET.
Importante
No use referencias de componentes para mutar el estado de los componentes secundarios. En su lugar, use parámetros de componente declarativos normales para pasar datos a componentes secundarios. El uso de parámetros de componente da como resultado componentes secundarios que se volverán a representar automáticamente justo cuando corresponda. Para obtener más información, consulte la sección de parámetros del componente y el artículo Enlace de datos de ASP.NET Core Blazor.
Contexto de sincronización
Blazor usa un contexto de sincronización (SynchronizationContext) para aplicar un único subproceso lógico de ejecución. Los métodos de ciclo de vida de un componente y las devoluciones de llamada de eventos que Blazor genere se ejecutan en el contexto de sincronización.
El contexto de sincronización de Blazor Server intenta emular un entorno de un solo subproceso para que coincida estrechamente con el modelo WebAssembly en el explorador, que es de un solo subproceso. En todo momento, el trabajo se realiza en exactamente un subproceso, lo que da la impresión de un único subproceso lógico. Nunca se ejecutan dos operaciones simultáneamente.
Evitar las llamadas de bloqueo de subprocesos
Por lo general, no llame a los métodos siguientes en componentes. Los métodos siguientes bloquean el subproceso de ejecución y, por tanto, impiden que la aplicación reanude el trabajo hasta que se complete Task:
Nota
Los ejemplos de documentación de Blazor que usan los métodos de bloqueo de subprocesos mencionados en esta sección solo usan los métodos con fines de demostración, no como guía de codificación recomendada. Por ejemplo, algunas demostraciones de código de componentes simulan un proceso de ejecución larga mediante una llamada a Thread.Sleep.
Invocación de métodos de componentes externamente para actualizar el estado
En caso de que un componente deba actualizarse en función de un evento externo, como un temporizador u otras notificaciones, use el método InvokeAsync, que envía la ejecución de código al contexto de sincronización de Blazor. Consideremos, por ejemplo, el siguiente servicio de notificador capaz de notificar el estado actualizado a cualquier componente de escucha. Se puede llamar al método Update desde cualquier lugar de la aplicación.
TimerService.cs:
using System;
using System.Timers;
using Microsoft.Extensions.Logging;
public class TimerService : IDisposable
{
private int elapsedCount;
private readonly ILogger<TimerService> logger;
private readonly NotifierService notifier;
private Timer timer;
public TimerService(NotifierService notifier, ILogger<TimerService> logger)
{
this.notifier = notifier;
this.logger = logger;
}
public void Start()
{
if (timer is null)
{
timer = new();
timer.AutoReset = true;
timer.Interval = 10000;
timer.Elapsed += HandleTimer;
timer.Enabled = true;
logger.LogInformation("Started");
}
}
private async void HandleTimer(object source, ElapsedEventArgs e)
{
elapsedCount += 1;
await notifier.Update("elapsedCount", elapsedCount);
logger.LogInformation($"elapsedCount: {elapsedCount}");
}
public void Dispose()
{
timer?.Dispose();
}
}
NotifierService.cs:
using System;
using System.Threading.Tasks;
public class NotifierService
{
public async Task Update(string key, int value)
{
if (Notify != null)
{
await Notify.Invoke(key, value);
}
}
public event Func<string, int, Task> Notify;
}
Registre los servicios:
En una aplicación Blazor WebAssembly, registre los servicios como singletons en
Program.cs:builder.Services.AddSingleton<NotifierService>(); builder.Services.AddSingleton<TimerService>();En una aplicación Blazor Server, registre los servicios como con ámbito en
Startup.ConfigureServices:services.AddScoped<NotifierService>(); services.AddScoped<TimerService>();
Use el elemento NotifierService para actualizar un componente.
Pages/ReceiveNotifications.razor:
@page "/receive-notifications"
@implements IDisposable
@inject NotifierService Notifier
@inject TimerService Timer
<h1>Receive Notifications</h1>
<h2>Timer Service</h2>
<button @onclick="StartTimer">Start Timer</button>
<h2>Notifications</h2>
<p>
Status:
@if (lastNotification.key is not null)
{
<span>@lastNotification.key = @lastNotification.value</span>
}
else
{
<span>Awaiting first notification</span>
}
</p>
@code {
private (string key, int value) lastNotification;
protected override void OnInitialized()
{
Notifier.Notify += OnNotify;
}
public async Task OnNotify(string key, int value)
{
await InvokeAsync(() =>
{
lastNotification = (key, value);
StateHasChanged();
});
}
private void StartTimer()
{
Timer.Start();
}
public void Dispose()
{
Notifier.Notify -= OnNotify;
}
}
En el ejemplo anterior:
NotifierServiceinvoca el métodoOnNotifydel componente fuera del contexto de sincronización de Blazor.InvokeAsyncse utiliza para cambiar al contexto correcto y poner una representación en cola. Para obtener más información, vea Representación de componentes de Blazor de ASP.NET Core.- El componente implementa IDisposable. El elemento delegado
OnNotifyanula su inscripción en el métodoDispose, al que llama el marco cuando se desecha el componente. Para obtener más información, vea Ciclo de vida de los componentes de ASP.NET Core Razor.
Uso de @key para controlar la conservación de elementos y componentes
Cuando se representa una lista de elementos o componentes, y esos elementos o componentes cambian posteriormente, Blazor debe decidir cuáles de los elementos o componentes anteriores se pueden conservar y cómo asignar objetos de modelo a ellos. Normalmente, este proceso es automático y se puede pasar por alto, pero hay casos en los que puede que queramos controlarlo.
Considere los componentes Details y People siguientes:
- El componente
Detailsrecibe datos (Data) del componente primarioPeople, que se muestra en un elemento<input>. Cualquier elemento<input>determinado que se muestra puede recibir el foco de la página del usuario cuando selecciona uno de los elementos<input>. - El componente
Peoplecrea una lista de objetos Person para mostrar mediante el componenteDetails. Cada tres segundos, se agrega una nueva persona a la colección.
Esta demostración le permite:
- Seleccionar un elemento
<input>de entre varios componentesDetailsrepresentados. - Estudiar el comportamiento del foco de la página a medida que crece automáticamente la colección People.
Shared/Details.razor:
<input value="@Data" />
@code {
[Parameter]
public string Data { get; set; }
}
En el componente People siguiente, cada iteración de agregar a una persona en OnTimerCallback da lugar a la recompilación de Blazor de toda la colección. El foco de la página permanece en la misma posición de índice de los elementos <input>, por lo que el foco cambia cada vez que se agrega a una persona. Desplazar el foco fuera de lo que seleccionó el usuario no es un comportamiento deseable. Tras demostrar el comportamiento deficiente con el componente siguiente, el atributo de directiva @key se usa para mejorar la experiencia del usuario.
Pages/People.razor:
@page "/people"
@using System.Timers
@implements IDisposable
@foreach (var person in people)
{
<Details Data="@person.Data" />
}
@code {
private Timer timer = new Timer(3000);
public List<Person> people =
new()
{
{ new Person { Data = "Person 1" } },
{ new Person { Data = "Person 2" } },
{ new Person { Data = "Person 3" } }
};
protected override void OnInitialized()
{
timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
timer.Start();
}
private void OnTimerCallback()
{
_ = InvokeAsync(() =>
{
people.Insert(0,
new Person
{
Data = $"INSERTED {DateTime.Now.ToString("hh:mm:ss tt")}"
});
StateHasChanged();
});
}
public void Dispose() => timer.Dispose();
public class Person
{
public string Data { get; set; }
}
}
El contenido de la colección people cambia porque se inserten, eliminen o reordenen entradas. La nueva representación puede dar lugar a diferencias de comportamiento visibles. Cada vez que se inserta a una persona en la colección people, el elemento anterior del elemento actualmente centrado recibe el foco. Se pierde el foco del usuario.
El proceso de asignación de elementos o componentes a una colección se puede controlar con el atributo de directiva @key. El uso de @key garantiza la conservación de elementos o componentes en función del valor de la clave. Si el componente Details del ejemplo anterior se codifica en el elemento person, Blazor omite la nueva representación de componentes Details que no han cambiado.
Para modificar el componente People para usar el atributo de directiva @key con la colección people, actualice el elemento <Details> a lo siguiente:
<Details @key="person" Data="@person.Data" />
Cuando la colección people cambia, la asociación entre las instancias de Details y las de person se mantiene. Cuando se inserta un elemento Person al principio de la colección, se inserta una nueva instancia Details en la posición correspondiente. Las demás instancias permanecerán inalteradas. Por lo tanto, el foco del usuario no se pierde a medida que se agregan personas a la colección.
Otras actualizaciones de colección muestran el mismo comportamiento cuando se usa el atributo de directiva @key:
- Si una instancia se elimina de la colección, solo se quitará de la interfaz de usuario la instancia de componente correspondiente. Las demás instancias permanecerán inalteradas.
- Si las entradas de colección se reordenan, las instancias de componente correspondientes se conservarán y reordenarán en la interfaz de usuario.
Importante
Las claves son locales de cada componente o elemento contenedor. Las claves no se comparan globalmente en todo el documento.
Cuándo debe usarse @key
Normalmente, usar @key tiene sentido cada vez que una lista se represente (por ejemplo, en un bloque foreach) y haya un valor adecuado para definir el elemento @key.
También puede usar @key para conservar un subárbol de elemento o componente cuando un objeto no cambia, como se muestra en los ejemplos siguientes.
Ejemplo 1:
<li @key="person">
<input value="@person.Data" />
</li>
Ejemplo 2:
<div @key="person">
@* other HTML elements *@
</div>
Si una instancia person cambia, la directiva de atributo @key fuerza a Blazor a:
- Descartar los elementos
<li>o<div>enteros y sus descendientes. - Volver a generar el subárbol dentro de la interfaz de usuario con nuevos elementos y componentes.
Esto es útil para garantizar que no se conserva ningún estado de la interfaz de usuario cuando la colección cambia dentro de un subárbol.
Ámbito de @key
La directiva de atributo @key tiene como ámbito los otros elementos de su elemento primario.
Considere el ejemplo siguiente. Las claves first y second se comparan entre sí en el mismo ámbito del elemento externo <div>:
<div>
<div @key="first">...</div>
<div @key="second">...</div>
</div>
En el ejemplo siguiente se muestran las claves first y second en sus propios ámbitos, no relacionadas entre sí y sin que una influya en la otra. Cada ámbito @key solo se aplica a su elemento <div> primario en lugar de en los elementos <div> primarios:
<div>
<div @key="first">...</div>
</div>
<div>
<div @key="second">...</div>
</div>
Para el componente Details mostrado anteriormente, los ejemplos siguientes representan datos de person dentro del mismo ámbito @key y muestran casos de uso típicos para @key:
<div>
@foreach (var person in people)
{
<Details @key="person" Data="@person.Data" />
}
</div>
@foreach (var person in people)
{
<div @key="person">
<Details Data="@person.Data" />
</div>
}
<ol>
@foreach (var person in people)
{
<li @key="person">
<Details Data="@person.Data" />
</li>
}
</ol>
Los ejemplos siguientes solo tienen como ámbito de @key el elemento <div> o <li> que rodea cada instancia del componente Details. Por lo tanto, los datos de person para cada miembro de la colección people no tienen clave en cada instancia de person en los componentes Details representados. Al usar @key, evite los patrones siguientes:
@foreach (var person in people)
{
<div>
<Details @key="person" Data="@person.Data" />
</div>
}
<ol>
@foreach (var person in people)
{
<li>
<Details @key="person" Data="@person.Data" />
</li>
}
</ol>
Cuándo no debe usarse @key
Las representaciones con @key repercuten en el rendimiento. El rendimiento no se ve especialmente afectado, pero pese a ello debemos especificar @key únicamente cuando mantener los componentes o elementos suponga un beneficio para la aplicación.
Aun cuando @key no se use, Blazor conserva las instancias de componentes y elementos secundarios lo máximo posible. La única ventaja de utilizar @key es el control sobre cómo se asignan instancias de modelo a las instancias de componente conservadas, en lugar de Blazor, que selecciona la asignación.
Valores que se pueden usar para @key
Por lo general, lo lógico es proporcionar uno de los siguientes valores en @key:
- Instancias de objeto de modelo. Por ejemplo, la instancia
Person(person) se usó en el ejemplo anterior. Esto garantiza la conservación en función de la igualdad de las referencias de objetos. - Identificadores únicos. Por ejemplo, los identificadores únicos pueden basarse en valores de clave principal de tipo
int,stringoGuid.
Asegúrese de que los valores usados en @key no entran en conflicto. Si se detectan valores en conflicto en el mismo elemento primario, Blazor produce una excepción porque no puede asignar de forma determinista elementos o componentes antiguos a nuevos elementos o componentes. Use exclusivamente valores distintos, como instancias de objeto o valores de clave principal.
Aplicación de un atributo
En los componentes se pueden aplicar atributos con la directiva @attribute. En el siguiente ejemplo se aplica el atributo [Authorize] a la clase del componente:
@page "/"
@attribute [Authorize]
Atributos de elementos HTML condicionales
Las propiedades de atributos de elementos HTML se establecen condicionalmente en función del valor de .NET. Si el valor es false o null, no se establece la propiedad. Si el valor es true, se establece la propiedad.
En el siguiente ejemplo, IsCompleted determina si se establece la propiedad <input> del elemento checked.
Pages/ConditionalAttribute.razor:
@page "/conditional-attribute"
<label>
<input type="checkbox" checked="@IsCompleted" />
Is Completed?
</label>
<button @onclick="@(() => IsCompleted = !IsCompleted)">
Change IsCompleted
</button>
@code {
[Parameter]
public bool IsCompleted { get; set; }
}
Para obtener más información, vea Referencia sobre la sintaxis de Razor para ASP.NET Core.
Advertencia
Algunos atributos HTML, como aria-pressed, no funcionan correctamente cuando el tipo de .NET es bool. En esos casos, use un tipo string en lugar de bool.
HTML sin formato
Normalmente, las cadenas se representan mediante nodos de texto DOM, lo que significa que cualquier marcado que esas cadenas puedan contener se omite y se trata como texto literal. Para representar HTML sin formato, encapsule el contenido HTML en un valor MarkupString. El valor se analiza como HTML o SVG y se inserta en el DOM.
Advertencia
La representación de HTML sin formato creado a partir de un origen que no es de confianza entraña un riesgo de seguridad y debe evitarse siempre.
En el siguiente ejemplo se describe cómo usar el tipo MarkupString para agregar un bloque de contenido HTML estático a la salida representada de un componente.
Pages/MarkupStringExample.razor:
@page "/markup-string-example"
@((MarkupString)myMarkup)
@code {
private string myMarkup =
"<p class=\"text-danger\">This is a dangerous <em>markup string</em>.</p>";
}
Plantillas de Razor
Los fragmentos de representación se pueden definir mediante la sintaxis de plantilla de Razor para definir un fragmento de interfaz de usuario. Las plantillas de Razor utilizan el siguiente formato:
@<{HTML tag}>...</{HTML tag}>
En el siguiente ejemplo se muestra cómo especificar los valores RenderFragment y RenderFragment<TValue> y las plantillas de representación directamente en un componente. Los fragmentos de representación también se pueden pasar como argumentos a componentes con plantilla.
Pages/RazorTemplate.razor:
@page "/razor-template"
@timeTemplate
@petTemplate(new Pet { Name = "Nutty Rex" })
@code {
private RenderFragment timeTemplate = @<p>The time is @DateTime.Now.</p>;
private RenderFragment<Pet> petTemplate = (pet) => @<p>Pet: @pet.Name</p>;
private class Pet
{
public string Name { get; set; }
}
}
Salida representada del código anterior:
<p>The time is 4/19/2021 8:54:46 AM.</p>
<p>Pet: Nutty Rex</p>
Recursos estáticos
Blazor sigue la convención de las aplicaciones de ASP.NET Core para los recursos estáticos. Los recursos estáticos se encuentran en la carpeta web root (wwwroot) del proyecto o en la carpeta wwwroot.
Use una ruta de acceso relativa base (/) para hacer referencia a la raíz web de un activo estático. En el ejemplo siguiente, logo.png se encuentra físicamente en la carpeta {PROJECT ROOT}/wwwroot/images. {PROJECT ROOT} es la raíz del proyecto de la aplicación.
<img alt="Company logo" src="/images/logo.png" />
Los componentes no admiten la notación de virgulilla-barra diagonal (~/).
Para más información sobre cómo establecer la ruta de acceso base de una aplicación, vea Hospedaje e implementación de ASP.NET Core Blazor.
Las aplicaciones auxiliares de etiquetas no se admiten en los componentes
Tag Helpers no se admiten en los componentes. Para proporcionar una funcionalidad similar a la de las aplicaciones auxiliares de etiquetas en Blazor, cree un componente con la misma funcionalidad que la aplicación auxiliar de etiquetas y use el componente en su lugar.
Imágenes con formato Scalable Vector Graphics (SVG)
Como Blazor representa HTML, se pueden usar imágenes compatibles con el explorador, incluidas imágenes con formato Scalable Vector Graphics (SVG) (.svg), mediante la etiqueta <img>:
<img alt="Example image" src="image.svg" />
Del mismo modo, se pueden usar imágenes SVG en las reglas de CSS de un archivo de hoja de estilos (.css):
.element-class {
background-image: url("image.svg");
}
Comportamiento de la representación de espacios en blanco
A menos que se use la directiva @preservewhitespace con un valor de true, el espacio en blanco adicional se quita de forma predeterminada si:
- Está delante o detrás de un elemento.
- Está delante o detrás de un parámetro RenderFragment/RenderFragment<TValue> (por ejemplo, el contenido secundario pasado a otro componente).
- Precede o sigue a un bloque de código de C#, como
@ifo@foreach.
La eliminación de espacios en blanco puede afectar a la salida representada cuando se usa una regla de CSS, como white-space: pre. Para deshabilitar esta optimización de rendimiento y conservar el espacio en blanco, realice una de las siguientes acciones:
- Agregue la directiva
@preservewhitespace trueen la parte superior del archivo de Razor (.razor) para aplicar las preferencias a un componente específico. - Agregue la directiva
@preservewhitespace truedentro de un archivo_Imports.razorpara aplicar la preferencia a un subdirectorio o a todo el proyecto.
En la mayoría de los casos, no se requiere ninguna acción, ya que las aplicaciones normalmente seguirán funcionando con normalidad (pero más rápido). Si la eliminación de espacios en blanco provoca un problema de representación en un componente determinado, use @preservewhitespace true en ese componente para deshabilitar esta optimización.
Compatibilidad con parámetro de tipo genérico
La directiva @typeparam declara un parámetro de tipo genérico para la clase de componente generada:
@typeparam TItem
Para más información, consulte los siguientes artículos.
Las aplicaciones de Blazor se crean usando componentes de Razor . Un componente es una parte independiente de la interfaz de usuario (UI) con lógica de procesamiento para habilitar el comportamiento dinámico. Los componentes se pueden anidar, reutilizar, compartir entre proyectos y usarse en aplicaciones MVC y de Razor Pages.
Clases de componentes
Los componentes se implementan en archivos de componentes de Razor con una extensión de archivo .razor mediante una combinación de C# y marcado HTML.
Sintaxis de Razor
Los componentes usan la sintaxis de Razor. Los componentes, las directivas y los atributos de directiva usan ampliamente dos características de Razor. Estas son palabras clave reservadas con el prefijo @ que aparecen en el marcado de Razor:
- Directivas: cambian el modo en que se analiza o funciona un marcado de componente. Por ejemplo, la directiva
@pageespecifica un componente enrutable con una plantilla de ruta, y se puede acceder directamente a esta mediante la solicitud de un usuario en el explorador en una dirección URL específica. - Atributos de directiva: cambian el modo en que se analiza o funciona un elemento de componente. Por ejemplo, el atributo de directiva
@bindde un elemento<input>enlaza los datos al valor del elemento.
Las directivas y los atributos de directiva que se usan en los componentes se explican más adelante en este artículo y en otros artículos del conjunto de documentación de Blazor. Para obtener información general sobre la sintaxis de Razor, vea Referencia sobre la sintaxis de Razor para ASP.NET Core.
Nombres
El nombre de un componente debe empezar por mayúsculas:
ProductDetail.razores válido.productDetail.razorno es válido.
Entre las convenciones de nomenclatura comunes de Blazor que se usan en toda la documentación de Blazor se incluyen:
- Las rutas de archivo de componente usan Pascal Case† y aparecen antes de mostrar ejemplos de código de componente. Las rutas indican ubicaciones de carpeta típicas. Por ejemplo,
Pages/ProductDetail.razorindica que el componenteProductDetailtiene el nombre de archivoProductDetail.razory reside en la carpetaPagesde la aplicación. - Las rutas de archivo de componente para los componentes enrutables hacen coincidir sus direcciones URL con guiones que aparecen para espacios entre palabras en la plantilla de ruta de un componente. Por ejemplo, se solicita un componente
ProductDetailcon una plantilla de ruta de/product-detail(@page "/product-detail") en un explorador en la dirección URL/product-detailrelativa.
†Pascal Case (mayúscula y minúscula camel) es una convención de nomenclatura sin espacios y signos de puntuación y con la primera letra de cada palabra en mayúsculas, incluida la primera palabra.
Enrutamiento
El enrutamiento en Blazor se consigue proporcionando una plantilla de ruta a cada componente accesible en la aplicación con una directiva @page. Cuando se compila un archivo de Razor con una directiva @page, la clase generada recibe un elemento RouteAttribute que especifica la plantilla de ruta. En tiempo de ejecución, el enrutador busca las clases de componentes con un atributo RouteAttribute y representa el componente que tenga una plantilla de ruta que coincida con la dirección URL solicitada.
El componente HelloWorld siguiente usa una plantilla de ruta de /hello-world. La página web que se representa para el componente se encuentra en la dirección URL /hello-world relativa. Al ejecutar una aplicación de Blazor localmente con el protocolo, el host y el puerto predeterminados, el componente HelloWorld se solicita en el explorador en https://localhost:5001/hello-world. Los componentes que generan páginas web suelen residir en la carpeta Pages, pero puede usar cualquier carpeta para contener componentes, incluidas las carpetas anidadas.
Pages/HelloWorld.razor:
@page "/hello-world"
<h1>Hello World!</h1>
El componente anterior se carga en el explorador en /hello-world independientemente de si agrega o no el componente a la navegación de la interfaz de usuario de la aplicación. De manera opcional, se pueden agregar componentes al componente NavMenu para que aparezca un vínculo al componente en la navegación basada en la interfaz de usuario de la aplicación.
Para el componente HelloWorld anterior, agregue el componente NavLink siguiente al componente NavMenu. Agregue el componente NavLink en un nuevo elemento de lista (<li>...</li>) entre las etiquetas de lista desordenadas (<ul>...</ul>).
Shared/NavMenu.razor:
<li class="nav-item px-3">
<NavLink class="nav-link" href="hello-world">
<span class="oi oi-list-rich" aria-hidden="true"></span> Hello World!
</NavLink>
</li>
Para obtener más información, incluidas las descripciones de los componentes NavLink y NavMenu, vea Enrutamiento de Blazor de ASP.NET Core.
marcado
La interfaz de usuario de un componente se define mediante la sintaxis de Razor, que consta de marcado de Razor, C# y HTML. Cuando una aplicación se compila, el marcado HTML y la lógica de representación de C# se convierten en una clase de componente. El nombre de la clase generada coincide con el nombre del archivo.
Los miembros de la clase de componente se definen en uno o varios bloques @code. En los bloques @code, el estado del componente se especifica y se procesa con C#:
- Inicializadores de propiedad y campo.
- Valores de parámetro de argumentos pasados por componentes primarios y parámetros de ruta.
- Métodos para el control de eventos de usuario, eventos de ciclo de vida y lógica de componentes personalizados.
Los miembros de componente se usan en la lógica de representación mediante expresiones de C# que comienzan por el símbolo @. Por ejemplo, un campo de C# se representa anteponiendo el prefijo @ al nombre del campo. En el componente Markup siguiente se evalúa y representa lo siguiente:
headingFontStylepara el valor de la propiedad CSSfont-styledel elemento de encabezado.headingTextpara el contenido del elemento de encabezado.
Pages/Markup.razor:
@page "/markup"
<h1 style="font-style:@headingFontStyle">@headingText</h1>
@code {
private string headingFontStyle = "italic";
private string headingText = "Put on your new Blazor!";
}
Nota
En los ejemplos de la documentación de Blazor se especifica el modificador de acceso private para los miembros privados. Los miembros privados tienen como ámbito la clase de un componente. Sin embargo, C# asume el modificador de acceso private cuando no hay ningún modificador de acceso presente, por lo que marcar explícitamente los miembros como "private" en su propio código es opcional. Para obtener más información sobre los modificadores de acceso, vea Modificadores de acceso (Guía de programación de C#).
El marco de Blazor procesa un componente internamente como un árbol de representación, que es la combinación del Document Object Model (DOM) y Cascading Style Sheet Object Model (CSSOM) de un componente. Una vez que el componente se ha representado inicialmente, se vuelve a generar su árbol de representación en respuesta a eventos. Blazor compara el nuevo árbol de representación con el anterior y aplica las modificaciones necesarias al DOM del explorador para su presentación. Para obtener más información, vea Representación de componentes de Blazor de ASP.NET Core.
Los componentes son clases de C# normales, y se pueden colocar en cualquier parte dentro de un proyecto. Los componentes que generan páginas web suelen residir en la carpeta Pages. Los componentes que no son de página se colocan con frecuencia en la carpeta Shared o en una carpeta personalizada agregada al proyecto.
Componentes anidados
Para que los componentes puedan incluir otros componentes, hay que declararlos usando la sintaxis HTML. El marcado para utilizar un componente se parece a una etiqueta HTML en la que el nombre de la etiqueta es el tipo de componente.
Tenga en cuenta el componente Heading siguiente, que pueden usar otros componentes para mostrar un encabezado.
Shared/Heading.razor:
<h1 style="font-style:@headingFontStyle">Heading Example</h1>
@code {
private string headingFontStyle = "italic";
}
El marcado siguiente del componente HeadingExample representa el componente Heading anterior en la ubicación donde aparece la etiqueta <Heading />.
Pages/HeadingExample.razor:
@page "/heading-example"
<Heading />
Si un componente contiene un elemento HTML cuyo nombre empieza por mayúsculas y no coincide con un nombre de componente con el mismo espacio de nombres, se emite una advertencia que indica que el elemento tiene un nombre inesperado. Al agregar una directiva @using relativa al espacio de nombres del componente, se permite que el componente esté disponible, lo que resuelve la advertencia. Para obtener más información, vea la sección Espacios de nombres.
El ejemplo de componente Heading que se muestra en esta sección no tiene una directiva @page, por lo que el componente Heading no es accesible directamente para un usuario por medio de una solicitud directa en el explorador. Sin embargo, cualquier componente con una directiva @page se puede anidar en otro componente. Si el componente Heading era accesible directamente mediante la inclusión de @page "/heading" en la parte superior de su archivo de Razor, el componente se representaría para las solicitudes del explorador en /heading y /heading-example.
Espacios de nombres
Por lo general, el espacio de nombres de un componente se deriva del espacio de nombres raíz de la aplicación y de la ubicación del componente (carpeta) dentro de la aplicación. Así, si el espacio de nombres raíz de la aplicación es BlazorSample y el componente Counter reside en la carpeta Pages:
- El espacio de nombres del componente
CounteresBlazorSample.Pages. - El nombre de tipo completo del componente es
BlazorSample.Pages.Counter.
En el caso de las carpetas personalizadas que contienen componentes, agregue una directiva @using al componente primario o al archivo _Imports.razor de la aplicación. En el ejemplo siguiente se ponen a disposición los componentes de la carpeta Components:
@using BlazorSample.Components
Nota
Las directivas @using del archivo _Imports.razor solo se aplican a los archivos Razor (.razor), por lo que no se aplican a los archivos C# (.cs).
También se puede hacer referencia a los componentes mediante sus nombres completos, lo cual no requiere el uso de la directiva @using. En el ejemplo siguiente se hace referencia directamente al componente ProductDetail de la carpeta Components de la aplicación:
<BlazorSample.Components.ProductDetail />
El espacio de nombres de un componente creado con Razor se basa en lo siguiente (por orden de prioridad):
- Directiva
@namespaceen el marcado del archivo de Razor (por ejemplo,@namespace BlazorSample.CustomNamespace). - Elemento
RootNamespacedel proyecto en el archivo de proyecto (por ejemplo,<RootNamespace>BlazorSample</RootNamespace>). - Nombre del proyecto, tomado del nombre de archivo del archivo de proyecto (
.csproj) y la ruta de acceso de la raíz del proyecto al componente. Por ejemplo, el marco de trabajo resuelve{PROJECT ROOT}/Pages/Index.razorcon un espacio de nombres de proyecto deBlazorSample(BlazorSample.csproj) en el espacio de nombresBlazorSample.Pagesdel componenteIndex.{PROJECT ROOT}es la ruta raíz del proyecto. Los componentes de C# siguen las reglas de los enlaces de nombres. En el caso del componenteIndexde este ejemplo, los componentes en el ámbito serían todos los componentes:- Ejecute
Pagesen la misma carpeta. - Los componentes en la raíz del proyecto que no especifiquen explícitamente un espacio de nombres diferente.
- Ejecute
No se admite lo siguiente:
- La calificación
global::. - La importación de componentes con instrucciones
usingcon alias. Por ejemplo,@using Foo = Barno es compatible. - Nombres parcialmente completos. Por ejemplo, no puede agregar
@using BlazorSamplea un componente y, después, hacer referencia al componenteNavMenuen la carpetaSharedde la aplicación (Shared/NavMenu.razor) con<Shared.NavMenu></Shared.NavMenu>.
Compatibilidad parcial de clases
Los componentes se generan como clases parciales de C# y se crean mediante cualquiera de los siguientes métodos:
- Un solo archivo contiene código de C# definido en uno o varios bloques
@code, marcado HTML y marcado de Razor. Las plantillas de proyecto de Blazor definen sus componentes con este método de archivo único. - El marcado HTML y de Razor se colocan en un archivo de Razor (
.razor). El código de C# se coloca en un archivo de código subyacente definido como una clase parcial (.cs).
En el siguiente ejemplo se muestra el componente Counter predeterminado con un bloque @code en una aplicación generada a partir de una plantilla de proyecto de Blazor. El marcado y el código de C# se encuentran en el mismo archivo. Este es el método más común que se aplica en la creación de componentes.
Pages/Counter.razor:
@page "/counter"
<h1>Counter</h1>
<p>Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
private int currentCount = 0;
private void IncrementCount()
{
currentCount++;
}
}
El componente Counter siguiente divide el marcado HTML y de Razor del código de C# mediante un archivo de código subyacente con una clase parcial:
Pages/CounterPartialClass.razor:
@page "/counter-partial-class"
<h1>Counter</h1>
<p>Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
Pages/CounterPartialClass.razor.cs:
namespace BlazorSample.Pages
{
public partial class CounterPartialClass
{
private int currentCount = 0;
void IncrementCount()
{
currentCount++;
}
}
}
Las directivas @using del archivo _Imports.razor solo se aplican a los archivos Razor (.razor), por lo que no se aplican a los archivos C# (.cs). Agregue los espacios de nombres que sean necesarios a un archivo de clase parcial.
Espacios de nombres típicos usados por los componentes:
using System.Net.Http;
using Microsoft.AspNetCore.Components.Forms;
using Microsoft.AspNetCore.Components.Routing;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.JSInterop;
Los espacios de nombres típicos también incluyen el espacio de nombres de la aplicación y el espacio de nombres correspondiente a la carpeta Shared de la aplicación:
using BlazorSample;
using BlazorSample.Shared;
Especificación de una clase base
La directiva @inherits se usa para especificar la clase base de un componente. En el siguiente ejemplo se muestra cómo un componente puede heredar una clase base para proporcionar las propiedades y los métodos del componente. La clase base BlazorRocksBase deriva de ComponentBase.
Pages/BlazorRocks.razor:
@page "/blazor-rocks"
@inherits BlazorRocksBase
<h1>@BlazorRocksText</h1>
BlazorRocksBase.cs:
using Microsoft.AspNetCore.Components;
namespace BlazorSample
{
public class BlazorRocksBase : ComponentBase
{
public string BlazorRocksText { get; set; } =
"Blazor rocks the browser!";
}
}
Parámetros del componente
Los parámetros del componente pasan los datos a componentes y se definen por medio de propiedades de C# públicas en la clase del componente con el atributo [Parameter]. En el ejemplo siguiente, un tipo de referencia integrado (System.String) y un tipo de referencia definido por el usuario (PanelBody) se pasan como parámetros del componente.
PanelBody.cs:
public class PanelBody
{
public string Text { get; set; }
public string Style { get; set; }
}
Shared/ParameterChild.razor:
<div class="card w-25" style="margin-bottom:15px">
<div class="card-header font-weight-bold">@Title</div>
<div class="card-body" style="font-style:@Body.Style">
@Body.Text
</div>
</div>
@code {
[Parameter]
public string Title { get; set; } = "Set By Child";
[Parameter]
public PanelBody Body { get; set; } =
new PanelBody()
{
Text = "Set by child.",
Style = "normal"
};
}
Advertencia
Se admite el suministro de valores iniciales para los parámetros del componente, pero no cree un componente que escriba en sus propios parámetros después de que el componente se represente por primera vez. Para obtener más información, vea la sección Parámetros sobrescritos de este artículo.
Los parámetros del componente Title y Body del componente ParameterChild se establecen mediante argumentos en la etiqueta HTML que representa la instancia del componente. El componente ParameterParent siguiente representa dos componentes ParameterChild:
- El primer componente
ParameterChildse representa sin proporcionar argumentos de parámetro. - El segundo componente
ParameterChildrecibe valores paraTitleyBodydel componenteParameterParent, que usa una expresión de C# explícita para establecer los valores de las propiedades dePanelBody.
Pages/ParameterParent.razor:
@page "/parameter-parent"
<h1>Child component (without attribute values)</h1>
<ParameterChild />
<h1>Child component (with attribute values)</h1>
<ParameterChild Title="Set by Parent"
Body="@(new PanelBody() { Text = "Set by parent.", Style = "italic" })" />
El siguiente marcado HTML representado del componente ParameterParent muestra los valores predeterminados del componente ParameterChild cuando el componente ParameterParent no proporciona valores de parámetros del componente. Cuando el componente ParameterParent proporciona valores de parámetros del componente, estos reemplazan los valores predeterminados del componente ParameterChild.
Nota
A modo de aclaración, las clases de estilo CSS representadas no se muestran en el siguiente marcado HTML representado.
<h1>Child component (without attribute values)</h1>
<div>
<div>Set By Child</div>
<div>Set by child.</div>
</div>
<h1>Child component (with attribute values)</h1>
<div>
<div>Set by Parent</div>
<div>Set by parent.</div>
</div>
Asigne un campo, propiedad o resultado de C# de un método a un parámetro del componente como un valor de atributo HTML mediante el símbolo Razor reservado de @. El componente ParameterParent2 siguiente muestra cuatro instancias del componente ParameterChild anterior y establece sus valores de parámetro Title en:
- El valor del campo
title. - El resultado del método
GetTitlede C#. - La fecha local actual en formato largo con ToLongDateString, que usa una expresión implícita de C#.
- La propiedad
Titledel objetopanelData.
Pages/ParameterParent2.razor:
@page "/parameter-parent-2"
<ParameterChild Title="@title" />
<ParameterChild Title="@GetTitle()" />
<ParameterChild Title="@DateTime.Now.ToLongDateString()" />
<ParameterChild Title="@panelData.Title" />
@code {
private string title = "From Parent field";
private PanelData panelData = new PanelData();
private string GetTitle()
{
return "From Parent method";
}
private class PanelData
{
public string Title { get; set; } = "From Parent object";
}
}
Nota
Al asignar un miembro de C# a un parámetro del componente, agregue un prefijo al miembro con el símbolo @ y nunca agregue un prefijo al atributo HTML del parámetro.
Correcto:
<ParameterChild Title="@title" />
Incorrecto:
<ParameterChild @Title="title" />
A diferencia de lo que ocurre en Razor Pages (.cshtml), Blazor no puede realizar el trabajo asincrónico en una expresión de Razor mientras se representa un componente. Esto se debe a que Blazor está diseñado para representar interfaces de usuario interactivas. En una interfaz de usuario interactiva, la pantalla debe mostrar siempre algo, por lo que no tiene sentido bloquear el flujo de representación. En su lugar, el trabajo asincrónico se realiza durante uno de los eventos asincrónicos del ciclo de vida. Después de cada evento asincrónico del ciclo de vida, el componente puede representarse de nuevo. La sintaxis de Razor siguiente no se admite:
<ParameterChild Title="@await ..." />
El código del ejemplo anterior genera un error del compilador si se compila la aplicación:
El operador "await" solo se puede usar dentro de un método asincrónico. Marque este método con el modificador "Async" y cambie su tipo de valor devuelto a "Task".
Para obtener un valor para el parámetro Title en el ejemplo anterior de manera asincrónica, el componente puede usar el evento del ciclo de vida OnInitializedAsync , como se muestra en el ejemplo siguiente:
<ParameterChild Title="@title" />
@code {
private string title;
protected override async Task OnInitializedAsync()
{
title = await ...;
}
}
Para obtener más información, vea Ciclo de vida de los componentes de ASP.NET Core Razor.
RazorNo se admite el uso de una expresión de explícita para concatenar texto con el resultado de una expresión para la asignación a un parámetro. En el ejemplo siguiente se busca concatenar el texto "Set by " con el valor de propiedad de un objeto. Aunque esta sintaxis se admite en una página de Razor (.cshtml), no es válida para la asignación al parámetro Title del elemento secundario de un componente. La sintaxis de Razor siguiente no se admite:
<ParameterChild Title="Set by @(panelData.Title)" />
El código del ejemplo anterior genera un error del compilador si se compila la aplicación:
Los atributos del componente no admiten contenido complejo (C# y marcado combinado).
Para admitir la asignación de un valor compuesto, utilice un método, un campo o una propiedad. En el ejemplo siguiente se realiza la concatenación de "Set by " y el valor de propiedad de un objeto en el método de C# GetTitle:
Pages/ParameterParent3.razor:
@page "/parameter-parent-3"
<ParameterChild Title="@GetTitle()" />
@code {
private PanelData panelData = new PanelData();
private string GetTitle() => $"Set by {panelData.Title}";
private class PanelData
{
public string Title { get; set; } = "Parent";
}
}
Para obtener más información, vea Referencia sobre la sintaxis de Razor para ASP.NET Core.
Advertencia
Se admite el suministro de valores iniciales para los parámetros del componente, pero no cree un componente que escriba en sus propios parámetros después de que el componente se represente por primera vez. Para obtener más información, vea la sección Parámetros sobrescritos de este artículo.
Los parámetros del componente se deben declarar como propiedades automáticas, lo que significa que no deben contener lógica personalizada en sus descriptores de acceso get o set. Por ejemplo, la siguiente propiedad StartData es una propiedad automática:
[Parameter]
public DateTime StartData { get; set; }
No coloque la lógica personalizada en el descriptor de acceso get o set porque los parámetros del componente están pensados exclusivamente para su uso como canal para que un componente primario envíe información a un componente secundario. Si un descriptor de acceso set de una propiedad de componente secundario contiene lógica que provoca la repetición de la representación del componente primario, se produce un bucle de representación infinito.
Para transformar un valor de parámetro recibido:
- Deje la propiedad del parámetro como propiedad automática para representar los datos sin procesar proporcionados.
- Cree otra propiedad o método que proporcione los datos transformados en función de la propiedad del parámetro.
Invalide OnParametersSetAsync para transformar un parámetro recibido cada vez que se reciban nuevos datos.
Se admite la escritura de un valor inicial en un parámetro de componente porque las asignaciones de valores iniciales no interfieren con la representación automática del componente de Blazor. La siguiente asignación de la configuración local DateTime actual de DateTime.Now a StartData es una sintaxis válida en un componente:
[Parameter]
public DateTime StartData { get; set; } = DateTime.Now;
Después de la asignación inicial de DateTime.Now, no asigne un valor a StartData en el código para desarrolladores. Para obtener más información, vea la sección Parámetros sobrescritos de este artículo.
Parámetros de ruta
Los componentes pueden especificar parámetros de ruta en la plantilla de ruta de la directiva @page. El enrutador de Blazor usa parámetros de ruta para rellenar los parámetros de componente correspondientes con el mismo nombre.
Pages/RouteParameter.razor:
@page "/route-parameter"
@page "/route-parameter/{text}"
<h1>Blazor is @Text!</h1>
@code {
[Parameter]
public string Text { get; set; }
protected override void OnInitialized()
{
Text = Text ?? "fantastic";
}
}
No se admiten parámetros de ruta opcionales, por lo que en el ejemplo anterior se aplican dos directivas @page. La primera directiva @page permite navegar al componente sin un parámetro de ruta, mientras que la segunda directiva @page recibe el parámetro de ruta {text} y asigna el valor a la propiedad Text.
Para obtener información sobre los parámetros de ruta comodín ({*pageRoute}), que capturan rutas de acceso en varios límites de carpeta, vea Enrutamiento de Blazor de ASP.NET Core.
Parámetros sobrescritos
El marco Blazor impone con carácter general una asignación de parámetro de componentes primarios a secundarios segura:
- Los parámetros no se sobrescriben de forma inesperada.
- Los efectos secundarios se minimizan. Por ejemplo,se evitan representaciones adicionales, ya que pueden crear bucles de representación infinitos.
Un componente secundario recibe nuevos valores de parámetro que posiblemente sobrescriban los valores existentes cuando el componente primario vuelva a representarse. Sobrescribir valores de parámetro en un componente secundario de forma accidental suele producirse al desarrollar el componente con uno o varios parámetros enlazados a datos y cuando el desarrollador escribe directamente en un parámetro del elemento secundario:
- El componente secundario se representa con uno o varios valores de parámetro del componente primario.
- El elemento secundario escribe directamente en el valor de un parámetro.
- El componente primario vuelve a representar el valor del parámetro del elemento secundario y lo sobrescribe.
La posibilidad de sobrescribir valores de parámetro se extiende también a los descriptores de acceso set de propiedades del componente secundario.
Importante
Nuestra orientación general es no crear componentes que escriban directamente en sus propios parámetros después de que el componente se represente por primera vez.
Considere el componente Expander erróneo siguiente que:
- Representa el contenido secundario.
- Alterna la visualización del contenido secundario con un parámetro de componente (
Expanded). - El componente escribe directamente en el parámetro
Expanded, que muestra el problema con los parámetros sobrescritos y debe evitarse.
Después de que el componente Expander siguiente muestre el método incorrecto para este escenario, se muestra un componente Expander modificado para mostrar el método correcto. Los ejemplos siguientes se pueden colocar en una aplicación de ejemplo local para experimentar los comportamientos descritos.
Shared/Expander.razor:
<div @onclick="Toggle" class="card bg-light mb-3" style="width:30rem">
<div class="card-body">
<h2 class="card-title">Toggle (<code>Expanded</code> = @Expanded)</h2>
@if (Expanded)
{
<p class="card-text">@ChildContent</p>
}
</div>
</div>
@code {
[Parameter]
public bool Expanded { get; set; }
[Parameter]
public RenderFragment ChildContent { get; set; }
private void Toggle()
{
Expanded = !Expanded;
}
}
El componente Expander se agrega al siguiente componente primario ExpanderExample que podría llamar a StateHasChanged:
- Al llamar a StateHasChanged en el código de desarrollador se notifica a un componente que su estado ha cambiado y normalmente desencadena la repetición de la representación del componente para actualizar la interfaz de usuario. StateHasChanged se trata detalladamente más adelante en Ciclo de vida de los componentes de ASP.NET Core Razor y Representación de componentes de Blazor de ASP.NET Core.
- El atributo de directiva
@onclickdel botón asocia un controlador de eventos al eventoonclickdel botón. El control de eventos se trata detalladamente más adelante en Control de eventos de Blazor en ASP.NET Core.
Pages/ExpanderExample.razor:
@page "/expander-example"
<Expander Expanded="true">
Expander 1 content
</Expander>
<Expander Expanded="true" />
<button @onclick="StateHasChanged">
Call StateHasChanged
</button>
Al principio, los componentes Expander se comportan de manera independiente cuando se alternan sus propiedades de Expanded. Los componentes secundarios mantienen sus estados según lo previsto. Cuando se llama a StateHasChanged en el componente principal, el parámetro Expanded del primer componente secundario se restablece nuevamente en su valor inicial (true). No se restablece el valor Expanded del segundo componente de Expander, porque no se representa ningún contenido secundario en el segundo componente.
Para mantener el estado en el escenario anterior, use un campo privado en el componente Expander para mantener su estado de alternancia.
El siguiente componente Expander revisado:
- Acepta el valor del parámetro de componente
Expandeddel componente principal. - Asigna el valor del parámetro de componente a un campo privado (
expanded) en el eventoOnInitialized. - Usa el campo privado para mantener su estado de alternancia interno, que muestra cómo evitar escribir directamente en un parámetro.
Nota
Los consejos de esta sección se aplican a una lógica similar en los descriptores de acceso set de parámetros de componente, lo que puede dar lugar a efectos secundarios no deseados similares.
Shared/Expander.razor:
<div @onclick="Toggle" class="card bg-light mb-3" style="width:30rem">
<div class="card-body">
<h2 class="card-title">Toggle (<code>expanded</code> = @expanded)</h2>
@if (expanded)
{
<p class="card-text">@ChildContent</p>
}
</div>
</div>
@code {
private bool expanded;
[Parameter]
public bool Expanded { get; set; }
[Parameter]
public RenderFragment ChildContent { get; set; }
protected override void OnInitialized()
{
expanded = Expanded;
}
private void Toggle()
{
expanded = !expanded;
}
}
Para obtener información adicional, consulte Error de enlace bidireccional de Blazor (dotnet/aspnetcore #24599).
Contenido secundario
Los componentes pueden definir el contenido de otro componente. El componente de asignación proporciona el contenido entre las etiquetas de apertura y cierre del componente secundario.
En el siguiente ejemplo, el componente RenderFragmentChild tiene una propiedad ChildContent que representa un segmento de la interfaz de usuario que se va a representar como RenderFragment. La posición de ChildContent en el marcado de Razor del componente es donde el contenido se representa en la salida HTML final.
Shared/RenderFragmentChild.razor:
<div class="card w-25" style="margin-bottom:15px">
<div class="card-header font-weight-bold">Child content</div>
<div class="card-body">@ChildContent</div>
</div>
@code {
[Parameter]
public RenderFragment ChildContent { get; set; }
}
Importante
La propiedad que recibe el contenido de RenderFragment debe denominarse ChildContent por convención.
El componente RenderFragmentParent siguiente proporciona contenido para representar el componente RenderFragmentChild mediante la colocación del contenido dentro de las etiquetas de apertura y cierre del componente secundario.
Pages/RenderFragmentParent.razor:
@page "/render-fragment-parent"
<h1>Render child content</h1>
<RenderFragmentChild>
Content of the child component is supplied
by the parent component.
</RenderFragmentChild>
Debido a la forma en que Blazor representa el contenido secundario, la representación de componentes dentro de un bucle for requiere una variable de índice local si se usa la variable de bucle incremental en el contenido del componente RenderFragmentChild. El ejemplo siguiente se puede agregar al componente RenderFragmentParent anterior:
<h1>Three children with an index variable</h1>
@for (int c = 0; c < 3; c++)
{
var current = c;
<RenderFragmentChild>
Count: @current
</RenderFragmentChild>
}
Como alternativa, use un bucle foreach con Enumerable.Range en lugar de un bucle for. El ejemplo siguiente se puede agregar al componente RenderFragmentParent anterior:
<h1>Second example of three children with an index variable</h1>
@foreach (var c in Enumerable.Range(0,3))
{
<RenderFragmentChild>
Count: @c
</RenderFragmentChild>
}
Para obtener información sobre cómo se puede usar un componente RenderFragment como plantilla para la interfaz de usuario del componente, consulte los siguientes artículos:
- Componentes con plantilla de Blazor en ASP.NET Core
- Procedimientos recomendados de rendimiento de Blazor en ASP.NET Core
Expansión de atributos y parámetros arbitrarios
Los componentes pueden capturar y representar más atributos aparte de los parámetros declarados del componente. Los atributos adicionales se pueden capturar en un diccionario y luego expandirse en un elemento cuando el componente se representa por medio del atributo de directiva de @attributesRazor. Este escenario es útil para definir un componente que genera un elemento de marcado que admite diversas personalizaciones. Por ejemplo, definir atributos por separado para un elemento <input> que admite muchos parámetros puede ser un engorro.
En el componente Splat siguiente:
- El primer elemento
<input>(id="useIndividualParams") usa parámetros de componente individuales. - El segundo elemento
<input>(id="useAttributesDict") usa la expansión de atributos.
Pages/Splat.razor:
@page "/splat"
<input id="useIndividualParams"
maxlength="@maxlength"
placeholder="@placeholder"
required="@required"
size="@size" />
<input id="useAttributesDict"
@attributes="InputAttributes" />
@code {
private string maxlength = "10";
private string placeholder = "Input placeholder text";
private string required = "required";
private string size = "50";
private Dictionary<string, object> InputAttributes { get; set; } =
new Dictionary<string, object>()
{
{ "maxlength", "10" },
{ "placeholder", "Input placeholder text" },
{ "required", "required" },
{ "size", "50" }
};
}
Los elementos <input> representados en la página web son idénticos:
<input id="useIndividualParams"
maxlength="10"
placeholder="Input placeholder text"
required="required"
size="50">
<input id="useAttributesDict"
maxlength="10"
placeholder="Input placeholder text"
required="required"
size="50">
Para aceptar atributos arbitrarios, defina un parámetro de componente con la propiedad CaptureUnmatchedValues establecida en true:
@code {
[Parameter(CaptureUnmatchedValues = true)]
public Dictionary<string, object> InputAttributes { get; set; }
}
La propiedad CaptureUnmatchedValues en [Parameter] permite que el parámetro coincida con todos los atributos que no coinciden con ningún otro parámetro. Un componente solamente puede definir un parámetro con CaptureUnmatchedValues. El tipo de propiedad que se usa con CaptureUnmatchedValues se debe poder asignar desde Dictionary<string, object> con claves de cadena. El uso de IEnumerable<KeyValuePair<string, object>> o IReadOnlyDictionary<string, object> también son opciones en este escenario.
La posición de @attributes relativa a la posición de los atributos de elemento es importante. Cuando @attributes se expande en el elemento, los atributos se procesan de derecha a izquierda (del último al primero). Veamos el siguiente ejemplo de un componente primario que usa un componente secundario:
Shared/AttributeOrderChild1.razor:
<div @attributes="AdditionalAttributes" extra="5" />
@code {
[Parameter(CaptureUnmatchedValues = true)]
public IDictionary<string, object> AdditionalAttributes { get; set; }
}
Pages/AttributeOrderParent1.razor:
@page "/attribute-order-parent-1"
<AttributeOrderChild1 extra="10" />
El atributo extra del componente AttributeOrderChild1 se establece a la derecha de @attributes. El elemento <div> representado del componente AttributeOrderParent1 contiene extra="5" cuando se pasa a través del atributo adicional, ya que los atributos se procesan de derecha a izquierda (de último a primero):
<div extra="5" />
En el ejemplo siguiente, el orden de extra y @attributes se invierte en el elemento <div> del componente secundario:
Shared/AttributeOrderChild2.razor:
<div extra="5" @attributes="AdditionalAttributes" />
@code {
[Parameter(CaptureUnmatchedValues = true)]
public IDictionary<string, object> AdditionalAttributes { get; set; }
}
Pages/AttributeOrderParent2.razor:
@page "/attribute-order-parent-2"
<AttributeOrderChild2 extra="10" />
El elemento <div> en la página web representada del componente primario contiene extra="10" cuando se pasa por el atributo adicional:
<div extra="10" />
Captura de referencias a componentes
Las referencias de componentes proporcionan una manera de hacer referencia a una instancia de componente para emitir comandos. Para capturar una referencia de componente:
- Agregue un atributo
@refal componente secundario. - Defina un campo con el mismo tipo que el componente secundario.
Cuando el componente se representa, el campo se rellena con la instancia del componente. Tras ello, se pueden invocar métodos de .NET en la instancia.
Tenga en cuenta el componente ReferenceChild siguiente, que registra un mensaje cuando se llama a su método ChildMethod.
Shared/ReferenceChild.razor:
@using Microsoft.Extensions.Logging
@inject ILogger<ReferenceChild> logger
@code {
public void ChildMethod(int value)
{
logger.LogInformation("Received {Value} in ChildMethod", value);
}
}
Una referencia de componente solo se rellena después de que el componente se represente, y su salida incluye el elemento de ReferenceChild. Hasta que se represente el componente, no hay nada a lo que hacer referencia.
Para manipular las referencias de componente una vez finalizada la representación del componente, use los métodos OnAfterRender o OnAfterRenderAsync.
Para usar una variable de referencia con un controlador de eventos, use una expresión lambda o asigne el delegado de controlador de eventos en los métodos OnAfterRender o OnAfterRenderAsync. Esto garantiza que la variable de referencia se asigna antes de que se asigne el controlador de eventos.
El siguiente método lambda usa el componente ReferenceChild anterior.
Pages/ReferenceParent1.razor:
@page "/reference-parent-1"
<button @onclick="@(() => childComponent.ChildMethod(5))">
Call <code>ReferenceChild.ChildMethod</code> with an argument of 5
</button>
<ReferenceChild @ref="childComponent" />
@code {
private ReferenceChild childComponent;
}
El siguiente método de delegado usa el componente ReferenceChild anterior.
Pages/ReferenceParent2.razor:
@page "/reference-parent-2"
<button @onclick="callChildMethod">
Call <code>ReferenceChild.ChildMethod</code> with an argument of 5
</button>
<ReferenceChild @ref="childComponent" />
@code {
private ReferenceChild childComponent;
private Action callChildMethod;
protected override void OnAfterRender(bool firstRender)
{
if (firstRender)
{
callChildMethod = CallChildMethod;
}
}
private void CallChildMethod()
{
childComponent.ChildMethod(5);
}
}
Use una colección para hacer referencia a componentes de un bucle. En el ejemplo siguiente:
- Los componentes se agregan a List<T>.
- Se crea un botón para cada componente que desencadena el método
ChildMethoddel componente correspondiente por su índice de componente en List<T>.
Pages/ReferenceParent3.razor mediante el componente ReferenceChild anterior:
@page "/reference-parent-3"
<ul>
@for (int i = 0; i < 5; i++)
{
var index = i;
var v = r.Next(1000);
<li>
<ReferenceChild @ref="childComponent" />
<button @onclick="@(() => callChildMethod(index, v))">
Component index @index: Call <code>ReferenceChild.ChildMethod(@v)</code>
</button>
</li>
}
</ul>
@code {
private Random r = new Random();
private List<ReferenceChild> components = new List<ReferenceChild>();
private Action<int, int> callChildMethod;
private ReferenceChild childComponent
{
set => components.Add(value);
}
protected override void OnAfterRender(bool firstRender)
{
if (firstRender)
{
callChildMethod = CallChildMethod;
}
}
private void CallChildMethod(int index, int value)
{
components.ElementAt(index).ChildMethod(value);
}
}
Si bien la captura de referencias de componentes emplea una sintaxis similar a la de la captura de referencias de elementos, esta no es una característica de interoperabilidad de JavaScript. Las referencias de componente no se pasan al código JavaScript. Solo se usan en código .NET.
Importante
No use referencias de componentes para mutar el estado de los componentes secundarios. En su lugar, use parámetros de componente declarativos normales para pasar datos a componentes secundarios. El uso de parámetros de componente da como resultado componentes secundarios que se volverán a representar automáticamente justo cuando corresponda. Para obtener más información, consulte la sección de parámetros del componente y el artículo Enlace de datos de ASP.NET Core Blazor.
Contexto de sincronización
Blazor usa un contexto de sincronización (SynchronizationContext) para aplicar un único subproceso lógico de ejecución. Los métodos de ciclo de vida de un componente y las devoluciones de llamada de eventos que Blazor genere se ejecutan en el contexto de sincronización.
El contexto de sincronización de Blazor Server intenta emular un entorno de un solo subproceso para que coincida estrechamente con el modelo WebAssembly en el explorador, que es de un solo subproceso. En todo momento, el trabajo se realiza en exactamente un subproceso, lo que da la impresión de un único subproceso lógico. Nunca se ejecutan dos operaciones simultáneamente.
Evitar las llamadas de bloqueo de subprocesos
Por lo general, no llame a los métodos siguientes en componentes. Los métodos siguientes bloquean el subproceso de ejecución y, por tanto, impiden que la aplicación reanude el trabajo hasta que se complete Task:
Nota
Los ejemplos de documentación de Blazor que usan los métodos de bloqueo de subprocesos mencionados en esta sección solo usan los métodos con fines de demostración, no como guía de codificación recomendada. Por ejemplo, algunas demostraciones de código de componentes simulan un proceso de ejecución larga mediante una llamada a Thread.Sleep.
Invocación de métodos de componentes externamente para actualizar el estado
En caso de que un componente deba actualizarse en función de un evento externo, como un temporizador u otras notificaciones, use el método InvokeAsync, que envía la ejecución de código al contexto de sincronización de Blazor. Consideremos, por ejemplo, el siguiente servicio de notificador capaz de notificar el estado actualizado a cualquier componente de escucha. Se puede llamar al método Update desde cualquier lugar de la aplicación.
TimerService.cs:
using System;
using System.Timers;
using Microsoft.Extensions.Logging;
public class TimerService : IDisposable
{
private int elapsedCount;
private readonly ILogger<TimerService> logger;
private readonly NotifierService notifier;
private Timer timer;
public TimerService(NotifierService notifier, ILogger<TimerService> logger)
{
this.notifier = notifier;
this.logger = logger;
}
public void Start()
{
if (timer is null)
{
timer = new();
timer.AutoReset = true;
timer.Interval = 10000;
timer.Elapsed += HandleTimer;
timer.Enabled = true;
logger.LogInformation("Started");
}
}
private async void HandleTimer(object source, ElapsedEventArgs e)
{
elapsedCount += 1;
await notifier.Update("elapsedCount", elapsedCount);
logger.LogInformation($"elapsedCount: {elapsedCount}");
}
public void Dispose()
{
timer?.Dispose();
}
}
NotifierService.cs:
using System;
using System.Threading.Tasks;
public class NotifierService
{
public async Task Update(string key, int value)
{
if (Notify != null)
{
await Notify.Invoke(key, value);
}
}
public event Func<string, int, Task> Notify;
}
Registre los servicios:
En una aplicación Blazor WebAssembly, registre los servicios como singletons en
Program.cs:builder.Services.AddSingleton<NotifierService>(); builder.Services.AddSingleton<TimerService>();En una aplicación Blazor Server, registre los servicios como con ámbito en
Startup.ConfigureServices:services.AddScoped<NotifierService>(); services.AddScoped<TimerService>();
Use el elemento NotifierService para actualizar un componente.
Pages/ReceiveNotifications.razor:
@page "/receive-notifications"
@implements IDisposable
@inject NotifierService Notifier
@inject TimerService Timer
<h1>Receive Notifications</h1>
<h2>Timer Service</h2>
<button @onclick="StartTimer">Start Timer</button>
<h2>Notifications</h2>
<p>
Status:
@if (lastNotification.key is not null)
{
<span>@lastNotification.key = @lastNotification.value</span>
}
else
{
<span>Awaiting first notification</span>
}
</p>
@code {
private (string key, int value) lastNotification;
protected override void OnInitialized()
{
Notifier.Notify += OnNotify;
}
public async Task OnNotify(string key, int value)
{
await InvokeAsync(() =>
{
lastNotification = (key, value);
StateHasChanged();
});
}
private void StartTimer()
{
Timer.Start();
}
public void Dispose()
{
Notifier.Notify -= OnNotify;
}
}
En el ejemplo anterior:
NotifierServiceinvoca el métodoOnNotifydel componente fuera del contexto de sincronización de Blazor.InvokeAsyncse utiliza para cambiar al contexto correcto y poner una representación en cola. Para obtener más información, vea Representación de componentes de Blazor de ASP.NET Core.- El componente implementa IDisposable. El elemento delegado
OnNotifyanula su inscripción en el métodoDispose, al que llama el marco cuando se desecha el componente. Para obtener más información, vea Ciclo de vida de los componentes de ASP.NET Core Razor.
Uso de @key para controlar la conservación de elementos y componentes
Cuando se representa una lista de elementos o componentes, y esos elementos o componentes cambian posteriormente, Blazor debe decidir cuáles de los elementos o componentes anteriores se pueden conservar y cómo asignar objetos de modelo a ellos. Normalmente, este proceso es automático y se puede pasar por alto, pero hay casos en los que puede que queramos controlarlo.
Considere los componentes Details y People siguientes:
- El componente
Detailsrecibe datos (Data) del componente primarioPeople, que se muestra en un elemento<input>. Cualquier elemento<input>determinado que se muestra puede recibir el foco de la página del usuario cuando selecciona uno de los elementos<input>. - El componente
Peoplecrea una lista de objetos Person para mostrar mediante el componenteDetails. Cada tres segundos, se agrega una nueva persona a la colección.
Esta demostración le permite:
- Seleccionar un elemento
<input>de entre varios componentesDetailsrepresentados. - Estudiar el comportamiento del foco de la página a medida que crece automáticamente la colección People.
Shared/Details.razor:
<input value="@Data" />
@code {
[Parameter]
public string Data { get; set; }
}
En el componente People siguiente, cada iteración de agregar a una persona en OnTimerCallback da lugar a la recompilación de Blazor de toda la colección. El foco de la página permanece en la misma posición de índice de los elementos <input>, por lo que el foco cambia cada vez que se agrega a una persona. Desplazar el foco fuera de lo que seleccionó el usuario no es un comportamiento deseable. Tras demostrar el comportamiento deficiente con el componente siguiente, el atributo de directiva @key se usa para mejorar la experiencia del usuario.
Pages/People.razor:
@page "/people"
@using System.Timers
@implements IDisposable
@foreach (var person in people)
{
<Details Data="@person.Data" />
}
@code {
private Timer timer = new Timer(3000);
public List<Person> people =
new List<Person>()
{
{ new Person { Data = "Person 1" } },
{ new Person { Data = "Person 2" } },
{ new Person { Data = "Person 3" } }
};
protected override void OnInitialized()
{
timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
timer.Start();
}
private void OnTimerCallback()
{
_ = InvokeAsync(() =>
{
people.Insert(0,
new Person
{
Data = $"INSERTED {DateTime.Now.ToString("hh:mm:ss tt")}"
});
StateHasChanged();
});
}
public void Dispose() => timer.Dispose();
public class Person
{
public string Data { get; set; }
}
}
El contenido de la colección people cambia porque se inserten, eliminen o reordenen entradas. La nueva representación puede dar lugar a diferencias de comportamiento visibles. Cada vez que se inserta a una persona en la colección people, el elemento anterior del elemento actualmente centrado recibe el foco. Se pierde el foco del usuario.
El proceso de asignación de elementos o componentes a una colección se puede controlar con el atributo de directiva @key. El uso de @key garantiza la conservación de elementos o componentes en función del valor de la clave. Si el componente Details del ejemplo anterior se codifica en el elemento person, Blazor omite la nueva representación de componentes Details que no han cambiado.
Para modificar el componente People para usar el atributo de directiva @key con la colección people, actualice el elemento <Details> a lo siguiente:
<Details @key="person" Data="@person.Data" />
Cuando la colección people cambia, la asociación entre las instancias de Details y las de person se mantiene. Cuando se inserta un elemento Person al principio de la colección, se inserta una nueva instancia Details en la posición correspondiente. Las demás instancias permanecerán inalteradas. Por lo tanto, el foco del usuario no se pierde a medida que se agregan personas a la colección.
Otras actualizaciones de colección muestran el mismo comportamiento cuando se usa el atributo de directiva @key:
- Si una instancia se elimina de la colección, solo se quitará de la interfaz de usuario la instancia de componente correspondiente. Las demás instancias permanecerán inalteradas.
- Si las entradas de colección se reordenan, las instancias de componente correspondientes se conservarán y reordenarán en la interfaz de usuario.
Importante
Las claves son locales de cada componente o elemento contenedor. Las claves no se comparan globalmente en todo el documento.
Cuándo debe usarse @key
Normalmente, usar @key tiene sentido cada vez que una lista se represente (por ejemplo, en un bloque foreach) y haya un valor adecuado para definir el elemento @key.
También puede usar @key para conservar un subárbol de elemento o componente cuando un objeto no cambia, como se muestra en los ejemplos siguientes.
Ejemplo 1:
<li @key="person">
<input value="@person.Data" />
</li>
Ejemplo 2:
<div @key="person">
@* other HTML elements *@
</div>
Si una instancia person cambia, la directiva de atributo @key fuerza a Blazor a:
- Descartar los elementos
<li>o<div>enteros y sus descendientes. - Volver a generar el subárbol dentro de la interfaz de usuario con nuevos elementos y componentes.
Esto es útil para garantizar que no se conserva ningún estado de la interfaz de usuario cuando la colección cambia dentro de un subárbol.
Ámbito de @key
La directiva de atributo @key tiene como ámbito los otros elementos de su elemento primario.
Considere el ejemplo siguiente. Las claves first y second se comparan entre sí en el mismo ámbito del elemento externo <div>:
<div>
<div @key="first">...</div>
<div @key="second">...</div>
</div>
En el ejemplo siguiente se muestran las claves first y second en sus propios ámbitos, no relacionadas entre sí y sin que una influya en la otra. Cada ámbito @key solo se aplica a su elemento <div> primario en lugar de en los elementos <div> primarios:
<div>
<div @key="first">...</div>
</div>
<div>
<div @key="second">...</div>
</div>
Para el componente Details mostrado anteriormente, los ejemplos siguientes representan datos de person dentro del mismo ámbito @key y muestran casos de uso típicos para @key:
<div>
@foreach (var person in people)
{
<Details @key="person" Data="@person.Data" />
}
</div>
@foreach (var person in people)
{
<div @key="person">
<Details Data="@person.Data" />
</div>
}
<ol>
@foreach (var person in people)
{
<li @key="person">
<Details Data="@person.Data" />
</li>
}
</ol>
Los ejemplos siguientes solo tienen como ámbito de @key el elemento <div> o <li> que rodea cada instancia del componente Details. Por lo tanto, los datos de person para cada miembro de la colección people no tienen clave en cada instancia de person en los componentes Details representados. Al usar @key, evite los patrones siguientes:
@foreach (var person in people)
{
<div>
<Details @key="person" Data="@person.Data" />
</div>
}
<ol>
@foreach (var person in people)
{
<li>
<Details @key="person" Data="@person.Data" />
</li>
}
</ol>
Cuándo no debe usarse @key
Las representaciones con @key repercuten en el rendimiento. El rendimiento no se ve especialmente afectado, pero pese a ello debemos especificar @key únicamente cuando mantener los componentes o elementos suponga un beneficio para la aplicación.
Aun cuando @key no se use, Blazor conserva las instancias de componentes y elementos secundarios lo máximo posible. La única ventaja de utilizar @key es el control sobre cómo se asignan instancias de modelo a las instancias de componente conservadas, en lugar de Blazor, que selecciona la asignación.
Valores que se pueden usar para @key
Por lo general, lo lógico es proporcionar uno de los siguientes valores en @key:
- Instancias de objeto de modelo. Por ejemplo, la instancia
Person(person) se usó en el ejemplo anterior. Esto garantiza la conservación en función de la igualdad de las referencias de objetos. - Identificadores únicos. Por ejemplo, los identificadores únicos pueden basarse en valores de clave principal de tipo
int,stringoGuid.
Asegúrese de que los valores usados en @key no entran en conflicto. Si se detectan valores en conflicto en el mismo elemento primario, Blazor produce una excepción porque no puede asignar de forma determinista elementos o componentes antiguos a nuevos elementos o componentes. Use exclusivamente valores distintos, como instancias de objeto o valores de clave principal.
Aplicación de un atributo
En los componentes se pueden aplicar atributos con la directiva @attribute. En el siguiente ejemplo se aplica el atributo [Authorize] a la clase del componente:
@page "/"
@attribute [Authorize]
Atributos de elementos HTML condicionales
Las propiedades de atributos de elementos HTML se establecen condicionalmente en función del valor de .NET. Si el valor es false o null, no se establece la propiedad. Si el valor es true, se establece la propiedad.
En el siguiente ejemplo, IsCompleted determina si se establece la propiedad <input> del elemento checked.
Pages/ConditionalAttribute.razor:
@page "/conditional-attribute"
<label>
<input type="checkbox" checked="@IsCompleted" />
Is Completed?
</label>
<button @onclick="@(() => IsCompleted = !IsCompleted)">
Change IsCompleted
</button>
@code {
[Parameter]
public bool IsCompleted { get; set; }
}
Para obtener más información, vea Referencia sobre la sintaxis de Razor para ASP.NET Core.
Advertencia
Algunos atributos HTML, como aria-pressed, no funcionan correctamente cuando el tipo de .NET es bool. En esos casos, use un tipo string en lugar de bool.
HTML sin formato
Normalmente, las cadenas se representan mediante nodos de texto DOM, lo que significa que cualquier marcado que esas cadenas puedan contener se omite y se trata como texto literal. Para representar HTML sin formato, encapsule el contenido HTML en un valor MarkupString. El valor se analiza como HTML o SVG y se inserta en el DOM.
Advertencia
La representación de HTML sin formato creado a partir de un origen que no es de confianza entraña un riesgo de seguridad y debe evitarse siempre.
En el siguiente ejemplo se describe cómo usar el tipo MarkupString para agregar un bloque de contenido HTML estático a la salida representada de un componente.
Pages/MarkupStringExample.razor:
@page "/markup-string-example"
@((MarkupString)myMarkup)
@code {
private string myMarkup =
"<p class=\"text-danger\">This is a dangerous <em>markup string</em>.</p>";
}
Plantillas de Razor
Los fragmentos de representación se pueden definir mediante la sintaxis de plantilla de Razor para definir un fragmento de interfaz de usuario. Las plantillas de Razor utilizan el siguiente formato:
@<{HTML tag}>...</{HTML tag}>
En el siguiente ejemplo se muestra cómo especificar los valores RenderFragment y RenderFragment<TValue> y las plantillas de representación directamente en un componente. Los fragmentos de representación también se pueden pasar como argumentos a componentes con plantilla.
Pages/RazorTemplate.razor:
@page "/razor-template"
@timeTemplate
@petTemplate(new Pet { Name = "Nutty Rex" })
@code {
private RenderFragment timeTemplate = @<p>The time is @DateTime.Now.</p>;
private RenderFragment<Pet> petTemplate = (pet) => @<p>Pet: @pet.Name</p>;
private class Pet
{
public string Name { get; set; }
}
}
Salida representada del código anterior:
<p>The time is 4/19/2021 8:54:46 AM.</p>
<p>Pet: Nutty Rex</p>
Recursos estáticos
Blazor sigue la convención de las aplicaciones de ASP.NET Core para los recursos estáticos. Los recursos estáticos se encuentran en la carpeta web root (wwwroot) del proyecto o en la carpeta wwwroot.
Use una ruta de acceso relativa base (/) para hacer referencia a la raíz web de un activo estático. En el ejemplo siguiente, logo.png se encuentra físicamente en la carpeta {PROJECT ROOT}/wwwroot/images. {PROJECT ROOT} es la raíz del proyecto de la aplicación.
<img alt="Company logo" src="/images/logo.png" />
Los componentes no admiten la notación de virgulilla-barra diagonal (~/).
Para más información sobre cómo establecer la ruta de acceso base de una aplicación, vea Hospedaje e implementación de ASP.NET Core Blazor.
Las aplicaciones auxiliares de etiquetas no se admiten en los componentes
Tag Helpers no se admiten en los componentes. Para proporcionar una funcionalidad similar a la de las aplicaciones auxiliares de etiquetas en Blazor, cree un componente con la misma funcionalidad que la aplicación auxiliar de etiquetas y use el componente en su lugar.
Imágenes con formato Scalable Vector Graphics (SVG)
Como Blazor representa HTML, se pueden usar imágenes compatibles con el explorador, incluidas imágenes con formato Scalable Vector Graphics (SVG) (.svg), mediante la etiqueta <img>:
<img alt="Example image" src="image.svg" />
Del mismo modo, se pueden usar imágenes SVG en las reglas de CSS de un archivo de hoja de estilos (.css):
.element-class {
background-image: url("image.svg");
}
Comportamiento de la representación de espacios en blanco
El espacio en blanco se conserva en el marcado de origen de un componente. El texto de solo espacio en blanco se representa en el modelo DOM del explorador aunque no haya ningún efecto visual.
Considere el siguiente marcado de componente:
<ul>
@foreach (var item in Items)
{
<li>
@item.Text
</li>
}
</ul>
En el ejemplo anterior se representa el siguiente espacio en blanco innecesario:
- Fuera del bloque de código
@foreach. - Alrededor del elemento
<li>. - Alrededor de la salida de
@item.Text.
Una lista de 100 elementos da como resultado más de 400 áreas de espacio en blanco. Ninguno de los espacios en blanco adicionales afecta visualmente a la salida representada.
Al representar HTML estático para componentes, no se conservaba el espacio en blanco dentro de una etiqueta. Por ejemplo, vea el resultado representado de la etiqueta <img> siguiente en un archivo de Razor de componente (.razor):
<img alt="Example image" src="img.png" />
No se conserva el espacio en blanco del marcado anterior:
<img alt="Example image" src="img.png" />
Compatibilidad con parámetro de tipo genérico
La directiva @typeparam declara un parámetro de tipo genérico para la clase de componente generada:
@typeparam TItem
Para más información, consulte los siguientes artículos.