Nota
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
Nota
Esta no es la versión más reciente de este artículo. Para la versión actual, consulte la versión de .NET 9 de este artículo.
Advertencia
Esta versión de ASP.NET Core ya no se admite. Para obtener más información, consulte la directiva de compatibilidad de .NET y .NET Core. Para la versión actual, consulte la versión de .NET 9 de este artículo.
Importante
Esta información hace referencia a un producto en versión preliminar, el cual puede sufrir importantes modificaciones antes de que se publique la versión comercial. Microsoft no proporciona ninguna garantía, expresa o implícita, con respecto a la información proporcionada aquí.
Para la versión actual, consulte la versión de .NET 9 de este artículo.
En este artículo se explica el ciclo de vida de los componentes de ASP.NET Core Razor y cómo usar los eventos de ciclo de vida.
Eventos de ciclo de vida
El componente Razor procesa los eventos de ciclo de vida de los componentes Razor en un conjunto de métodos de ciclo de vida sincrónicos y asincrónicos. Los métodos de ciclo de vida se pueden invalidar para realizar operaciones adicionales en los componentes durante la inicialización y representación de estos.
En este artículo se simplifica el procesamiento de eventos del ciclo de vida de los componentes para aclarar la lógica compleja del marco y no se cubren todos los cambios realizados durante los años. Es posible que tenga que acceder al ComponentBase
origen de referencia para integrar el procesamiento de eventos personalizados con el procesamiento de eventos del ciclo de vida de Blazor. Los comentarios de código del origen de referencia incluyen comentarios adicionales sobre el procesamiento de eventos del ciclo de vida que no aparecen en este artículo ni en la documentación de la API.
Nota
Los vínculos de la documentación al origen de referencia de .NET cargan normalmente la rama predeterminada del repositorio, que representa el desarrollo actual para la próxima versión de .NET. Para seleccionar una etiqueta de una versión específica, usa la lista desplegable Cambiar ramas o etiquetas. Para obtener más información, vea Procedimientos para seleccionar una etiqueta de versión de código fuente de ASP.NET Core (dotnet/AspNetCore.Docs #26205).
En los siguientes diagramas simplificados se ilustra el procesamiento de eventos de ciclo de vida de los componentes Razor. Los métodos de C# asociados a los eventos de ciclo de vida se definen con ejemplos en las siguientes secciones de este artículo.
Eventos del ciclo de vida del componente:
- Si el componente se representa por primera vez en una solicitud:
- Cree la instancia del componente.
- Realice la inserción de propiedades.
- Llame a
OnInitialized{Async}
. Si se devuelve un elemento Task incompleto, Task se espera y, luego, el componente se vuelve a representar. Se llama al método sincrónico antes del método asincrónico.
- Llame a
OnParametersSet{Async}
. Si se devuelve un elemento Task incompleto, Task se espera y, luego, el componente se vuelve a representar. Se llama al método sincrónico antes del método asincrónico. - Realice la representación de todo el trabajo sincrónico y complete la clase Task.
Nota
Las acciones asincrónicas realizadas en eventos de ciclo de vida pueden retrasar la representación de componentes o mostrar datos. Para obtener más información, consulte la sección "Manejar acciones asincrónicas incompletas durante el renderizado" más adelante en este artículo.
Un componente primario se representa antes que sus componentes secundarios porque la representación es lo que determina qué elementos secundarios están presentes. Si se usa la inicialización de componentes primarios sincrónicos, se garantiza que la inicialización primaria se complete primero. Si se usa la inicialización asincrónica de componentes primarios, no se puede determinar el orden de finalización de la inicialización de componentes primarios y secundarios porque depende del código de inicialización en ejecución.
Procesamiento de eventos DOM:
- Se ejecuta el controlador de eventos.
- Si se devuelve un elemento Task incompleto, Task se espera y, luego, el componente se vuelve a representar.
- Realice la representación de todo el trabajo sincrónico y complete la clase Task.
Ciclo de vida de Render
:
- Evita más operaciones de representación en el componente cuando se cumplan las dos condiciones siguientes:
- No es la primera representación.
-
ShouldRender
devuelvefalse
.
- Compile la diff (comparación) del árbol de representación y represente el componente.
- Espera a que se actualice el DOM.
- Llame a
OnAfterRender{Async}
. Se llama al método sincrónico antes del método asincrónico.
Las llamadas del desarrollador a StateHasChanged
producen una representación. Para obtener más información, consulta Representación de componentes de Razor de ASP.NET Core.
Inactividad durante la representación previa
En las aplicaciones de Blazor del lado servidor, la representación previa espera a la inactividad, lo que significa que un componente no se representa hasta que todos los componentes del árbol de representación hayan terminado de representarse. La quiescencia puede provocar retrasos notables en la representación cuando un componente realiza tareas de larga duración durante la inicialización y otros métodos de ciclo de vida, lo que conduce a una experiencia de usuario deficiente. Para obtener más información, consulte la sección "Manejar acciones asincrónicas incompletas durante el renderizado" más adelante en este artículo.
Cuando los parámetros están establecidos (SetParametersAsync
)
SetParametersAsync define los parámetros que proporciona el elemento primario del componente en el árbol de representación o a partir de los parámetros de ruta.
El parámetro ParameterView del método contiene el conjunto de valores de parámetros de componentes del componente cada vez que se llama a SetParametersAsync. Al invalidar el método SetParametersAsync, el código del desarrollador puede interactuar directamente con los parámetros de ParameterView.
La implementación predeterminada de SetParametersAsync establece el valor de cada propiedad con el atributo [Parameter]
o [CascadingParameter]
, que tiene un valor correspondiente en ParameterView. Los parámetros que no tienen un valor correspondiente en ParameterView se dejan sin cambios.
Por lo general, el código debe llamar al método de clase base (await base.SetParametersAsync(parameters);
) al invalidar SetParametersAsync. En escenarios avanzados, el código del desarrollador puede interpretar los valores de los parámetros entrantes de la forma que desee sin invocar el método de la clase base. Por ejemplo, no hay ningún requisito para asignar los parámetros entrantes a las propiedades de la clase. Sin embargo, debes consultar el ComponentBase
origen de referencia cuando estructures tu código sin llamar al método de la clase base, ya que este llama a otros métodos del ciclo de vida y desencadena la representación de forma compleja.
Nota
Los vínculos de la documentación al origen de referencia de .NET cargan normalmente la rama predeterminada del repositorio, que representa el desarrollo actual para la próxima versión de .NET. Para seleccionar una etiqueta de una versión específica, usa la lista desplegable Cambiar ramas o etiquetas. Para obtener más información, vea Procedimientos para seleccionar una etiqueta de versión de código fuente de ASP.NET Core (dotnet/AspNetCore.Docs #26205).
Si deseas confiar en la lógica de inicialización y representación de ComponentBase.SetParametersAsync pero no procesar los parámetros entrantes, tienes la opción de pasar un ParameterView vacío al método de la clase base:
await base.SetParametersAsync(ParameterView.Empty);
Si los controladores de eventos se proporcionan en el código del desarrollador, desenlácelos para su eliminación. Para más información, consulte Consumo de componentes ASP.NET CoreRazor.
En el ejemplo siguiente, ParameterView.TryGetValue asigna el valor del parámetro Param
a value
si el análisis de un parámetro de ruta para Param
se realiza correctamente. Cuando value
no es null
, el componente muestra el valor.
Aunque la coincidencia de parámetros de ruta no distingue entre mayúsculas y minúsculas, TryGetValue solo coincide con los nombres de parámetro que sí lo hacen en la plantilla de ruta. En el ejemplo siguiente se requiere el uso de /{Param?}
en la plantilla de ruta para obtener el valor con TryGetValue, y no /{param?}
. Si se usa /{param?}
en este escenario, TryGetValue devuelve false
y message
no se establece en ninguna cadena message
.
SetParamsAsync.razor
:
@page "/set-params-async/{Param?}"
<PageTitle>Set Parameters Async</PageTitle>
<h1>Set Parameters Async Example</h1>
<p>@message</p>
@code {
private string message = "Not set";
[Parameter]
public string? Param { get; set; }
public override async Task SetParametersAsync(ParameterView parameters)
{
if (parameters.TryGetValue<string>(nameof(Param), out var value))
{
if (value is null)
{
message = "The value of 'Param' is null.";
}
else
{
message = $"The value of 'Param' is {value}.";
}
}
await base.SetParametersAsync(parameters);
}
}
@page "/set-params-async/{Param?}"
<PageTitle>Set Parameters Async</PageTitle>
<h1>Set Parameters Async Example</h1>
<p>@message</p>
@code {
private string message = "Not set";
[Parameter]
public string? Param { get; set; }
public override async Task SetParametersAsync(ParameterView parameters)
{
if (parameters.TryGetValue<string>(nameof(Param), out var value))
{
if (value is null)
{
message = "The value of 'Param' is null.";
}
else
{
message = $"The value of 'Param' is {value}.";
}
}
await base.SetParametersAsync(parameters);
}
}
@page "/set-params-async/{Param?}"
<p>@message</p>
@code {
private string message = "Not set";
[Parameter]
public string? Param { get; set; }
public override async Task SetParametersAsync(ParameterView parameters)
{
if (parameters.TryGetValue<string>(nameof(Param), out var value))
{
if (value is null)
{
message = "The value of 'Param' is null.";
}
else
{
message = $"The value of 'Param' is {value}.";
}
}
await base.SetParametersAsync(parameters);
}
}
@page "/set-params-async/{Param?}"
<p>@message</p>
@code {
private string message = "Not set";
[Parameter]
public string? Param { get; set; }
public override async Task SetParametersAsync(ParameterView parameters)
{
if (parameters.TryGetValue<string>(nameof(Param), out var value))
{
if (value is null)
{
message = "The value of 'Param' is null.";
}
else
{
message = $"The value of 'Param' is {value}.";
}
}
await base.SetParametersAsync(parameters);
}
}
@page "/set-params-async/{Param?}"
<p>@message</p>
@code {
private string message = "Not set";
[Parameter]
public string Param { get; set; }
public override async Task SetParametersAsync(ParameterView parameters)
{
if (parameters.TryGetValue<string>(nameof(Param), out var value))
{
if (value is null)
{
message = "The value of 'Param' is null.";
}
else
{
message = $"The value of 'Param' is {value}.";
}
}
await base.SetParametersAsync(parameters);
}
}
@page "/set-params-async"
@page "/set-params-async/{Param}"
<p>@message</p>
@code {
private string message = "Not set";
[Parameter]
public string Param { get; set; }
public override async Task SetParametersAsync(ParameterView parameters)
{
if (parameters.TryGetValue<string>(nameof(Param), out var value))
{
if (value is null)
{
message = "The value of 'Param' is null.";
}
else
{
message = $"The value of 'Param' is {value}.";
}
}
await base.SetParametersAsync(parameters);
}
}
Inicialización de componentes (OnInitialized{Async}
)
OnInitialized y OnInitializedAsync se usan exclusivamente para inicializar un componente durante toda la duración de la instancia del componente. Los valores de parámetro y los cambios en estos no deben afectar a la inicialización realizada en estos métodos. Por ejemplo, la carga de opciones estáticas en una lista desplegable que no cambia durante la vigencia del componente y que no depende de los valores de parámetro se realiza en uno de estos métodos de ciclo de vida. Si los valores de parámetro o los cambios en los valores de parámetro afectan al estado del componente, use OnParametersSet{Async}
en su lugar.
Estos métodos se invocan cuando se inicializa el componente después de haber recibido sus parámetros iniciales en SetParametersAsync. Se llama al método sincrónico antes del método asincrónico.
Si se usa la inicialización de componentes primarios sincrónicos, se garantiza que la inicialización primaria se complete antes que la de los componentes secundarios. Si se usa la inicialización asincrónica de componentes primarios, no se puede determinar el orden de finalización de la inicialización de componentes primarios y secundarios porque depende del código de inicialización en ejecución.
En el caso de una operación sincrónica, invalide OnInitialized:
OnInit.razor
:
@page "/on-init"
<PageTitle>On Initialized</PageTitle>
<h1>On Initialized Example</h1>
<p>@message</p>
@code {
private string? message;
protected override void OnInitialized() =>
message = $"Initialized at {DateTime.Now}";
}
@page "/on-init"
<PageTitle>On Initialized</PageTitle>
<h1>On Initialized Example</h1>
<p>@message</p>
@code {
private string? message;
protected override void OnInitialized() =>
message = $"Initialized at {DateTime.Now}";
}
@page "/on-init"
<p>@message</p>
@code {
private string? message;
protected override void OnInitialized()
{
message = $"Initialized at {DateTime.Now}";
}
}
@page "/on-init"
<p>@message</p>
@code {
private string? message;
protected override void OnInitialized()
{
message = $"Initialized at {DateTime.Now}";
}
}
@page "/on-init"
<p>@message</p>
@code {
private string message;
protected override void OnInitialized()
{
message = $"Initialized at {DateTime.Now}";
}
}
@page "/on-init"
<p>@message</p>
@code {
private string message;
protected override void OnInitialized()
{
message = $"Initialized at {DateTime.Now}";
}
}
Para realizar una operación asincrónica, invalida OnInitializedAsync y usa el operador await
:
protected override async Task OnInitializedAsync()
{
await ...
}
Si se usa una clase base personalizada con lógica de inicialización personalizada, llama a OnInitializedAsync en la clase base:
protected override async Task OnInitializedAsync()
{
await ...
await base.OnInitializedAsync();
}
No es necesario llamar a ComponentBase.OnInitializedAsync a menos que se use una clase base personalizada con lógica personalizada. Para obtener más información, consulta la sección Métodos de ciclo de vida de clases base.
Un componente debe asegurarse de que está en un estado válido para el renderizado cuando OnInitializedAsync espera a un Taskque podría estar incompleto. Si el método devuelve un Taskincompleto, la parte del método que se completa de forma sincrónica debe dejar el componente en un estado válido para la representación. Para obtener más información, vea las observaciones introductorias del contexto de sincronización de ASP.NET Core Blazor y la eliminación de componentes de ASP.NET Core Razor.
Las aplicaciones de Blazor que representan previamente su contenido en el servidor llaman a OnInitializedAsyncdos veces:
- Una vez cuando el componente es renderizado inicialmente de forma estática como parte de la página.
- Una segunda vez cuando el explorador representa el componente.
Para evitar que el código de desarrollador en OnInitializedAsync se ejecute dos veces al realizar la representación previa, consulta la sección Reconexión con estado después de la representación previa. El contenido de la sección se centra en aplicaciones Blazor Web Apps y el estado SignalRde la reconexión. Para conservar el estado durante la ejecución del código de inicialización durante la representación previa, consulta Representación previa de componentes Razor de ASP.NET Core.
Para evitar que el código de desarrollador en OnInitializedAsync se ejecute dos veces al realizar la representación previa, consulta la sección Reconexión con estado después de la representación previa. Aunque el contenido de la sección se centra en Blazor Server y en la reconexiónSignalRcon estado, el escenario para la representación previa en las soluciones Blazor WebAssembly hospedadas (WebAssemblyPrerendered) conlleva condiciones y enfoques similares para impedir que el código del desarrollador se ejecute dos veces. Para conservar el estado durante la ejecución del código de inicialización durante la representación previa, consulte Integración de ASP.NET componentes principales Razor con MVC o Razor Pages.
Durante la representación previa de una aplicación Blazor, no es posible realizar ciertas acciones, como llamar a JavaScript (interoperabilidad de JS). Es posible que los componentes tengan que representarse de forma diferente cuando se representen previamente. Para obtener más información, consulta la sección Representación previa con interoperabilidad de JavaScript.
Si los controladores de eventos se proporcionan en el código del desarrollador, desenlácelos para su eliminación. Para más información, consulte Consumo de componentes ASP.NET CoreRazor.
Use representación de streaming con la representación estática del lado servidor (SSR estático) o la representación previa para mejorar la experiencia del usuario para los componentes que realizan tareas asincrónicas de larga duración en OnInitializedAsync para una representación completa. Para obtener más información, consulte los siguientes recursos:
Después de establecer los parámetros (OnParametersSet{Async}
)
Se llaman OnParametersSet o OnParametersSetAsync:
Después de inicializar el componente en OnInitialized o OnInitializedAsync.
Cuando el componente primario vuelve a representarse y proporciona lo siguiente:
- Tipos inmutables primitivos conocidos cuando un parámetro por lo menos ha cambiado.
- Parámetros de tipo complejo. El marco no puede saber si los valores de un parámetro de tipo complejo se han mutado internamente, por lo que siempre trata el conjunto de parámetros como modificado cuando uno o más parámetros de tipo complejo están presentes.
Para obtener más información sobre las convenciones de representación, consulta la representación de componentes en Razor ASP.NET Core.
Se llama al método sincrónico antes del método asincrónico.
Los métodos se pueden invocar incluso aunque los valores de parámetro no hayan cambiado. Este comportamiento subraya la necesidad de que los desarrolladores implementen lógica adicional dentro de los métodos para comprobar si los valores de parámetro han cambiado antes de volver a inicializar los datos o el estado dependientes de esos parámetros.
En el siguiente componente de ejemplo, vaya a la página del componente con una dirección URL:
- Con una fecha de inicio recibida por
StartDate
:/on-parameters-set/2021-03-19
- Sin una fecha de inicio, donde a
StartDate
se le asigna un valor de la hora local actual:/on-parameters-set
Nota
En una ruta de componente, no es posible restringir un parámetro DateTime con datetime
de restricción de ruta y hacer que el parámetro sea opcional. Por lo tanto, el siguiente componente OnParamsSet
utiliza dos directivas @page
para controlar el enrutamiento con y sin un segmento de fecha proporcionado en la dirección URL.
OnParamsSet.razor
:
@page "/on-params-set"
@page "/on-params-set/{StartDate:datetime}"
<PageTitle>On Parameters Set</PageTitle>
<h1>On Parameters Set Example</h1>
<p>
Pass a datetime in the URI of the browser's address bar.
For example, add <code>/1-1-2024</code> to the address.
</p>
<p>@message</p>
@code {
private string? message;
[Parameter]
public DateTime StartDate { get; set; }
protected override void OnParametersSet()
{
if (StartDate == default)
{
StartDate = DateTime.Now;
message = $"No start date in URL. Default value applied " +
$"(StartDate: {StartDate}).";
}
else
{
message = $"The start date in the URL was used " +
$"(StartDate: {StartDate}).";
}
}
}
@page "/on-params-set"
@page "/on-params-set/{StartDate:datetime}"
<PageTitle>On Parameters Set</PageTitle>
<h1>On Parameters Set Example</h1>
<p>
Pass a datetime in the URI of the browser's address bar.
For example, add <code>/1-1-2024</code> to the address.
</p>
<p>@message</p>
@code {
private string? message;
[Parameter]
public DateTime StartDate { get; set; }
protected override void OnParametersSet()
{
if (StartDate == default)
{
StartDate = DateTime.Now;
message = $"No start date in URL. Default value applied " +
$"(StartDate: {StartDate}).";
}
else
{
message = $"The start date in the URL was used " +
$"(StartDate: {StartDate}).";
}
}
}
@page "/on-params-set"
@page "/on-params-set/{StartDate:datetime}"
<p>@message</p>
@code {
private string? message;
[Parameter]
public DateTime StartDate { get; set; }
protected override void OnParametersSet()
{
if (StartDate == default)
{
StartDate = DateTime.Now;
message = $"No start date in URL. Default value applied (StartDate: {StartDate}).";
}
else
{
message = $"The start date in the URL was used (StartDate: {StartDate}).";
}
}
}
@page "/on-params-set"
@page "/on-params-set/{StartDate:datetime}"
<p>@message</p>
@code {
private string? message;
[Parameter]
public DateTime StartDate { get; set; }
protected override void OnParametersSet()
{
if (StartDate == default)
{
StartDate = DateTime.Now;
message = $"No start date in URL. Default value applied (StartDate: {StartDate}).";
}
else
{
message = $"The start date in the URL was used (StartDate: {StartDate}).";
}
}
}
@page "/on-params-set"
@page "/on-params-set/{StartDate:datetime}"
<p>@message</p>
@code {
private string message;
[Parameter]
public DateTime StartDate { get; set; }
protected override void OnParametersSet()
{
if (StartDate == default)
{
StartDate = DateTime.Now;
message = $"No start date in URL. Default value applied (StartDate: {StartDate}).";
}
else
{
message = $"The start date in the URL was used (StartDate: {StartDate}).";
}
}
}
@page "/on-params-set"
@page "/on-params-set/{StartDate:datetime}"
<p>@message</p>
@code {
private string message;
[Parameter]
public DateTime StartDate { get; set; }
protected override void OnParametersSet()
{
if (StartDate == default)
{
StartDate = DateTime.Now;
message = $"No start date in URL. Default value applied (StartDate: {StartDate}).";
}
else
{
message = $"The start date in the URL was used (StartDate: {StartDate}).";
}
}
}
El trabajo asincrónico al aplicar parámetros y valores de propiedad debe producirse durante el evento de ciclo de vida de OnParametersSetAsync:
protected override async Task OnParametersSetAsync()
{
await ...
}
Si se usa una clase base personalizada con lógica de inicialización personalizada, llama a OnParametersSetAsync en la clase base:
protected override async Task OnParametersSetAsync()
{
await ...
await base.OnParametersSetAsync();
}
No es necesario llamar a ComponentBase.OnParametersSetAsync a menos que se use una clase base personalizada con lógica personalizada. Para obtener más información, consulta la sección Métodos de ciclo de vida de clases base.
Un componente debe asegurarse de que está en un estado válido para el renderizado cuando OnParametersSetAsync espera a un Taskque podría estar incompleto. Si el método devuelve un Taskincompleto, la parte del método que se completa de forma sincrónica debe dejar el componente en un estado válido para la representación. Para obtener más información, vea las observaciones introductorias del contexto de sincronización de ASP.NET Core Blazor y la eliminación de componentes de ASP.NET Core Razor.
Si los controladores de eventos se proporcionan en el código del desarrollador, desenlácelos para su eliminación. Para más información, consulte Consumo de componentes ASP.NET CoreRazor.
Si un componente desechable no usa una CancellationToken, OnParametersSet y OnParametersSetAsync se debe comprobar si el componente se elimina. Si OnParametersSetAsync devuelve un Taskincompleto, el componente debe asegurarse de que la parte del método que se completa sincrónicamente deja el componente en un estado válido para la representación. Para obtener más información, vea las observaciones introductorias del contexto de sincronización de ASP.NET Core Blazor y la eliminación de componentes de ASP.NET Core Razor.
Para obtener más información sobre los parámetros de ruta y las restricciones, consulta Enrutamiento y navegación de Blazor de ASP.NET Core.
Para obtener un ejemplo de implementación SetParametersAsync
manual para mejorar el rendimiento en algunos escenarios, consulte procedimientos recomendados de rendimiento de representación de ASP.NET CoreBlazor.
Después de renderizar el componente (OnAfterRender{Async}
)
OnAfterRender y OnAfterRenderAsync se invocan después de que un componente se haya representado de forma interactiva y la interfaz de usuario haya terminado de actualizarse (por ejemplo, después de agregar elementos al DOM del explorador). En este momento, se rellenan las referencias a elementos y componentes. Usa esta fase para realizar pasos de inicialización adicionales con el contenido representado, como llamadas de interoperabilidad de JS que interactúan con los elementos DOM representados. Se llama al método sincrónico antes del método asincrónico.
Estos métodos no se invocan durante la representación previa o la representación estática del lado servidor (SSR estático) en el servidor porque esos procesos no están conectados a un DOM del explorador activo y ya están completos antes de actualizar el DOM.
Para OnAfterRenderAsync, el componente no se actualiza automáticamente después de la finalización de cualquier Task
devuelto para evitar un bucle de renderizado infinito.
Se llama a OnAfterRender y OnAfterRenderAsync una vez que un componente haya terminado la representación. En este momento, se rellenan las referencias a elementos y componentes. Usa esta fase para realizar pasos de inicialización adicionales con el contenido representado, como llamadas de interoperabilidad de JS que interactúan con los elementos DOM representados. Se llama al método sincrónico antes del método asincrónico.
Estos métodos no se invocan durante la representación previa porque la representación previa no está conectada a un DOM del explorador activo y ya está completa antes de actualizar el DOM.
Para OnAfterRenderAsync, el componente no se actualiza automáticamente después de la finalización de cualquier Task
devuelto para evitar un bucle de renderizado infinito.
El parámetro firstRender
para OnAfterRender y OnAfterRenderAsync:
- Se establece en
true
la primera vez que se representa la instancia del componente. - Se puede utilizar para garantizar que el trabajo de inicialización solo se realiza una vez.
AfterRender.razor
:
@page "/after-render"
@inject ILogger<AfterRender> Logger
<PageTitle>After Render</PageTitle>
<h1>After Render Example</h1>
<p>
<button @onclick="HandleClick">Log information (and trigger a render)</button>
</p>
<p>Study logged messages in the console.</p>
@code {
protected override void OnAfterRender(bool firstRender) =>
Logger.LogInformation("firstRender = {FirstRender}", firstRender);
private void HandleClick() => Logger.LogInformation("HandleClick called");
}
@page "/after-render"
@inject ILogger<AfterRender> Logger
<PageTitle>After Render</PageTitle>
<h1>After Render Example</h1>
<p>
<button @onclick="HandleClick">Log information (and trigger a render)</button>
</p>
<p>Study logged messages in the console.</p>
@code {
protected override void OnAfterRender(bool firstRender) =>
Logger.LogInformation("firstRender = {FirstRender}", firstRender);
private void HandleClick() => Logger.LogInformation("HandleClick called");
}
@page "/after-render"
@inject ILogger<AfterRender> Logger
<PageTitle>After Render</PageTitle>
<h1>After Render Example</h1>
<p>
<button @onclick="HandleClick">Log information (and trigger a render)</button>
</p>
<p>Study logged messages in the console.</p>
@code {
protected override void OnAfterRender(bool firstRender)
{
Logger.LogInformation("OnAfterRender: firstRender = {FirstRender}", firstRender);
}
private void HandleClick()
{
Logger.LogInformation("HandleClick called");
}
}
@page "/after-render"
@inject ILogger<AfterRender> Logger
<PageTitle>After Render</PageTitle>
<h1>After Render Example</h1>
<p>
<button @onclick="HandleClick">Log information (and trigger a render)</button>
</p>
<p>Study logged messages in the console.</p>
@code {
protected override void OnAfterRender(bool firstRender)
{
Logger.LogInformation("OnAfterRender: firstRender = {FirstRender}", firstRender);
}
private void HandleClick()
{
Logger.LogInformation("HandleClick called");
}
}
@page "/after-render"
@using Microsoft.Extensions.Logging
@inject ILogger<AfterRender> Logger
<h1>After Render Example</h1>
<p>
<button @onclick="HandleClick">Log information (and trigger a render)</button>
</p>
<p>Study logged messages in the console.</p>
@code {
protected override void OnAfterRender(bool firstRender)
{
Logger.LogInformation("OnAfterRender: firstRender = {FirstRender}", firstRender);
}
private void HandleClick()
{
Logger.LogInformation("HandleClick called");
}
}
@page "/after-render"
@using Microsoft.Extensions.Logging
@inject ILogger<AfterRender> Logger
<h1>After Render Example</h1>
<p>
<button @onclick="HandleClick">Log information (and trigger a render)</button>
</p>
<p>Study logged messages in the console.</p>
@code {
protected override void OnAfterRender(bool firstRender)
{
Logger.LogInformation("OnAfterRender: firstRender = {FirstRender}", firstRender);
}
private void HandleClick()
{
Logger.LogInformation("HandleClick called");
}
}
El ejemplo de AfterRender.razor
genera la siguiente salida en la consola cuando se carga la página y se selecciona el botón:
OnAfterRender: firstRender = True
HandleClick called
OnAfterRender: firstRender = False
El trabajo asincrónico inmediatamente después de la representación debe producirse durante el evento de ciclo de vida de OnAfterRenderAsync:
protected override async Task OnAfterRenderAsync(bool firstRender)
{
...
}
Si se usa una clase base personalizada con lógica de inicialización personalizada, llama a OnAfterRenderAsync en la clase base:
protected override async Task OnAfterRenderAsync(bool firstRender)
{
...
await base.OnAfterRenderAsync(firstRender);
}
No es necesario llamar a ComponentBase.OnAfterRenderAsync a menos que se use una clase base personalizada con lógica personalizada. Para obtener más información, consulta la sección Métodos de ciclo de vida de clases base.
Incluso si se devuelve un elemento Task desde OnAfterRenderAsync, el marco no programa un ciclo de representación adicional para el componente una vez que la tarea se completa. Esto se hace para evitar un bucle de representación infinito. Esto es diferente de los otros métodos del ciclo de vida, que programan un ciclo de renderización adicional una vez que el Task devuelto se completa.
OnAfterRender y OnAfterRenderAsyncno se llaman durante el proceso de representación previa en el servidor Se llama a estos métodos cuando el componente se representa de forma interactiva una vez finalizada la representación previa. Cuando la aplicación se representa previamente:
- El componente se ejecuta en el servidor para generar algo de marcado HTML estático en la respuesta HTTP. Durante esta fase, no se llama a OnAfterRender ni a OnAfterRenderAsync.
- Cuando el script Blazor (
blazor.{server|webassembly|web}.js
) se inicia en el explorador, el componente se reinicia en modo de representación interactiva. Cuando un componente se reinicia, OnAfterRender llama a OnAfterRenderAsync y , dado que la aplicación ya no está en fase de representación previa.
Si los controladores de eventos se proporcionan en el código del desarrollador, desenlácelos para su eliminación. Para más información, consulte Consumo de componentes ASP.NET CoreRazor.
Métodos de ciclo de vida de clase base
Al invalidar los métodos de ciclo de vida de Blazor, no es necesario llamar a métodos de ciclo de vida de clase base para ComponentBase. Sin embargo, un componente debe llamar a un método de ciclo de vida de clase base invalidado en las siguientes situaciones:
- Cuando se invalida ComponentBase.SetParametersAsync, normalmente se invoca
await base.SetParametersAsync(parameters);
porque el método de la clase base llama a otros métodos del ciclo de vida y desencadena la representación de forma compleja. Para obtener más información, consulta la sección Cuando se establecen los parámetros(SetParametersAsync
). - Si el método de clase base contiene lógica que se debe ejecutar. Los usuarios de bibliotecas suelen llamar a métodos de ciclo de vida base al heredar una clase base porque las clases base de las bibliotecas suelen tener lógica de ciclo de vida personalizada para ejecutarse. Si la aplicación usa una clase base de una biblioteca, consulte la documentación de la biblioteca para obtener instrucciones.
En el ejemplo siguiente, se llama a base.OnInitialized();
para asegurarse de que se ejecute el método OnInitialized
de la clase base. Sin la llamada, BlazorRocksBase2.OnInitialized
no se ejecuta.
BlazorRocks2.razor
:
@page "/blazor-rocks-2"
@inherits BlazorRocksBase2
@inject ILogger<BlazorRocks2> Logger
<PageTitle>Blazor Rocks!</PageTitle>
<h1>Blazor Rocks! Example 2</h1>
<p>
@BlazorRocksText
</p>
@code {
protected override void OnInitialized()
{
Logger.LogInformation("Initialization code of BlazorRocks2 executed!");
base.OnInitialized();
}
}
@page "/blazor-rocks-2"
@inherits BlazorRocksBase2
@inject ILogger<BlazorRocks2> Logger
<PageTitle>Blazor Rocks!</PageTitle>
<h1>Blazor Rocks! Example 2</h1>
<p>
@BlazorRocksText
</p>
@code {
protected override void OnInitialized()
{
Logger.LogInformation("Initialization code of BlazorRocks2 executed!");
base.OnInitialized();
}
}
@page "/blazor-rocks-2"
@inherits BlazorRocksBase2
@inject ILogger<BlazorRocks2> Logger
<PageTitle>Blazor Rocks!</PageTitle>
<h1>Blazor Rocks! Example 2</h1>
<p>
@BlazorRocksText
</p>
@code {
protected override void OnInitialized()
{
Logger.LogInformation("Initialization code of BlazorRocks2 executed!");
base.OnInitialized();
}
}
@page "/blazor-rocks-2"
@inherits BlazorRocksBase2
@inject ILogger<BlazorRocks2> Logger
<PageTitle>Blazor Rocks!</PageTitle>
<h1>Blazor Rocks! Example 2</h1>
<p>
@BlazorRocksText
</p>
@code {
protected override void OnInitialized()
{
Logger.LogInformation("Initialization code of BlazorRocks2 executed!");
base.OnInitialized();
}
}
@page "/blazor-rocks-2"
@using Microsoft.Extensions.Logging
@inherits BlazorRocksBase2
@inject ILogger<BlazorRocks2> Logger
<h1>Blazor Rocks! Example 2</h1>
<p>
@BlazorRocksText
</p>
@code {
protected override void OnInitialized()
{
Logger.LogInformation("Initialization code of BlazorRocks2 executed!");
base.OnInitialized();
}
}
@page "/blazor-rocks-2"
@using Microsoft.Extensions.Logging
@inherits BlazorRocksBase2
@inject ILogger<BlazorRocks2> Logger
<h1>Blazor Rocks! Example 2</h1>
<p>
@BlazorRocksText
</p>
@code {
protected override void OnInitialized()
{
Logger.LogInformation("Initialization code of BlazorRocks2 executed!");
base.OnInitialized();
}
}
BlazorRocksBase2.cs
:
using Microsoft.AspNetCore.Components;
namespace BlazorSample;
public class BlazorRocksBase2 : ComponentBase
{
[Inject]
private ILogger<BlazorRocksBase2> Logger { get; set; } = default!;
public string BlazorRocksText { get; set; } = "Blazor rocks the browser!";
protected override void OnInitialized() =>
Logger.LogInformation("Initialization code of BlazorRocksBase2 executed!");
}
using Microsoft.AspNetCore.Components;
namespace BlazorSample;
public class BlazorRocksBase2 : ComponentBase
{
[Inject]
private ILogger<BlazorRocksBase2> Logger { get; set; } = default!;
public string BlazorRocksText { get; set; } = "Blazor rocks the browser!";
protected override void OnInitialized() =>
Logger.LogInformation("Initialization code of BlazorRocksBase2 executed!");
}
using Microsoft.AspNetCore.Components;
namespace BlazorSample;
public class BlazorRocksBase2 : ComponentBase
{
[Inject]
private ILogger<BlazorRocksBase2> Logger { get; set; } = default!;
public string BlazorRocksText { get; set; } =
"Blazor rocks the browser!";
protected override void OnInitialized()
{
Logger.LogInformation("Initialization code of BlazorRocksBase2 executed!");
}
}
using Microsoft.AspNetCore.Components;
namespace BlazorSample;
public class BlazorRocksBase2 : ComponentBase
{
[Inject]
private ILogger<BlazorRocksBase2> Logger { get; set; } = default!;
public string BlazorRocksText { get; set; } =
"Blazor rocks the browser!";
protected override void OnInitialized()
{
Logger.LogInformation("Initialization code of BlazorRocksBase2 executed!");
}
}
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Logging;
namespace BlazorSample;
public class BlazorRocksBase2 : ComponentBase
{
[Inject]
private ILogger<BlazorRocksBase2> Logger { get; set; } = default!;
public string BlazorRocksText { get; set; } =
"Blazor rocks the browser!";
protected override void OnInitialized()
{
Logger.LogInformation("Initialization code of BlazorRocksBase2 executed!");
}
}
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Logging;
namespace BlazorSample;
public class BlazorRocksBase2 : ComponentBase
{
[Inject]
private ILogger<BlazorRocksBase2> Logger { get; set; } = default!;
public string BlazorRocksText { get; set; } =
"Blazor rocks the browser!";
protected override void OnInitialized()
{
Logger.LogInformation("Initialization code of BlazorRocksBase2 executed!");
}
}
Cambios de estado (StateHasChanged
)
StateHasChanged notifica al componente que su estado ha cambiado. Cuando proceda, al llamar a StateHasChanged se aplica una nueva representación que se produce cuando el subproceso principal de la aplicación esté libre.
Se llama a StateHasChanged automáticamente para métodos EventCallback. Para obtener más información sobre las devoluciones de llamada de eventos, vea Control de eventos de Blazor en ASP.NET Core .
Para obtener más información sobre la representación de componentes y cuándo llamar a StateHasChanged, incluido cuando llamarlo con ComponentBase.InvokeAsync, consulta Representación de componentes de Razor de ASP.NET Core.
Control de acciones asincrónicas incompletas en la representación
Es posible que las acciones asincrónicas llevadas a cabo durante los eventos del ciclo de vida no se hayan completado antes de que se renderice el componente. Los objetos podrían ser null
o rellenarse de forma incompleta con datos mientras se ejecuta el método de ciclo de vida. Proporciona lógica de representación para confirmar que los objetos se inicializan. Represente los elementos de la interfaz de usuario del marcador de posición (por ejemplo, un mensaje de carga) mientras los objetos sean null
.
En el siguiente componente de Slow
, OnInitializedAsync se sobrescribe para ejecutar asincrónicamente una tarea prolongada. Mientras isLoading
es true
, se muestra al usuario un mensaje de carga. Una vez completada la devolución de Task
por OnInitializedAsync, el componente se renderiza de nuevo con el estado actualizado, mostrando el mensaje "Finished!
".
Slow.razor
:
@page "/slow"
<h2>Slow Component</h2>
@if (isLoading)
{
<div><em>Loading...</em></div>
}
else
{
<div>Finished!</div>
}
@code {
private bool isLoading = true;
protected override async Task OnInitializedAsync()
{
await LoadDataAsync();
isLoading = false;
}
private Task LoadDataAsync()
{
return Task.Delay(10000);
}
}
El componente anterior usa una variable isLoading
para mostrar el mensaje de carga. Se utiliza un enfoque similar para un componente que carga datos en una colección y verifica si la colección es null
para presentar el mensaje de carga. En el siguiente ejemplo se verifica si la colección movies
contiene null
para así mostrar el mensaje de carga o la colección de películas:
@if (movies == null)
{
<p><em>Loading...</em></p>
}
else
{
@* display movies *@
}
@code {
private Movies[]? movies;
protected override async Task OnInitializedAsync()
{
movies = await GetMovies();
}
}
La representación previa espera la inactividad, lo que significa que un componente no se representa hasta que todos los componentes del árbol de representación hayan terminado de representarse. Esto significa que un mensaje de carga no se muestra mientras el método OnInitializedAsync de un componente secundario ejecuta una tarea de ejecución prolongada durante la representación previa. Para demostrar este comportamiento, coloque el componente de Slow
anterior en el componente Home
de una aplicación de prueba:
@page "/"
<PageTitle>Home</PageTitle>
<h1>Hello, world!</h1>
Welcome to your new app.
<Slow />
Nota
Aunque en los ejemplos de esta sección se describe el método de ciclo de vida de OnInitializedAsync, otros métodos de ciclo de vida que se ejecutan durante la representación previa pueden retrasar la representación final de un componente. Solo OnAfterRender{Async}
no se ejecuta durante la representación previa y es ajeno a retrasos debido a la inactividad.
Durante la representación previa, el componente Home
no se representa hasta que se representa el componente Slow
, que tarda diez segundos. La interfaz de usuario está en blanco durante este período de diez segundos y no hay ningún mensaje de carga. Después de la representación previa, se representa el componente Home
y se muestra el mensaje de carga del componente Slow
. Después de diez segundos más, el componente Slow
finalmente muestra el mensaje finalizado.
Como se muestra en la demostración anterior, el modo inactivo durante la representación previa da como resultado una experiencia de usuario deficiente. Para mejorar la experiencia del usuario, empiece por implementar representación de streaming para evitar tener que esperar a que se complete la tarea asincrónica durante la representación previa.
Agregue el atributo [StreamRendering]
al componente Slow
(use [StreamRendering(true)]
en .NET 8):
@attribute [StreamRendering]
Cuando el componente Home
se representa previamente, el componente Slow
se representa rápidamente con su mensaje de carga. El componente Home
no espera diez segundos para que el componente Slow
finalice la representación. Sin embargo, el mensaje terminado que se muestra al final de la representación previa se reemplaza por el mensaje de carga mientras el componente finalmente se representa, que es otro retraso de diez segundos. Esto ocurre porque el componente Slow
se representa dos veces, junto con LoadDataAsync
ejecutándose dos veces. Cuando un componente accede a recursos, como servicios y bases de datos, la ejecución doble de llamadas de servicio y consultas de base de datos crea una carga no deseada en los recursos de la aplicación.
Para abordar la representación doble del mensaje de carga y la re-ejecución de las llamadas de servicio y base de datos, mantenga el estado pre-renderizado utilizando PersistentComponentState para la representación final del componente, como se ve en las siguientes actualizaciones del componente Slow
.
@page "/slow"
@attribute [StreamRendering]
<h2>Slow Component</h2>
@if (Data is null)
{
<div><em>Loading...</em></div>
}
else
{
<div>@Data</div>
}
@code {
[SupplyParameterFromPersistentComponentState]
public string? Data { get; set; }
protected override async Task OnInitializedAsync()
{
Data ??= await LoadDataAsync();
}
private async Task<string> LoadDataAsync()
{
await Task.Delay(5000);
return "Finished!";
}
}
@page "/slow"
@attribute [StreamRendering]
@implements IDisposable
@inject PersistentComponentState ApplicationState
<h2>Slow Component</h2>
@if (data is null)
{
<div><em>Loading...</em></div>
}
else
{
<div>@data</div>
}
@code {
private string? data;
private PersistingComponentStateSubscription persistingSubscription;
protected override async Task OnInitializedAsync()
{
if (!ApplicationState.TryTakeFromJson<string>(nameof(data), out var restored))
{
data = await LoadDataAsync();
}
else
{
data = restored!;
}
// Call at the end to avoid a potential race condition at app shutdown
persistingSubscription = ApplicationState.RegisterOnPersisting(PersistData);
}
private Task PersistData()
{
ApplicationState.PersistAsJson(nameof(data), data);
return Task.CompletedTask;
}
private async Task<string> LoadDataAsync()
{
await Task.Delay(5000);
return "Finished!";
}
void IDisposable.Dispose()
{
persistingSubscription.Dispose();
}
}
Mediante la combinación del renderizado en streaming con el estado persistente de componentes:
- Los servicios y las bases de datos solo requieren una sola llamada para la inicialización de componentes.
- Los componentes representan rápidamente sus interfaces de usuario mostrando mensajes de carga durante tareas prolongadas para ofrecer la mejor experiencia al usuario.
Para obtener más información, consulte los siguientes recursos:
- Representación de componentes Razor de ASP.NET Core
- Representación previa de componentes de Razor de ASP.NET Core
:::moniker-end
El modo inactivo durante la representación previa da como resultado una experiencia de usuario deficiente. El retraso se puede solucionar en las aplicaciones que tienen como destino .NET 8 o posterior con una característica denominada transmisión de representación, normalmente combinada con estado de componente persistente durante la representación previa para evitar esperar a que se complete la tarea asincrónica. En versiones de .NET anteriores a .NET 8, la ejecución de una tarea en segundo plano prolongada que carga los datos después de la representación final puede abordar un retraso de representación largo debido a la inactividad.
Control de errores
Para obtener más información sobre cómo controlar los errores durante la ejecución del método de ciclo de vida, consulta Control de errores en aplicaciones Blazor de ASP.NET Core.
Reconexión con estado después de la representación previa
Al representarse previamente en el servidor, un componente se representa inicialmente de forma estática como parte de la página. Una vez que el explorador vuelve a establecer una conexión SignalR con el servidor, el componente se representa otra vez y es interactivo. Si el método de ciclo de vida OnInitialized{Async}
para inicializar el componente está presente, el método se ejecuta dos veces:
- Cuando el componente se representa previamente de forma estática.
- Después de establecerse la conexión con el servidor.
Esto puede dar como resultado un cambio evidente en los datos mostrados en la interfaz de usuario cuando el componente se representa finalmente. Para evitar este comportamiento, pasa un identificador para almacenar en caché el estado durante la representación previa y recuperar el estado después de la representación previa.
En el código siguiente se muestra un objeto WeatherForecastService
que evita el cambio en la presentación de datos debido a la representación previa. El esperado Delay (await Task.Delay(...)
) simula un breve retraso antes de devolver datos del método GetForecastAsync
.
Agregue servicios IMemoryCache con AddMemoryCache en la colección de servicios en el archivo Program
de la aplicación:
builder.Services.AddMemoryCache();
WeatherForecastService.cs
:
using Microsoft.Extensions.Caching.Memory;
namespace BlazorSample;
public class WeatherForecastService(IMemoryCache memoryCache)
{
private static readonly string[] summaries =
[
"Freezing", "Bracing", "Chilly", "Cool", "Mild",
"Warm", "Balmy", "Hot", "Sweltering", "Scorching"
];
public IMemoryCache MemoryCache { get; } = memoryCache;
public Task<WeatherForecast[]?> GetForecastAsync(DateOnly startDate)
{
return MemoryCache.GetOrCreateAsync(startDate, async e =>
{
e.SetOptions(new MemoryCacheEntryOptions
{
AbsoluteExpirationRelativeToNow =
TimeSpan.FromSeconds(30)
});
await Task.Delay(TimeSpan.FromSeconds(10));
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = startDate.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = summaries[Random.Shared.Next(summaries.Length)]
}).ToArray();
});
}
}
using Microsoft.Extensions.Caching.Memory;
namespace BlazorSample;
public class WeatherForecastService(IMemoryCache memoryCache)
{
private static readonly string[] summaries =
[
"Freezing", "Bracing", "Chilly", "Cool", "Mild",
"Warm", "Balmy", "Hot", "Sweltering", "Scorching"
];
public IMemoryCache MemoryCache { get; } = memoryCache;
public Task<WeatherForecast[]?> GetForecastAsync(DateOnly startDate)
{
return MemoryCache.GetOrCreateAsync(startDate, async e =>
{
e.SetOptions(new MemoryCacheEntryOptions
{
AbsoluteExpirationRelativeToNow =
TimeSpan.FromSeconds(30)
});
await Task.Delay(TimeSpan.FromSeconds(10));
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = startDate.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = summaries[Random.Shared.Next(summaries.Length)]
}).ToArray();
});
}
}
using Microsoft.Extensions.Caching.Memory;
public class WeatherForecastService
{
private static readonly string[] summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild",
"Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
public WeatherForecastService(IMemoryCache memoryCache)
{
MemoryCache = memoryCache;
}
public IMemoryCache MemoryCache { get; }
public Task<WeatherForecast[]?> GetForecastAsync(DateTime startDate)
{
return MemoryCache.GetOrCreateAsync(startDate, async e =>
{
e.SetOptions(new MemoryCacheEntryOptions
{
AbsoluteExpirationRelativeToNow =
TimeSpan.FromSeconds(30)
});
await Task.Delay(TimeSpan.FromSeconds(10));
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = startDate.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = summaries[Random.Shared.Next(summaries.Length)]
}).ToArray();
});
}
}
using Microsoft.Extensions.Caching.Memory;
public class WeatherForecastService
{
private static readonly string[] summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild",
"Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
public WeatherForecastService(IMemoryCache memoryCache)
{
MemoryCache = memoryCache;
}
public IMemoryCache MemoryCache { get; }
public Task<WeatherForecast[]> GetForecastAsync(DateTime startDate)
{
return MemoryCache.GetOrCreateAsync(startDate, async e =>
{
e.SetOptions(new MemoryCacheEntryOptions
{
AbsoluteExpirationRelativeToNow =
TimeSpan.FromSeconds(30)
});
await Task.Delay(TimeSpan.FromSeconds(10));
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = startDate.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = summaries[Random.Shared.Next(summaries.Length)]
}).ToArray();
});
}
}
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Memory;
using BlazorSample.Shared;
public class WeatherForecastService
{
private static readonly string[] summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild",
"Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
public WeatherForecastService(IMemoryCache memoryCache)
{
MemoryCache = memoryCache;
}
public IMemoryCache MemoryCache { get; }
public Task<WeatherForecast[]> GetForecastAsync(DateTime startDate)
{
return MemoryCache.GetOrCreateAsync(startDate, async e =>
{
e.SetOptions(new MemoryCacheEntryOptions
{
AbsoluteExpirationRelativeToNow =
TimeSpan.FromSeconds(30)
});
var rng = new Random();
await Task.Delay(TimeSpan.FromSeconds(10));
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = startDate.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = summaries[rng.Next(summaries.Length)]
}).ToArray();
});
}
}
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Memory;
using BlazorSample.Shared;
public class WeatherForecastService
{
private static readonly string[] summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild",
"Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
public WeatherForecastService(IMemoryCache memoryCache)
{
MemoryCache = memoryCache;
}
public IMemoryCache MemoryCache { get; }
public Task<WeatherForecast[]> GetForecastAsync(DateTime startDate)
{
return MemoryCache.GetOrCreateAsync(startDate, async e =>
{
e.SetOptions(new MemoryCacheEntryOptions
{
AbsoluteExpirationRelativeToNow =
TimeSpan.FromSeconds(30)
});
var rng = new Random();
await Task.Delay(TimeSpan.FromSeconds(10));
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = startDate.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = summaries[rng.Next(summaries.Length)]
}).ToArray();
});
}
}
Para obtener más información sobre RenderMode, consulta Guía de BlazorSignalR para ASP.NET Core.
El contenido de esta sección se centra en aplicaciones Blazor Web Apps y el estado SignalRde la reconexión. Para conservar el estado durante la ejecución del código de inicialización durante la representación previa, consulta Representación previa de componentes Razor de ASP.NET Core.
Aunque el contenido de la sección se centra en Blazor Server y en la reconexiónSignalRcon estado, el escenario para la representación previa en las soluciones Blazor WebAssembly hospedadas (WebAssemblyPrerendered) conlleva condiciones y enfoques similares para impedir que el código del desarrollador se ejecute dos veces. Para conservar el estado durante la ejecución del código de inicialización durante la representación previa, consulte Integración de ASP.NET componentes principales Razor con MVC o Razor Pages.
Representación previa con interoperabilidad de JavaScript
Esta sección se dirige a las aplicaciones del lado servidor que ofrecen una representación previa de los componentes de Razor. La representación previa se describe en Representación previa de componentes Razor de ASP.NET Core.
Nota
La navegación interna para el enrutamiento interactivo en Blazor Web App no implica solicitar contenido de página nuevo desde el servidor. Por lo tanto, la representación previa no se produce para las solicitudes de página internas. Si la aplicación adopta el enrutamiento interactivo, realiza una recarga de página completa para los ejemplos de componentes que muestran el comportamiento de representación previa. Para obtener más información, consulta Representación previa de componentes Razor de ASP.NET Core .
Esta sección se aplica a las apps del lado del servidor y a las apps Blazor WebAssembly alojadas que pre-renderizan componentes Razor. La representación previa se trata en Integración de componentes de ASP.NET Core Razor con MVC o Razor Páginas.
Durante la representación previa, no es posible llamar a JavaScript (JS). En el ejemplo siguiente se muestra cómo usar la interoperabilidad de JS como parte de la lógica de inicialización de un componente de una manera compatible con la representación previa.
La siguiente función scrollElementIntoView
:
- Se desplaza hasta el elemento pasado con
scrollIntoView
. - Devuelve el valor de la propiedad
top
del elemento proporcionado desde el métodogetBoundingClientRect
.
window.scrollElementIntoView = (element) => {
element.scrollIntoView();
return element.getBoundingClientRect().top;
}
Cuando IJSRuntime.InvokeAsync llama a la función JS en el código de componente, ElementReference solo se utiliza en OnAfterRenderAsync y no en ningún otro método de ciclo de vida anterior porque no existe ningún elemento HTML de DOM hasta después de representar el componente.
Se invoca a StateHasChanged
(origen de referencia) para programar la re-renderización del componente con el nuevo estado obtenido de la llamada de interoperabilidad de JS (consulta Representación de componentes de Razor de ASP.NET Core para obtener más información). No se crea un bucle infinito porque solo se llama a StateHasChanged cuando scrollPosition
es null
.
PrerenderedInterop.razor
:
@page "/prerendered-interop"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS
<PageTitle>Prerendered Interop</PageTitle>
<h1>Prerendered Interop Example</h1>
<div @ref="divElement" style="margin-top:2000px">
Set value via JS interop call: <strong>@scrollPosition</strong>
</div>
@code {
private ElementReference divElement;
private double? scrollPosition;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender && scrollPosition is null)
{
scrollPosition = await JS.InvokeAsync<double>(
"scrollElementIntoView", divElement);
StateHasChanged();
}
}
}
@page "/prerendered-interop"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS
<h1>Prerendered Interop Example</h1>
<div @ref="divElement" style="margin-top:2000px">
Set value via JS interop call: <strong>@scrollPosition</strong>
</div>
@code {
private ElementReference divElement;
private double? scrollPosition;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender && scrollPosition is null)
{
scrollPosition = await JS.InvokeAsync<double>(
"scrollElementIntoView", divElement);
StateHasChanged();
}
}
}
@page "/prerendered-interop"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS
<h1>Prerendered Interop Example</h1>
<div @ref="divElement" style="margin-top:2000px">
Set value via JS interop call: <strong>@scrollPosition</strong>
</div>
@code {
private ElementReference divElement;
private double? scrollPosition;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender && scrollPosition is null)
{
scrollPosition = await JS.InvokeAsync<double>(
"scrollElementIntoView", divElement);
StateHasChanged();
}
}
}
@page "/prerendered-interop"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS
<h1>Prerendered Interop Example</h1>
<div @ref="divElement" style="margin-top:2000px">
Set value via JS interop call: <strong>@scrollPosition</strong>
</div>
@code {
private ElementReference divElement;
private double? scrollPosition;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender && scrollPosition is null)
{
scrollPosition = await JS.InvokeAsync<double>(
"scrollElementIntoView", divElement);
StateHasChanged();
}
}
}
@page "/prerendered-interop"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS
<h1>Prerendered Interop Example</h1>
<div @ref="divElement" style="margin-top:2000px">
Set value via JS interop call: <strong>@scrollPosition</strong>
</div>
@code {
private ElementReference divElement;
private double? scrollPosition;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender && scrollPosition is null)
{
scrollPosition = await JS.InvokeAsync<double>(
"scrollElementIntoView", divElement);
StateHasChanged();
}
}
}
En el ejemplo anterior se contamina el cliente con una función global. Para obtener un mejor enfoque en las aplicaciones de producción, consulta Aislamiento de JavaScript en módulos de JavaScript.
Trabajo en segundo plano cancelable
Los componentes suelen llevar a cabo un trabajo en segundo plano de ejecución prolongada, como realizar llamadas de red (HttpClient) e interactuar con bases de datos. Es deseable detener el trabajo en segundo plano para conservar los recursos del sistema en diversas situaciones. Por ejemplo, las operaciones asincrónicas en segundo plano no se detienen automáticamente cuando un usuario navega fuera de un componente.
Entre otras de las razones por las que los elementos de trabajo en segundo plano pueden requerir cancelación se incluyen las siguientes:
- Una tarea en segundo plano en ejecución se inició con datos de entrada o parámetros de procesamiento incorrectos.
- El conjunto actual de elementos de trabajo en segundo plano en ejecución debe reemplazarse por un nuevo conjunto de elementos de trabajo.
- La prioridad de las tareas que se están ejecutando debe cambiarse.
- La aplicación debe ser cerrada para la reimplementación del servidor.
- Los recursos del servidor se vuelven limitados, lo que requiere la reprogramación de los elementos de trabajo en segundo plano.
Para implementar un patrón de trabajo en segundo plano cancelable en un componente:
- Usa CancellationTokenSource y CancellationToken.
- Tras la eliminación del componente, y en cualquier momento en el que interese realizar la cancelación mediante la cancelación manual del token, llame a
CancellationTokenSource.Cancel
para indicar que se debe cancelar el trabajo en segundo plano. - Cuando se devuelva la llamada asincrónica, llame a ThrowIfCancellationRequested en el token.
En el ejemplo siguiente:
-
await Task.Delay(10000, cts.Token);
representa el trabajo asincrónico en segundo plano de ejecución prolongada. -
BackgroundResourceMethod
representa un método en segundo plano de ejecución prolongada que no debe iniciarse si se eliminaResource
antes de llamar al método.
BackgroundWork.razor
:
@page "/background-work"
@implements IDisposable
@inject ILogger<BackgroundWork> Logger
<PageTitle>Background Work</PageTitle>
<h1>Background Work Example</h1>
<p>
<button @onclick="LongRunningWork">Trigger long running work</button>
<button @onclick="Dispose">Trigger Disposal</button>
</p>
<p>Study logged messages in the console.</p>
<p>
If you trigger disposal within 10 seconds of page load, the
<code>BackgroundResourceMethod</code> isn't executed.
</p>
<p>
If disposal occurs after <code>BackgroundResourceMethod</code> is called but
before action is taken on the resource, an <code>ObjectDisposedException</code>
is thrown by <code>BackgroundResourceMethod</code>, and the resource isn't
processed.
</p>
@code {
private Resource resource = new();
private CancellationTokenSource cts = new();
private IList<string> messages = [];
protected async Task LongRunningWork()
{
Logger.LogInformation("Long running work started");
await Task.Delay(10000, cts.Token);
cts.Token.ThrowIfCancellationRequested();
resource.BackgroundResourceMethod(Logger);
}
public void Dispose()
{
Logger.LogInformation("Executing Dispose");
if (!cts.IsCancellationRequested)
{
cts.Cancel();
}
cts?.Dispose();
resource?.Dispose();
}
private class Resource : IDisposable
{
private bool disposed;
public void BackgroundResourceMethod(ILogger<BackgroundWork> logger)
{
logger.LogInformation("BackgroundResourceMethod: Start method");
if (disposed)
{
logger.LogInformation("BackgroundResourceMethod: Disposed");
throw new ObjectDisposedException(nameof(Resource));
}
// Take action on the Resource
logger.LogInformation("BackgroundResourceMethod: Action on Resource");
}
public void Dispose() => disposed = true;
}
}
@page "/background-work"
@implements IDisposable
@inject ILogger<BackgroundWork> Logger
<PageTitle>Background Work</PageTitle>
<h1>Background Work Example</h1>
<p>
<button @onclick="LongRunningWork">Trigger long running work</button>
<button @onclick="Dispose">Trigger Disposal</button>
</p>
<p>Study logged messages in the console.</p>
<p>
If you trigger disposal within 10 seconds of page load, the
<code>BackgroundResourceMethod</code> isn't executed.
</p>
<p>
If disposal occurs after <code>BackgroundResourceMethod</code> is called but
before action is taken on the resource, an <code>ObjectDisposedException</code>
is thrown by <code>BackgroundResourceMethod</code>, and the resource isn't
processed.
</p>
@code {
private Resource resource = new();
private CancellationTokenSource cts = new();
private IList<string> messages = [];
protected async Task LongRunningWork()
{
Logger.LogInformation("Long running work started");
await Task.Delay(10000, cts.Token);
cts.Token.ThrowIfCancellationRequested();
resource.BackgroundResourceMethod(Logger);
}
public void Dispose()
{
Logger.LogInformation("Executing Dispose");
if (!cts.IsCancellationRequested)
{
cts.Cancel();
}
cts?.Dispose();
resource?.Dispose();
}
private class Resource : IDisposable
{
private bool disposed;
public void BackgroundResourceMethod(ILogger<BackgroundWork> logger)
{
logger.LogInformation("BackgroundResourceMethod: Start method");
if (disposed)
{
logger.LogInformation("BackgroundResourceMethod: Disposed");
throw new ObjectDisposedException(nameof(Resource));
}
// Take action on the Resource
logger.LogInformation("BackgroundResourceMethod: Action on Resource");
}
public void Dispose() => disposed = true;
}
}
@page "/background-work"
@using System.Threading
@using Microsoft.Extensions.Logging
@implements IDisposable
@inject ILogger<BackgroundWork> Logger
<button @onclick="LongRunningWork">Trigger long running work</button>
<button @onclick="Dispose">Trigger Disposal</button>
@code {
private Resource resource = new();
private CancellationTokenSource cts = new();
protected async Task LongRunningWork()
{
Logger.LogInformation("Long running work started");
await Task.Delay(5000, cts.Token);
cts.Token.ThrowIfCancellationRequested();
resource.BackgroundResourceMethod(Logger);
}
public void Dispose()
{
Logger.LogInformation("Executing Dispose");
cts.Cancel();
cts.Dispose();
resource?.Dispose();
}
private class Resource : IDisposable
{
private bool disposed;
public void BackgroundResourceMethod(ILogger<BackgroundWork> logger)
{
logger.LogInformation("BackgroundResourceMethod: Start method");
if (disposed)
{
logger.LogInformation("BackgroundResourceMethod: Disposed");
throw new ObjectDisposedException(nameof(Resource));
}
// Take action on the Resource
logger.LogInformation("BackgroundResourceMethod: Action on Resource");
}
public void Dispose()
{
disposed = true;
}
}
}
@page "/background-work"
@using System.Threading
@using Microsoft.Extensions.Logging
@implements IDisposable
@inject ILogger<BackgroundWork> Logger
<button @onclick="LongRunningWork">Trigger long running work</button>
<button @onclick="Dispose">Trigger Disposal</button>
@code {
private Resource resource = new();
private CancellationTokenSource cts = new();
protected async Task LongRunningWork()
{
Logger.LogInformation("Long running work started");
await Task.Delay(5000, cts.Token);
cts.Token.ThrowIfCancellationRequested();
resource.BackgroundResourceMethod(Logger);
}
public void Dispose()
{
Logger.LogInformation("Executing Dispose");
cts.Cancel();
cts.Dispose();
resource?.Dispose();
}
private class Resource : IDisposable
{
private bool disposed;
public void BackgroundResourceMethod(ILogger<BackgroundWork> logger)
{
logger.LogInformation("BackgroundResourceMethod: Start method");
if (disposed)
{
logger.LogInformation("BackgroundResourceMethod: Disposed");
throw new ObjectDisposedException(nameof(Resource));
}
// Take action on the Resource
logger.LogInformation("BackgroundResourceMethod: Action on Resource");
}
public void Dispose()
{
disposed = true;
}
}
}
@page "/background-work"
@using System.Threading
@using Microsoft.Extensions.Logging
@implements IDisposable
@inject ILogger<BackgroundWork> Logger
<button @onclick="LongRunningWork">Trigger long running work</button>
<button @onclick="Dispose">Trigger Disposal</button>
@code {
private Resource resource = new();
private CancellationTokenSource cts = new();
protected async Task LongRunningWork()
{
Logger.LogInformation("Long running work started");
await Task.Delay(5000, cts.Token);
cts.Token.ThrowIfCancellationRequested();
resource.BackgroundResourceMethod(Logger);
}
public void Dispose()
{
Logger.LogInformation("Executing Dispose");
cts.Cancel();
cts.Dispose();
resource?.Dispose();
}
private class Resource : IDisposable
{
private bool disposed;
public void BackgroundResourceMethod(ILogger<BackgroundWork> logger)
{
logger.LogInformation("BackgroundResourceMethod: Start method");
if (disposed)
{
logger.LogInformation("BackgroundResourceMethod: Disposed");
throw new ObjectDisposedException(nameof(Resource));
}
// Take action on the Resource
logger.LogInformation("BackgroundResourceMethod: Action on Resource");
}
public void Dispose()
{
disposed = true;
}
}
}
@page "/background-work"
@using System.Threading
@using Microsoft.Extensions.Logging
@implements IDisposable
@inject ILogger<BackgroundWork> Logger
<button @onclick="LongRunningWork">Trigger long running work</button>
<button @onclick="Dispose">Trigger Disposal</button>
@code {
private Resource resource = new Resource();
private CancellationTokenSource cts = new CancellationTokenSource();
protected async Task LongRunningWork()
{
Logger.LogInformation("Long running work started");
await Task.Delay(5000, cts.Token);
cts.Token.ThrowIfCancellationRequested();
resource.BackgroundResourceMethod(Logger);
}
public void Dispose()
{
Logger.LogInformation("Executing Dispose");
cts.Cancel();
cts.Dispose();
resource?.Dispose();
}
private class Resource : IDisposable
{
private bool disposed;
public void BackgroundResourceMethod(ILogger<BackgroundWork> logger)
{
logger.LogInformation("BackgroundResourceMethod: Start method");
if (disposed)
{
logger.LogInformation("BackgroundResourceMethod: Disposed");
throw new ObjectDisposedException(nameof(Resource));
}
// Take action on the Resource
logger.LogInformation("BackgroundResourceMethod: Action on Resource");
}
public void Dispose()
{
disposed = true;
}
}
}
Para mostrar un indicador de carga mientras se está llevando a cabo el trabajo en segundo plano, use el siguiente enfoque.
Cree un componente de indicador de carga con un parámetro Loading
que pueda mostrar contenido secundario en un RenderFragment. Para el Loading
parámetro :
- Cuando
true
, muestra un indicador de carga. - Cuando
false
, represente el contenido del componente (ChildContent
). Para obtener más información, consulte Fragmentos de representación de contenido secundario.
ContentLoading.razor
:
@if (Loading)
{
<progress id="loadingIndicator" aria-label="Content loading…"></progress>
}
else
{
@ChildContent
}
@code {
[Parameter]
public RenderFragment? ChildContent { get; set; }
[Parameter]
public bool Loading { get; set; }
}
Para cargar estilos CSS para el indicador, agregue los estilos al contenido <head>
con el componente HeadContent. Para obtener más información, consulta Control del contenido principal en aplicaciones Blazor de ASP.NET Core.
@if (Loading)
{
<!-- OPTIONAL ...
<HeadContent>
<style>
...
</style>
</HeadContent>
-->
<progress id="loadingIndicator" aria-label="Content loading…"></progress>
}
else
{
@ChildContent
}
...
Cuando el componente realiza el trabajo de inicialización, encapsula el marcado del componente Razor con el componente ContentLoading
y pasa un valor a un parámetro en un campo de C# para Loading
.
<ContentLoading Loading="@loading">
...
</ContentLoading>
@code {
private bool loading = true;
...
protected override async Task OnInitializedAsync()
{
await LongRunningWork().ContinueWith(_ => loading = false);
}
...
}
Eventos de reconexión de Blazor Server
Los eventos de ciclo de vida de los componentes descritos en este artículo funcionan por separado de controladores de eventos de reconexión del lado servidor. Cuando se pierde la conexión SignalR al cliente, solo se interrumpen las actualizaciones de la interfaz de usuario. Dichas actualizaciones se reanudan cuando se restablece la conexión. Para obtener más información sobre los eventos de controlador de circuito y su configuración, consulta Guía de BlazorSignalR para ASP.NET Core.
Recursos adicionales
Control de las excepciones detectadas fuera del ciclo de vida de un componente Razor