Жизненный цикл компонента Razor ASP.NET Core

Примечание.

Это не последняя версия этой статьи. В текущем выпуске см . версию .NET 8 этой статьи.

Предупреждение

Эта версия ASP.NET Core больше не поддерживается. Дополнительные сведения см. в статье о политике поддержки .NET и .NET Core. В текущем выпуске см . версию .NET 8 этой статьи.

Внимание

Эта информация относится к предварительному выпуску продукта, который может быть существенно изменен до его коммерческого выпуска. Майкрософт не предоставляет никаких гарантий, явных или подразумеваемых, относительно приведенных здесь сведений.

В текущем выпуске см . версию .NET 8 этой статьи.

В этой статье приведены сведения о жизненном цикле компонента Razor ASP.NET Core, а также о том, как использовать события жизненного цикла.

События жизненного цикла

Компонент Razor обрабатывает события жизненного цикла компонента Razor в наборе синхронных и асинхронных методов жизненного цикла. Методы жизненного цикла можно переопределить для выполнения дополнительных операций с компонентами во время инициализации и отрисовки компонента.

Эта статья упрощает обработку событий жизненного цикла компонентов, чтобы уточнить сложную логику платформы и не охватывает все изменения, внесенные в течение многих лет. Возможно, вам потребуется получить доступ к источнику ссылки для интеграции пользовательской ComponentBase обработки событий с Blazorобработкой событий жизненного цикла. Комментарии кода в справочном источнике включают дополнительные замечания по обработке событий жизненного цикла, которые не отображаются в этой статье или в документации по API.

Примечание.

По ссылкам в документации на справочные материалы по .NET обычно загружается ветвь репозитория по умолчанию, которая представляет текущую разработку для следующего выпуска .NET. Чтобы выбрать тег для определенного выпуска, используйте раскрывающийся список Switch branches or tags (Переключение ветвей или тегов). Дополнительные сведения см. в статье Выбор тега версии исходного кода ASP.NET Core (dotnet/AspNetCore.Docs #26205).

На следующих упрощенных схемах показана Razor обработка событий жизненного цикла компонентов. Методы C#, связанные с событиями жизненного цикла, с примерами приводятся в следующих разделах этой статьи.

События жизненного цикла компонента

  1. Если компонент выполняет отрисовку в первый раз по запросу:
    • Создайте экземпляр компонента.
    • Выполните внедрение свойств.
    • Вызовите процедуру OnInitialized{Async}. Если метод Task возвращает неполное значение, ожидается Task, а затем компонент отрисовывается. Синхронный метод вызывается до асинхронного метода.
  2. Вызовите процедуру OnParametersSet{Async}. Если метод Task возвращает неполное значение, ожидается Task, а затем компонент отрисовывается. Синхронный метод вызывается до асинхронного метода.
  3. Рендеринг для всей синхронной работы и выполнение операций класса Task.

Примечание.

Асинхронные действия, выполняемые в событиях жизненного цикла, могут не выполняться до отрисовки компонента. Дополнительные сведения см. в разделе Обработка незавершенных асинхронных действий при отрисовке далее в этой статье.

Родительский компонент отображается перед дочерними компонентами, так как отрисовка определяет, какие дочерние элементы присутствуют. Если используется синхронная инициализация родительского компонента, то родительская инициализация гарантированно завершится первым. Если используется асинхронная инициализация родительского компонента, порядок завершения инициализации родительского и дочернего компонента не может быть определен, так как он зависит от выполняемого кода инициализации.

События жизненного Razor цикла компонента в Blazor

Обработка событий DOM:

  1. Выполняется обработчик событий.
  2. Если метод Task возвращает неполное значение, ожидается Task, а затем компонент отрисовывается.
  3. Рендеринг для всей синхронной работы и выполнение операций класса Task.

Обработка событий DOM

Жизненный цикл Render

  1. Избегайте дальнейших операций отрисовки компонента при выполнении обоих следующих условий:
    • Это не первый отрисовка.
    • ShouldRender возвращает false.
  2. Выполните сборку отличий дерева отрисовки и отрисуйте компонент.
  3. Подождите, пока модель DOM обновится.
  4. Вызовите процедуру OnAfterRender{Async}. Синхронный метод вызывается до асинхронного метода.

Жизненный цикл процесса отрисовки

Вызовы разработчика к StateHasChanged результату rerender. Дополнительные сведения см. в статье Отрисовка компонентов Razor ASP.NET Core.

После указания параметров (SetParametersAsync)

SetParametersAsync задает параметры, предоставляемые родительским элементом компонента в дереве отрисовки или из параметров маршрута.

Параметр ParameterView метода содержит набор значений параметров для компонента при каждом вызове SetParametersAsync. Переопределяя метод SetParametersAsync, код разработчика может напрямую взаимодействовать с параметрами ParameterView.

Реализация SetParametersAsync по умолчанию задает значение каждого свойства с атрибутом [Parameter] либо атрибутом [CascadingParameter], имеющим соответствующее значение в ParameterView. Параметры, у которых нет соответствующего значения в ParameterView, остаются неизменными.

Как правило, код должен вызывать метод базового класса (await base.SetParametersAsync(parameters);) при переопределении SetParametersAsync. В расширенных сценариях код разработчика может интерпретировать значения входящих параметров каким-либо образом, не вызывая метод базового класса. Например, назначать входящие параметры свойствам класса не обязательно. Однако при структурировании кода необходимо ссылаться на ComponentBase источник ссылок, не вызывая метод базового класса, так как он вызывает другие методы жизненного цикла и активирует отрисовку сложным образом.

Примечание.

По ссылкам в документации на справочные материалы по .NET обычно загружается ветвь репозитория по умолчанию, которая представляет текущую разработку для следующего выпуска .NET. Чтобы выбрать тег для определенного выпуска, используйте раскрывающийся список Switch branches or tags (Переключение ветвей или тегов). Дополнительные сведения см. в статье Выбор тега версии исходного кода ASP.NET Core (dotnet/AspNetCore.Docs #26205).

Если вы хотите полагаться на логику ComponentBase.SetParametersAsync инициализации и отрисовки, но не обработать входящие параметры, у вас есть возможность передать пустой ParameterView метод базового класса:

await base.SetParametersAsync(ParameterView.Empty);

Если обработчики событий предоставляются в коде разработчика, отсоедините их при удалении. Дополнительные сведения см. в разделе Удаление компонентов с помощью раздела IDisposable и IAsyncDisposable.

В следующем примере ParameterView.TryGetValue присваивает value значение параметраParam, если анализ параметра маршрута для Param выполнен успешно. Если value не равно null, значение отображается компонентом.

Хотя сопоставление параметров маршрута не учитывает регистр, в шаблоне маршрута совпадают только имена параметров с TryGetValue учетом регистра. В следующем примере, чтобы получить значение с помощью метода TryGetValue, вам потребуется использовать в шаблоне маршрута /{Param?}, а не /{param?}. Если в этом сценарии используется /{param?}, функция TryGetValue возвращает значение false, а message не задается в качестве строкового параметра 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);
    }
}

Инициализация компонента (OnInitialized{Async})

OnInitialized и OnInitializedAsync используется исключительно для инициализации компонента в течение всего времени существования экземпляра компонента. Значения параметров и изменения значений параметров не должны влиять на инициализацию, выполняемую в этих методах. Например, загрузка статических параметров в раскрывающийся список, который не изменяется в течение всего времени существования компонента и который не зависит от значений параметров, выполняется в одном из этих методов жизненного цикла. Если значения параметров или изменения значений параметров влияют на состояние компонента, используйте OnParametersSet{Async} вместо этого.

Эти методы вызываются при инициализации компонента после получения его начальных параметров в SetParametersAsync. Синхронный метод вызывается до асинхронного метода.

Если используется синхронная инициализация родительского компонента, родительская инициализация гарантированно завершится до инициализации дочернего компонента. Если используется асинхронная инициализация родительского компонента, порядок завершения инициализации родительского и дочернего компонента не может быть определен, так как он зависит от выполняемого кода инициализации.

Для синхронной операции переопределите 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}";
    }
}

Чтобы выполнить асинхронную операцию, переопределите OnInitializedAsync и используйте оператор await:

protected override async Task OnInitializedAsync()
{
    await ...
}

Если пользовательский базовый класс используется с пользовательской логикой инициализации, вызовите OnInitializedAsync базовый класс:

protected override async Task OnInitializedAsync()
{
    await ...

    await base.OnInitializedAsync();
}

Не обязательно ComponentBase.OnInitializedAsync вызывать, если настраиваемый базовый класс не используется с пользовательской логикой. Дополнительные сведения см. в разделе "Методы жизненного цикла базового класса".

Приложения Blazor, которые предварительно отрисовывают свое содержимое на сервере, вызывают OnInitializedAsync дважды:

  • Один раз, когда компонент изначально отрисовывается статически как часть страницы.
  • И второй раз, когда браузер отрисовывает компонент.

Чтобы узнать, как предотвратить двойное выполнение кода разработчика в OnInitializedAsync при выполнении предварительной отрисовки, см. раздел Повторное подключение с отслеживанием состояния после предварительной отрисовки. Содержимое в разделе посвящено повторному подключению с Blazor Web Appотслеживанием SignalRсостояния. Сведения о сохранении состояния во время выполнения кода инициализации во время предварительной подготовки см. в разделе prerender ASP.NET Core Razor components.

Чтобы узнать, как предотвратить двойное выполнение кода разработчика в OnInitializedAsync при выполнении предварительной отрисовки, см. раздел Повторное подключение с отслеживанием состояния после предварительной отрисовки. Хотя содержимое в разделе посвящено Blazor Server повторному подключению с отслеживаниемSignalR состояния, сценарий предварительной подготовки в размещенных Blazor WebAssembly решениях (WebAssemblyPrerendered) включает аналогичные условия и подходы, чтобы предотвратить выполнение кода разработчика дважды. Сведения о сохранении состояния во время выполнения кода инициализации на этапе предварительной отрисовка см. в статье Компоненты Razor для предварительной визуализации и интеграции ASP.NET Core.

Когда приложение Blazor выполняет предварительную отрисовку, некоторые действия, такие как вызов в JavaScript (взаимодействие с JS), невозможны. При предварительной отрисовке компоненты могут отрисовываться иначе. Дополнительные сведения см. в разделе Предварительная отрисовка с помощью взаимодействия с JavaScript.

Если обработчики событий предоставляются в коде разработчика, отсоедините их при удалении. Дополнительные сведения см. в разделе Удаление компонента с использованием IDisposableIAsyncDisposable.

Используйте потоковую отрисовку со статической отрисовкой на стороне сервера (статический SSR) или предварительной подготовкой для улучшения пользовательского интерфейса компонентов, выполняющих длительные асинхронные задачи для OnInitializedAsync полной отрисовки. Дополнительные сведения см. в статье Отрисовка компонентов Razor ASP.NET Core.

После указания параметров (OnParametersSet{Async})

OnParametersSet или OnParametersSetAsync вызываются:

  • После инициализации компонента в OnInitialized или OnInitializedAsync.

  • Когда родительский компонент повторно отрисовывается и предоставляет:

    • Известные или примитивные неизменяемые типы, в которых изменился по крайней мере один параметр.
    • Параметры сложных типов. Платформа не может определить, изменились ли значения параметров сложного типа на внутреннем уровне, поэтому при наличии одного или нескольких параметров сложных типов она рассматривает набор параметров как измененный.

    Дополнительные сведения о соглашениях об отрисовке см. в статье Отрисовка компонентов Razor ASP.NET Core.

Синхронный метод вызывается до асинхронного метода.

Методы можно вызывать, даже если значения параметров не изменились. Это поведение подчеркивает необходимость реализации дополнительной логики разработчиками в методах для проверки того, изменились ли значения параметров перед повторной инициализацией данных или состояний, зависящих от этих параметров.

В следующем примере компонента перейдите на страницу компонента по URL-адресу:

  • В котором указана дата начала, полученная StartDate: /on-parameters-set/2021-03-19
  • В котором не указана дата начала, и где StartDate присваивается значение текущего местного времени: /on-parameters-set

Примечание.

В маршруте компонента невозможно одновременно ограничить параметр DateTime и применить ограничение маршрута datetime, а также сделать параметр необязательным. Поэтому следующий компонент OnParamsSet для управления маршрутизацией в URL-адресе с заданным сегментом даты и без него использует две директивы @page.

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

Асинхронная работа при применении параметров и значений свойств должна происходить во время события жизненного цикла OnParametersSetAsync:

protected override async Task OnParametersSetAsync()
{
    await ...
}

Если пользовательский базовый класс используется с пользовательской логикой инициализации, вызовите OnParametersSetAsync базовый класс:

protected override async Task OnParametersSetAsync()
{
    await ...

    await base.OnParametersSetAsync();
}

Не обязательно ComponentBase.OnParametersSetAsync вызывать, если настраиваемый базовый класс не используется с пользовательской логикой. Дополнительные сведения см. в разделе "Методы жизненного цикла базового класса".

Если обработчики событий предоставляются в коде разработчика, отсоедините их при удалении. Дополнительные сведения см. в разделе Удаление компонента с использованием IDisposableIAsyncDisposable.

Дополнительные сведения о параметрах маршрута и ограничениях см. в статье Маршрутизация ASP.NET Core Blazor и навигация.

Пример реализации SetParametersAsync вручную для повышения производительности в некоторых сценариях см. в ASP.NET рекомендациях по производительности CoreBlazor.

После отрисовки компонента (OnAfterRender{Async})

OnAfterRender и OnAfterRenderAsync вызываются после интерактивной отрисовки компонента, а пользовательский интерфейс завершил обновление (например, после добавления элементов в браузер DOM). В этот момент указываются ссылки на элементы и компоненты. Используйте этот этап, чтобы выполнить дополнительные шаги инициализации отрисованного содержимого, такого как вызовы взаимодействия с JS, которые взаимодействуют с отрисованными элементами модели DOM. Синхронный метод вызывается до асинхронного метода.

Эти методы не вызываются во время предварительной отрисовки или отрисовки на стороне сервера (статический SSR), так как эти процессы не подключены к DOM в динамическом браузере и уже завершены до обновления DOM.

Для OnAfterRenderAsyncэтого компонент не выполняет автоматическую отрисовку после завершения любого возвращаемого Task , чтобы избежать бесконечного цикла отрисовки.

OnAfterRender и OnAfterRenderAsync вызываются после завершения отрисовки компонента. В этот момент указываются ссылки на элементы и компоненты. Используйте этот этап, чтобы выполнить дополнительные шаги инициализации отрисованного содержимого, такого как вызовы взаимодействия с JS, которые взаимодействуют с отрисованными элементами модели DOM. Синхронный метод вызывается до асинхронного метода.

Эти методы не вызываются во время предварительной отрисовки, так как предварительная отрисовка не присоединена к DOM в динамическом браузере и уже завершена до обновления DOM.

Для OnAfterRenderAsyncэтого компонент не выполняет автоматическую отрисовку после завершения любого возвращаемого Task , чтобы избежать бесконечного цикла отрисовки.

Параметр firstRender для OnAfterRender и OnAfterRenderAsync:

  • Устанавливается в значение true при первой отрисовке экземпляра компонента.
  • Может использоваться, чтобы гарантировать однократное выполнение инициализации.

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

В AfterRender.razor примере создаются следующие выходные данные для консоли при загрузке страницы и выбрана кнопка:

OnAfterRender: firstRender = True
HandleClick called
OnAfterRender: firstRender = False

Асинхронная работа сразу после отрисовки должна произойти во время события жизненного цикла OnAfterRenderAsync:

protected override async Task OnAfterRenderAsync(bool firstRender)
{
    ...
}

Если пользовательский базовый класс используется с пользовательской логикой инициализации, вызовите OnAfterRenderAsync базовый класс:

protected override async Task OnAfterRenderAsync(bool firstRender)
{
    ...

    await base.OnAfterRenderAsync(firstRender);
}

Не обязательно ComponentBase.OnAfterRenderAsync вызывать, если настраиваемый базовый класс не используется с пользовательской логикой. Дополнительные сведения см. в разделе "Методы жизненного цикла базового класса".

Даже если вы возвращаете Task из OnAfterRenderAsync, платформа не планирует дальнейший цикл отрисовки компонента после завершения задачи. Это позволяет избежать бесконечного цикла отрисовки. Это поведение отличается от других методов жизненного цикла, которые планируют дальнейший цикл отрисовки после завершения возвращаемой операции Task.

OnAfterRender и OnAfterRenderAsync не вызываются при предварительной отрисовке на сервере. Методы вызываются, когда компонент отрисовывается в интерактивном режиме после предварительной отрисовки. При предварительной отрисовке приложения происходит следующее.

  1. Компонент выполняется на сервере для создания статической HTML-разметки в HTTP-ответе. На этом этапе OnAfterRender и OnAfterRenderAsync не вызываются.
  2. Blazor При запуске скрипта (blazor.{server|webassembly|web}.js) в браузере компонент перезапускается в интерактивном режиме отрисовки. После перезапуска компонента вызываются методы OnAfterRender и OnAfterRenderAsync, так как приложение больше не находится на этапе предварительной отрисовки.

Если обработчики событий предоставляются в коде разработчика, отсоедините их при удалении. Дополнительные сведения см. в разделе Удаление компонента с использованием IDisposableIAsyncDisposable.

Методы жизненного цикла базового класса

При переопределении Blazorметодов жизненного цикла не требуется вызывать методы жизненного цикла базового класса.ComponentBase Однако компонент должен вызывать переопределенный метод жизненного цикла базового класса в следующих ситуациях:

  • При переопределении ComponentBase.SetParametersAsyncawait base.SetParametersAsync(parameters); обычно вызывается, так как метод базового класса вызывает другие методы жизненного цикла и запускает отрисовку сложным образом. Дополнительные сведения см. в разделе "Настройка параметров (SetParametersAsync)
  • Если метод базового класса содержит логику, которую необходимо выполнить. Потребители библиотек обычно вызывают методы жизненного цикла базового класса при наследовании базового класса, так как базовые классы библиотек часто имеют пользовательскую логику жизненного цикла для выполнения. Если приложение использует базовый класс из библиотеки, ознакомьтесь с документацией по библиотеке.

В следующем примере base.OnInitialized(); вызывается для обеспечения выполнения метода базового класса OnInitialized . Без вызова BlazorRocksBase2.OnInitialized не выполняется.

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

Изменения состояния (StateHasChanged)

StateHasChanged уведомляет компонент о том, что его состояние изменилось. При необходимости вызов StateHasChanged вызывает rerender, который возникает, когда основной поток приложения свободен.

StateHasChanged вызывается автоматически для методов EventCallback. Дополнительные сведения об обратных вызовах событий см. в разделе Обработка событий Blazor в ASP.NET Core.

Дополнительные сведения об отрисовке компонентов и о том, когда лучше вызывать StateHasChanged и когда для этого следует использовать ComponentBase.InvokeAsync, см. в статье Отрисовка компонента Razor ASP.NET Core.

Обработка незавершенных асинхронных действий при отрисовке

Асинхронные действия, выполняемые в событиях жизненного цикла, могут не завершиться до отрисовки компонента. Во время выполнения метода жизненного цикла объекты могут быть null или заполнены данными не полностью. Предоставьте логику отрисовки для подтверждения инициализации объектов. Отрисуйте элементы пользовательского интерфейса заполнителя (например, сообщение загрузки), пока объекты имеют значение null.

В следующем компоненте OnInitializedAsync переопределяется для асинхронного предоставления данных о рейтинге фильмов (movies). Если movies имеет значение null, пользователю выводится сообщение о загрузке. Когда элемент Task, возвращенный OnInitializedAsync, завершается, компонент отрисовывается повторно с обновленным состоянием.

<h1>Sci-Fi Movie Ratings</h1>

@if (movies == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <ul>
        @foreach (var movie in movies)
        {
            <li>@movie.Title &mdash; @movie.Rating</li>
        }
    </ul>
}

@code {
    private Movies[]? movies;

    protected override async Task OnInitializedAsync()
    {
        movies = await GetMovieRatings(DateTime.Now);
    }
}

Обработка ошибок

Сведения об обработке ошибок во время выполнения метода жизненного цикла см. в статье Обработка ошибок в приложениях ASP.NET Core Blazor.

Повторное подключение с отслеживанием состояния после предварительной отрисовки

При предварительной подготовке на сервере компонент изначально отображается статически как часть страницы. После того как браузер установит соединение SignalR с сервером, компонент будет отрисован снова и станет интерактивным. Если метод жизненного цикла OnInitialized{Async} для инициализации компонента присутствует, он выполняется дважды:

  • Когда компонент предварительно отрисовывается статически.
  • После установления соединения с сервером.

Это может привести к заметному изменению данных, отображаемых в пользовательском интерфейсе, когда компонент отрисовывается окончательно. Чтобы избежать этого поведения, передайте идентификатор в кэш состояния во время предварительной отрисовки и получите состояние после предварительной отрисовки.

В следующем коде показано WeatherForecastService , как избежать изменения отображения данных из-за предварительной отрисовки. Ожидается Delay (await Task.Delay(...)) имитирует короткую задержку перед возвратом данных из GetForecastAsync метода.

Добавьте IMemoryCache службы в AddMemoryCache коллекцию служб в файле приложения Program :

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

Дополнительные сведения о RenderMode см. в статье руководстве по ASP.NET Core BlazorSignalR.

В этом разделе основное внимание уделяется Blazor Web Appповторному подключению с отслеживанием SignalRсостояния. Сведения о сохранении состояния во время выполнения кода инициализации во время предварительной подготовки см. в разделе prerender ASP.NET Core Razor components.

Хотя содержимое этого раздела посвящено Blazor Server повторному подключению и повторному подключению с отслеживаниемSignalR состояния, сценарий предварительной подготовки в размещенных Blazor WebAssembly решениях (WebAssemblyPrerendered) включает аналогичные условия и подходы, чтобы предотвратить выполнение кода разработчика дважды. Сведения о сохранении состояния во время выполнения кода инициализации на этапе предварительной отрисовка см. в статье Компоненты Razor для предварительной визуализации и интеграции ASP.NET Core.

Предварительная отрисовка с помощью взаимодействия с JavaScript

Этот раздел относится к серверным приложениям, которые предопределили Razor компоненты. Предварительная отрисовка рассматривается в компонентах Prerender ASP.NET CoreRazor.

Примечание.

Внутренняя навигация для интерактивной маршрутизации в Blazor Web Apps не включает запрос нового содержимого страницы с сервера. Поэтому предварительное отображение не выполняется для внутренних запросов страниц. Если приложение принимает интерактивную маршрутизацию, выполните полную перезагрузку страницы для примеров компонентов, демонстрирующих поведение предварительной подготовки. Дополнительные сведения см. в разделе "Предварительная ASP.NET Основные Razor компоненты".

Этот раздел относится к серверным приложениям и размещенным приложениям, которые предустановили Blazor WebAssembly Razor компоненты. Предварительная отрисовка рассматривается в статье Предварительная отрисовка и интеграция компонентов Razor в ASP.NET Core.

Во время предварительной подготовки вызов в JavaScript (JS) невозможен. В следующем примере показано, как использовать JS взаимодействие в рамках логики инициализации компонента таким образом, который совместим с предварительной подготовкой.

Следующая функция scrollElementIntoView :

  • Прокручивается до переданного элемента с scrollIntoViewпомощью .
  • Возвращает значение свойства элемента top из getBoundingClientRect метода.
window.scrollElementIntoView = (element) => {
  element.scrollIntoView();
  return element.getBoundingClientRect().top;
}

При IJSRuntime.InvokeAsync вызове JS функции в коде компонента используется OnAfterRenderAsync только в более ранних методах жизненного цикла, ElementReference так как после отрисовки компонента отсутствует элемент HTML DOM.

StateHasChanged(ссылочный источник) вызывается для перечисления rerendering компонента с новым состоянием, полученным из JS вызова взаимодействия (дополнительные сведения см. в разделе ASP.NET Отрисовка основных Razor компонентов). Бесконечный цикл не создается, так как StateHasChanged вызывается только в том nullслучаеscrollPosition.

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

В предыдущем примере клиент загрязняет глобальную функцию. Более эффективный подход для приложений в рабочей среде приведен в разделе Изоляция JavaScript в модулях JavaScript.

Удаление компонента с использованием IDisposable и IAsyncDisposable

Если компонент реализует IDisposable или IAsyncDisposable, платформа вызывает удаление ресурсов при удалении компонента из пользовательского интерфейса. Не полагаться на точное время выполнения этих методов. Например, IAsyncDisposable можно активировать до или после вызова или завершения асинхронного Task ожидания.OnInitalizedAsync Кроме того, код удаления объектов не должен предполагать, что объекты, созданные во время инициализации или других методов жизненного цикла, существуют.

Компоненты не должны реализовывать IDisposable и IAsyncDisposable одновременно. Если реализуются оба, платформа выполняет только асинхронную перегрузку.

Код разработчика должен гарантировать, что реализации IAsyncDisposable не займут много времени.

Удаление ссылок на объекты взаимодействия JavaScript

Примеры в статьях взаимодействия JavaScript (JS) демонстрируют типичные шаблоны удаления объектов:

JS Ссылки на объекты взаимодействия реализуются в виде карты с ключом идентификатора на стороне JS вызова взаимодействия, создающего ссылку. Если удаление объектов инициируется из .NET или JS на стороне, удаляет запись из карты, Blazor а объект может быть собран мусором до тех пор, пока нет другой строгой ссылки на объект.

Как минимум, всегда удалять объекты, созданные на стороне .NET, чтобы избежать утечки управляемой памяти .NET.

Задачи очистки DOM во время удаления компонентов

Дополнительные сведения см. в разделе Взаимодействие JavaScript приложения Blazor ASP.NET Core (взаимодействие JS).

Инструкции по отключению канала см. в JSDisconnectedException разделе ASP.NET взаимодействие JavaScript Core Blazor (JSвзаимодействие). Общие рекомендации по обработке ошибок взаимодействия JavaScript см. в разделе "Обработка ошибок взаимодействия JavaScript" в приложениях ASP.NET CoreBlazor.

Синхронный интерфейс IDisposable

Для задач синхронного удаления используйте IDisposable.Dispose.

Приведенный ниже компонент делает следующее.

  • IDisposable Реализуется с помощью директивы@implementsRazor.
  • objУдаляет , который является типом, реализующим IDisposable.
  • выполняет проверку значений NULL, так как объект obj создается в методе жизненного цикла (не показан).
@implements IDisposable

...

@code {
    ...

    public void Dispose()
    {
        obj?.Dispose();
    }
}

При необходимости один объект можно удалить, используя при вызове Dispose лямбда-выражение. Следующий пример можно найти в статье Отрисовка компонента приложения Razor в ASP.NET Core. Он демонстрирует использование лямбда-выражения для удаления объекта Timer.

TimerDisposal1.razor:

@page "/timer-disposal-1"
@using System.Timers
@implements IDisposable

<PageTitle>Timer Disposal 1</PageTitle>

<h1>Timer Disposal Example 1</h1>

<p>Current count: @currentCount</p>

@code {
    private int currentCount = 0;
    private Timer timer = new(1000);

    protected override void OnInitialized()
    {
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            currentCount++;
            StateHasChanged();
        });
    }

    public void Dispose() => timer.Dispose();
}

TimerDisposal1.razor:

@page "/timer-disposal-1"
@using System.Timers
@implements IDisposable

<PageTitle>Timer Disposal 1</PageTitle>

<h1>Timer Disposal Example 1</h1>

<p>Current count: @currentCount</p>

@code {
    private int currentCount = 0;
    private Timer timer = new(1000);

    protected override void OnInitialized()
    {
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            currentCount++;
            StateHasChanged();
        });
    }

    public void Dispose() => timer.Dispose();
}

CounterWithTimerDisposal1.razor:

@page "/counter-with-timer-disposal-1"
@using System.Timers
@implements IDisposable

<h1>Counter with <code>Timer</code> disposal</h1>

<p>Current count: @currentCount</p>

@code {
    private int currentCount = 0;
    private Timer timer = new(1000);

    protected override void OnInitialized()
    {
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            currentCount++;
            StateHasChanged();
        });
    }

    public void Dispose() => timer.Dispose();
}

CounterWithTimerDisposal1.razor:

@page "/counter-with-timer-disposal-1"
@using System.Timers
@implements IDisposable

<h1>Counter with <code>Timer</code> disposal</h1>

<p>Current count: @currentCount</p>

@code {
    private int currentCount = 0;
    private Timer timer = new(1000);

    protected override void OnInitialized()
    {
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            currentCount++;
            StateHasChanged();
        });
    }

    public void Dispose() => timer.Dispose();
}

CounterWithTimerDisposal1.razor:

@page "/counter-with-timer-disposal-1"
@using System.Timers
@implements IDisposable

<h1>Counter with <code>Timer</code> disposal</h1>

<p>Current count: @currentCount</p>

@code {
    private int currentCount = 0;
    private Timer timer = new(1000);

    protected override void OnInitialized()
    {
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            currentCount++;
            StateHasChanged();
        });
    }

    public void Dispose() => timer.Dispose();
}

CounterWithTimerDisposal1.razor:

@page "/counter-with-timer-disposal-1"
@using System.Timers
@implements IDisposable

<h1>Counter with <code>Timer</code> disposal</h1>

<p>Current count: @currentCount</p>

@code {
    private int currentCount = 0;
    private Timer timer = new Timer(1000);

    protected override void OnInitialized()
    {
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            currentCount++;
            StateHasChanged();
        });
    }

    public void Dispose() => timer.Dispose();
}

Примечание.

В предыдущем примере вызов StateHasChanged помещается в вызов ComponentBase.InvokeAsync, так как обратный вызов инициируется вне контекста синхронизации Blazor. Дополнительные сведения см. в статье Отрисовка компонентов Razor ASP.NET Core.

Если объект создан в методе жизненного цикла (например, OnInitialized{Async}), проверьте его на наличие значений null перед вызовом Dispose.

TimerDisposal2.razor:

@page "/timer-disposal-2"
@using System.Timers
@implements IDisposable

<PageTitle>Timer Disposal 2</PageTitle>

<h1>Timer Disposal Example 2</h1>

<p>Current count: @currentCount</p>

@code {
    private int currentCount = 0;
    private Timer? timer;

    protected override void OnInitialized()
    {
        timer = new Timer(1000);
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            currentCount++;
            StateHasChanged();
        });
    }

    public void Dispose() => timer?.Dispose();
}

TimerDisposal2.razor:

@page "/timer-disposal-2"
@using System.Timers
@implements IDisposable

<PageTitle>Timer Disposal 2</PageTitle>

<h1>Timer Disposal Example 2</h1>

<p>Current count: @currentCount</p>

@code {
    private int currentCount = 0;
    private Timer? timer;

    protected override void OnInitialized()
    {
        timer = new Timer(1000);
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            currentCount++;
            StateHasChanged();
        });
    }

    public void Dispose() => timer?.Dispose();
}

CounterWithTimerDisposal2.razor:

@page "/counter-with-timer-disposal-2"
@using System.Timers
@implements IDisposable

<h1>Counter with <code>Timer</code> disposal</h1>

<p>Current count: @currentCount</p>

@code {
    private int currentCount = 0;
    private Timer? timer;

    protected override void OnInitialized()
    {
        timer = new Timer(1000);
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            currentCount++;
            StateHasChanged();
        });
    }

    public void Dispose() => timer?.Dispose();
}

CounterWithTimerDisposal2.razor:

@page "/counter-with-timer-disposal-2"
@using System.Timers
@implements IDisposable

<h1>Counter with <code>Timer</code> disposal</h1>

<p>Current count: @currentCount</p>

@code {
    private int currentCount = 0;
    private Timer? timer;

    protected override void OnInitialized()
    {
        timer = new Timer(1000);
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            currentCount++;
            StateHasChanged();
        });
    }

    public void Dispose() => timer?.Dispose();
}

CounterWithTimerDisposal2.razor:

@page "/counter-with-timer-disposal-2"
@using System.Timers
@implements IDisposable

<h1>Counter with <code>Timer</code> disposal</h1>

<p>Current count: @currentCount</p>

@code {
    private int currentCount = 0;
    private Timer timer;

    protected override void OnInitialized()
    {
        timer = new Timer(1000);
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            currentCount++;
            StateHasChanged();
        });
    }

    public void Dispose() => timer?.Dispose();
}

CounterWithTimerDisposal2.razor:

@page "/counter-with-timer-disposal-2"
@using System.Timers
@implements IDisposable

<h1>Counter with <code>Timer</code> disposal</h1>

<p>Current count: @currentCount</p>

@code {
    private int currentCount = 0;
    private Timer timer;

    protected override void OnInitialized()
    {
        timer = new Timer(1000);
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            currentCount++;
            StateHasChanged();
        });
    }

    public void Dispose() => timer?.Dispose();
}

Дополнительные сведения см. в разделе:

Асинхронный интерфейс IAsyncDisposable

Для задач асинхронного удаления используйте IAsyncDisposable.DisposeAsync.

Приведенный ниже компонент делает следующее.

  • IAsyncDisposable Реализуется с помощью директивы@implementsRazor.
  • удаляет объект obj, который является неуправляемым типом, реализующим IAsyncDisposable;
  • выполняет проверку значений NULL, так как объект obj создается в методе жизненного цикла (не показан).
@implements IAsyncDisposable

...

@code {
    ...

    public async ValueTask DisposeAsync()
    {
        if (obj is not null)
        {
            await obj.DisposeAsync();
        }
    }
}

Дополнительные сведения см. в разделе:

Назначение null для удаленных объектов

Обычно нет необходимости назначать null удаленным объектам после вызова Dispose/DisposeAsync. К редким случаям назначения null можно отнести приведенные ниже.

  • Если тип объекта плохо реализован и не допускает повторных вызовов Dispose/DisposeAsync, назначьте null после удаления, чтобы корректно пропустить дальнейшие вызовы Dispose/DisposeAsync.
  • Если долгосрочный процесс продолжает сохранять ссылку на удаленный объект, назначение null позволяет сборщику мусора освободить объект, несмотря на эту ссылку.

Это нетипичные сценарии. При использовании объектов, которые реализованы правильно и ведут себя нормально, нет смысла назначать удаленным объектам значение null. В редких случаях, когда объекту должно быть назначено значение null, рекомендуется задокументировать причину и найти решение, которое устраняет необходимость назначения null.

StateHasChanged

Примечание.

Dispose Звонки StateHasChanged и DisposeAsync не поддерживаются. StateHasChanged может вызываться в процессе уничтожения отрисовщика, поэтому запрос обновлений пользовательского интерфейса на этом этапе не поддерживается.

Обработчики событий

Всегда отменяйте подписки на обработчики событий из событий .NET. В следующих примерах формы Blazor показано, как отсоединить обработчик событий в методе Dispose:

  • Подход с использованием частного поля и лямбда-выражения

    @implements IDisposable
    
    <EditForm ... EditContext="editContext" ...>
        ...
        <button type="submit" disabled="@formInvalid">Submit</button>
    </EditForm>
    
    @code {
        ...
    
        private EventHandler<FieldChangedEventArgs>? fieldChanged;
    
        protected override void OnInitialized()
        {
            editContext = new(model);
    
            fieldChanged = (_, __) =>
            {
                ...
            };
    
            editContext.OnFieldChanged += fieldChanged;
        }
    
        public void Dispose()
        {
            editContext.OnFieldChanged -= fieldChanged;
        }
    }
    
  • Подход с использованием частного метода

    @implements IDisposable
    
    <EditForm ... EditContext="editContext" ...>
        ...
        <button type="submit" disabled="@formInvalid">Submit</button>
    </EditForm>
    
    @code {
        ...
    
        protected override void OnInitialized()
        {
            editContext = new(model);
            editContext.OnFieldChanged += HandleFieldChanged;
        }
    
        private void HandleFieldChanged(object sender, FieldChangedEventArgs e)
        {
            ...
        }
    
        public void Dispose()
        {
            editContext.OnFieldChanged -= HandleFieldChanged;
        }
    }
    

Дополнительные сведения см. в разделе Удаление компонентов с помощью раздела IDisposable и IAsyncDisposable.

Дополнительные сведения о компоненте и формах см. в обзоре EditForm основных форм ASP.NET Blazor и других статьях форм на узле Forms.

Анонимные функции, методы и выражения

При использовании анонимных функций, методов или выражений реализовывать IDisposable и отсоединять делегаты не обязательно. Однако отсоединение делегата может стать проблемой, когда объект, предоставляющий событие, переживает время существования компонента, регистрирующего делегат. В этом случае возникает утечка памяти, поскольку зарегистрированный делегат сохраняет исходный объект в активном состоянии. Поэтому следует использовать только следующие подходы, если известно, что делегат события быстро удаляется. Если точное время существования объектов, требующих удаления, неизвестно, подключите метод делегата и правильно удалите делегат, как показано в предыдущих примерах.

  • Подход с использованием анонимного лямбда-метода (явное удаление не требуется):

    private void HandleFieldChanged(object sender, FieldChangedEventArgs e)
    {
        formInvalid = !editContext.Validate();
        StateHasChanged();
    }
    
    protected override void OnInitialized()
    {
        editContext = new(starship);
        editContext.OnFieldChanged += (s, e) => HandleFieldChanged((editContext)s, e);
    }
    
  • Подход с использованием анонимного лямбда-выражения (явное удаление не требуется):

    private ValidationMessageStore? messageStore;
    
    [CascadingParameter]
    private EditContext? CurrentEditContext { get; set; }
    
    protected override void OnInitialized()
    {
        ...
    
        messageStore = new(CurrentEditContext);
    
        CurrentEditContext.OnValidationRequested += (s, e) => messageStore.Clear();
        CurrentEditContext.OnFieldChanged += (s, e) => 
            messageStore.Clear(e.FieldIdentifier);
    }
    

    Полный пример предыдущего кода с анонимными лямбда-выражениями отображается в статье проверки ASP.NET Основных Blazor форм .

Дополнительные сведения см. в статье Очистка неуправляемых ресурсов и следующих разделах, в которых описана реализация методов Dispose и DisposeAsync.

Отменяемая фоновая операция

Компоненты часто выполняют длительные фоновые операции, например осуществление сетевых вызовов (HttpClient) и взаимодействие с базами данных. В целях экономии системных ресурсов в ряде ситуаций желательно отключить выполнение фоновых операций. Например, фоновые асинхронные операции не останавливаются автоматически, когда пользователь выходит из компонента.

Ниже перечислены другие причины, по которым может потребоваться отмена фоновых рабочих элементов.

  • Выполнение фоновой задачи было начато с ошибочными входными данными или параметрами обработки.
  • Текущий набор выполняемых фоновых рабочих элементов должен быть заменен новым набором.
  • Необходимо изменить приоритет выполняемых в данный момент задач.
  • Чтобы выполнить повторное развертывание на сервере, завершите работу приложения.
  • Ресурсы сервера становятся ограниченными, в связи с чем возникает необходимость в пересмотре графика выполнения фоновых рабочих элементов.

Чтобы реализовать механизм отменяемой фоновой операции в компоненте, выполните следующие действия.

В следующем примере :

  • await Task.Delay(5000, cts.Token); представляет длительную асинхронную фоновую операцию.
  • BackgroundResourceMethod представляет длительный фоновый метод, который не должен запускаться, если Resource удаляется перед вызовом метода.

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

События повторного подключения Blazor Server

События жизненного цикла компонентов, описанные в этой статье, работают отдельно от обработчиков событий повторного подключения на стороне сервера. SignalR Когда подключение к клиенту потеряно, прерваны только обновления пользовательского интерфейса. Они возобновляются при восстановлении подключения. Дополнительные сведения о конфигурации и событиях обработчика канала см. в статье Руководство по ASP.NET Core BlazorSignalR.

Дополнительные ресурсы

Обработка перехвата исключений за пределами жизненного Razor цикла компонента