Обработка ошибок в приложениях ASP.NET Core Blazor
Примечание.
Это не последняя версия этой статьи. В текущем выпуске см . версию .NET 8 этой статьи.
Предупреждение
Эта версия ASP.NET Core больше не поддерживается. Дополнительные сведения см. в статье о политике поддержки .NET и .NET Core. В текущем выпуске см . версию .NET 8 этой статьи.
Внимание
Эта информация относится к предварительному выпуску продукта, который может быть существенно изменен до его коммерческого выпуска. Майкрософт не предоставляет никаких гарантий, явных или подразумеваемых, относительно приведенных здесь сведений.
В текущем выпуске см . версию .NET 8 этой статьи.
В этой статье описывается, как Blazor управляет необработанными исключениями и как разрабатывать приложения, которые обнаруживают и обрабатывают ошибки.
Подробные сведения об ошибках во время разработки
Если во время разработки приложение Blazor работает неправильно, подробные сведения об ошибках в приложении могут помочь в устранении неполадок. При возникновении ошибки в приложении Blazor в нижней части экрана отображается светло-желтая полоса:
- Во время разработки из этой полосы можно перейти в консоль браузера, где можно просмотреть исключение.
- В рабочей среде эта полоса уведомляет пользователя о том, что произошла ошибка, и рекомендует обновить содержимое окна браузера.
Пользовательский интерфейс для этого процесса обработки ошибок входит в состав шаблонов проектов Blazor. Не все версии Blazor шаблонов проектов используют data-nosnippet
атрибут для сигнала браузерам не кэшировать содержимое пользовательского интерфейса ошибки, но все версии Blazor документации применяют атрибут.
В файле Blazor Web Appзапустите настройку MainLayout
интерфейса компонента. Так как вспомогательный компонент тега среды (например, <environment include="Production">...</environment>
) не поддерживается в Razor компонентах, в следующем примере внедряется IHostEnvironment настройка сообщений об ошибках для разных сред.
В верхней части MainLayout.razor
:
@inject IHostEnvironment HostEnvironment
Создайте или измените разметку пользовательского Blazor интерфейса ошибки:
<div id="blazor-error-ui" data-nosnippet>
@if (HostEnvironment.IsProduction())
{
<span>An error has occurred.</span>
}
else
{
<span>An unhandled exception occurred.</span>
}
<a href="" class="reload">Reload</a>
<a class="dismiss">🗙</a>
</div>
Blazor Server В приложении настройте интерфейс в Pages/_Host.cshtml
файле. В следующем примере используется вспомогательный элемент тега среды для настройки сообщений об ошибках для разных сред.
Blazor Server В приложении настройте интерфейс в Pages/_Layout.cshtml
файле. В следующем примере используется вспомогательный элемент тега среды для настройки сообщений об ошибках для разных сред.
Blazor Server В приложении настройте интерфейс в Pages/_Host.cshtml
файле. В следующем примере используется вспомогательный элемент тега среды для настройки сообщений об ошибках для разных сред.
Создайте или измените разметку пользовательского Blazor интерфейса ошибки:
<div id="blazor-error-ui" data-nosnippet>
<environment include="Staging,Production">
An error has occurred.
</environment>
<environment include="Development">
An unhandled exception occurred.
</environment>
<a href="" class="reload">Reload</a>
<a class="dismiss">🗙</a>
</div>
В приложении Blazor WebAssembly настройте интерфейс в файле wwwroot/index.html
:
<div id="blazor-error-ui" data-nosnippet>
An unhandled error has occurred.
<a href="" class="reload">Reload</a>
<a class="dismiss">🗙</a>
</div>
Элемент blazor-error-ui
обычно скрыт из-за наличия display: none
стиля blazor-error-ui
класса CSS в автоматически созданной таблице стилей приложения. При возникновении ошибки платформа применяет display: block
к элементу.
Элемент blazor-error-ui
обычно скрыт из-за наличия display: none
стиля blazor-error-ui
класса CSS в таблице стилей сайта в папке wwwroot/css
. При возникновении ошибки платформа применяет display: block
к элементу.
Подробное описание ошибок каналов
Этот раздел относится к Blazor Web Appоперационным системам по каналу.
Этот раздел относится к приложениям Blazor Server.
Ошибки на стороне клиента не включают сведения о стеке вызовов и не предоставляют сведения о причине ошибки, но журналы сервера содержат эти сведения. В целях разработки конфиденциальные сведения об ошибках каналов можно сделать доступными для клиента, включив подробное описание ошибок.
Задайте для параметра CircuitOptions.DetailedErrors значение true
. Дополнительные сведения и пример см. в разделе Руководство поBlazorSignalR ASP.NET Core .
Альтернативой параметру CircuitOptions.DetailedErrors является установка DetailedErrors
ключа true
конфигурации в файле параметров среды приложения Development
(appsettings.Development.json
). Кроме того, задайте для параметра SignalRведения журнала на стороне сервера (Microsoft.AspNetCore.SignalR
) значение Отладка или Трассировка, чтобы обеспечить подробное ведение журнала SignalR.
appsettings.Development.json
:
{
"DetailedErrors": true,
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information",
"Microsoft.AspNetCore.SignalR": "Debug"
}
}
}
DetailedErrors Ключ конфигурации также можно задать true
для использования ASPNETCORE_DETAILEDERRORS
переменной среды со значением true
на Development
/Staging
серверах среды или локальной системе.
Предупреждение
Никогда не предоставляйте сведения об ошибках клиентам в Интернете, так как это подвергает безопасность угрозе.
Подробные ошибки для Razor отрисовки на стороне сервера компонента
Этот раздел относится к Blazor Web Apps.
RazorComponentsServiceOptions.DetailedErrors Используйте параметр для управления получением подробных сведений об ошибках для Razor отрисовки на стороне сервера компонента. Значение по умолчанию — false
.
В следующем примере приводятся подробные ошибки:
builder.Services.AddRazorComponents(options =>
options.DetailedErrors = builder.Environment.IsDevelopment());
Предупреждение
Включите только подробные Development
ошибки в среде. Подробные ошибки могут содержать конфиденциальную информацию о приложении, которое злоумышленники могут использовать в атаке.
В предыдущем примере обеспечивается степень безопасности путем задания значения DetailedErrors на основе возвращаемого IsDevelopmentзначения. Если приложение находится в Development
среде, DetailedErrors задается значение true
. Такой подход не является обманным, так как в среде можно разместить рабочее приложение на общедоступном сервере Development
.
Управление необработанными исключениями в коде разработчика
Чтобы приложение продолжало работу после возникновения ошибки, в нем должна быть логика обработки ошибок. В последующих разделах этой статьи описываются потенциальные источники необработанных исключений.
В рабочей среде не следует отображать сообщения об исключениях платформы или трассировки стека в пользовательском интерфейсе. Отображение сообщений об исключениях или трассировок стека может:
- Раскрыть конфиденциальные сведения конечным пользователям.
- Помочь злоумышленнику обнаружить слабые места в приложении, что может нарушить безопасность приложения, сервера или сети.
Необработанные исключения для каналов
Этот раздел относится к приложениям на стороне сервера, работающим над каналом.
Razor компоненты с включенной интерактивностью сервера находятся на сервере с отслеживанием состояния. Хотя пользователи взаимодействуют с компонентом на сервере, они поддерживают подключение к серверу, известному как канал. Канал содержит экземпляры активных компонентов, а также многие другие аспекты состояния, например:
- Последние отображаемые выходные данные компонентов.
- Текущий набор делегатов обработки событий, которые могут вызываться событиями на стороне клиента.
Если пользователь открывает приложение на нескольких вкладках браузера, он создает несколько независимых каналов.
Blazor обрабатывает большинство необработанных исключений как неустранимые для канала, где они происходят. Если канал завершается из-за необработанного исключения, пользователь может продолжить взаимодействие с приложением только путем перезагрузки страницы для создания нового канала. Это не влияет на остальные каналы для других пользователей или других вкладок браузера. Этот сценарий аналогичен сценарию с настольным приложением, которое аварийно завершает работу. Такое приложение необходимо перезапустить, но остальные приложения это не затрагивает.
Платформа завершает канал при возникновении необработанного исключения по следующим причинам:
- Необработанное исключение часто оставляет канал в неопределенном состоянии.
- После необработанного исключения нормальную работу приложения невозможно гарантировать.
- Дальнейшая работа канала в неопределенном состоянии может стать причиной уязвимостей системы безопасности.
Глобальная обработка исключений
Подходы к обработке исключений глобально см. в следующих разделах:
- Границы ошибок: применяется ко всем Blazor приложениям.
- Альтернативная глобальная обработка исключений: применяется к Blazor Server, Blazor WebAssemblyи Blazor Web Apps (8.0 или более поздней версии), которые применяют глобальный интерактивный режим отрисовки.
Границы ошибок
Границы ошибок предоставляют удобный подход к обработке исключений. Компонент ErrorBoundary:
- Отображает свое дочернее содержимое при возникновении ошибки.
- Отрисовывает пользовательский интерфейс ошибки при возникновении необработанного исключения любым компонентом в пределах границы ошибки.
Чтобы определить границу ошибки, используйте ErrorBoundary компонент для упаковки одного или нескольких других компонентов. Граница ошибки управляет необработанными исключениями, создаваемыми компонентами, которые он упаковывает.
<ErrorBoundary>
...
</ErrorBoundary>
Чтобы реализовать границу ошибок в глобальном режиме, добавьте границу вокруг основного макета приложения.
В MainLayout.razor
:
<article class="content px-4">
<ErrorBoundary>
@Body
</ErrorBoundary>
</article>
В Blazor Web Appс границой ошибки, применяемой только к статическому MainLayout
компоненту, граница активна только во время отрисовки на стороне статического сервера (статический SSR). Граница не активируется только потому, что компонент дальше вниз иерархии компонентов является интерактивным.
Не удается применить интерактивный режим отрисовки к MainLayout
компоненту, так как параметр компонента Body
является делегатом RenderFragment , который является произвольным кодом и не может быть сериализован. Чтобы включить интерактивность для компонента и rest компонентов, которые далее вниз по иерархии компонентов, приложение должно принять глобальный интерактивный режим отрисовки, применяя интерактивный режим отрисовки к HeadOutlet
Routes
экземплярам и экземплярам компонентов в корневом компоненте приложения, который обычно является компонентомApp
.MainLayout
В следующем примере используется режим отрисовки интерактивного сервера (InteractiveServer
) глобально.
В Components/App.razor
:
<HeadOutlet @rendermode="InteractiveServer" />
...
<Routes @rendermode="InteractiveServer" />
Если вы предпочитаете не включать глобальную интерактивность, поместите границу ошибки дальше вниз по иерархии компонентов. Важные понятия, которые следует учитывать, заключается в том, что везде, где размещается граница ошибки:
- Если компонент, на котором размещена граница ошибки, не является интерактивным, граница ошибки может активироваться только на сервере во время статического SSR. Например, граница может активироваться при возникновении ошибки в методе жизненного цикла компонента, но не для события, активируемого взаимодействием пользователей в компоненте, например ошибка, возникаемая обработчиком нажатия кнопки.
- Если компонент, на котором размещена граница ошибки, является интерактивным, граница ошибки может активироваться для интерактивных компонентов, которые он упаковывает.
Примечание.
Приведенные выше рекомендации не относятся к автономным Blazor WebAssembly приложениям, так как клиентская отрисовка (CSR) Blazor WebAssembly приложения полностью интерактивна.
Рассмотрим следующий пример, когда исключение, созданное внедренным компонентом счетчика, перехватывается границой ошибки в Home
компоненте, которая принимает интерактивный режим отрисовки.
EmbeddedCounter.razor
:
<h1>Embedded Counter</h1>
<p role="status">Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
private int currentCount = 0;
private void IncrementCount()
{
currentCount++;
if (currentCount > 5)
{
throw new InvalidOperationException("Current count is too big!");
}
}
}
Home.razor
:
@page "/"
@rendermode InteractiveServer
<PageTitle>Home</PageTitle>
<h1>Home</h1>
<ErrorBoundary>
<EmbeddedCounter />
</ErrorBoundary>
Рассмотрим следующий пример, когда исключение, вызываемое внедренным компонентом счетчика, перехватывается границой ошибки в компоненте Home
.
EmbeddedCounter.razor
:
<h1>Embedded Counter</h1>
<p role="status">Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
private int currentCount = 0;
private void IncrementCount()
{
currentCount++;
if (currentCount > 5)
{
throw new InvalidOperationException("Current count is too big!");
}
}
}
Home.razor
:
@page "/"
<PageTitle>Home</PageTitle>
<h1>Home</h1>
<ErrorBoundary>
<EmbeddedCounter />
</ErrorBoundary>
Если необработанное исключение создается для currentCount
более пяти:
- Ошибка регистрируется обычно (
System.InvalidOperationException: Current count is too big!
). - Созданное исключение обрабатывается границей ошибки.
- Пользовательский интерфейс ошибки по умолчанию отображается границой ошибки.
Компонент ErrorBoundary отрисовывает пустой <div>
элемент с помощью blazor-error-boundary
класса CSS для его содержимого ошибки. Цвета, текст и значок пользовательского интерфейса по умолчанию определяются в таблице стилей приложения в wwwroot
папке, поэтому вы можете настроить пользовательский интерфейс ошибки.
Чтобы изменить содержимое ошибки по умолчанию, выполните следующие действия.
- Обтекание компонентами границы ошибки в свойстве ChildContent .
- Задайте для свойства содержимое ErrorContent ошибки.
В следующем примере выполняется оболочка EmbeddedCounter
компонента и предоставляется пользовательское содержимое ошибки:
<ErrorBoundary>
<ChildContent>
<EmbeddedCounter />
</ChildContent>
<ErrorContent>
<p class="errorUI">😈 A rotten gremlin got us. Sorry!</p>
</ErrorContent>
</ErrorBoundary>
В предыдущем примере таблица стилей приложения, предположительно, включает errorUI
класс CSS для стиля содержимого. Содержимое ошибки отрисовывается из ErrorContent свойства без элемента уровня блока. Элемент уровня блока, например деление (<div>
) или элемент абзаца (<p>
), может упаковать разметку содержимого ошибки, но она не требуется.
При необходимости используйте контекст (@context
) для получения данных об ошибках ErrorContent :
<ErrorContent>
@context.HelpLink
</ErrorContent>
Также ErrorContent можно назвать контекст. В следующем примере контекст называется exception
:
<ErrorContent Context="exception">
@exception.HelpLink
</ErrorContent>
Предупреждение
Никогда не предоставляйте сведения об ошибках клиентам в Интернете, так как это подвергает безопасность угрозе.
Если граница ошибки определена в макете приложения, пользовательский интерфейс ошибки отображается независимо от того, на какую страницу пользователь переходит после возникновения ошибки. В большинстве случаев рекомендуется сузить границы ошибок. Если граница ошибки широко ограничена, ее можно сбросить в состояние, отличное от ошибок, на последующих событиях навигации страницы путем вызова метода границы Recover ошибки.
В MainLayout.razor
:
- Добавьте поле для ErrorBoundary записи ссылки на нее
@ref
с помощью директивы атрибута. - В методе
OnParameterSet
жизненного цикла можно активировать восстановление на границе ошибки, Recover чтобы очистить ошибку при переходе пользователя к другому компоненту.
...
<ErrorBoundary @ref="errorBoundary">
@Body
</ErrorBoundary>
...
@code {
private ErrorBoundary? errorBoundary;
protected override void OnParametersSet()
{
errorBoundary?.Recover();
}
}
Чтобы избежать бесконечного цикла, когда восстановление просто rerenders компонента, который снова вызывает ошибку, не вызывайте Recover логику отрисовки. Вызов выполняется Recover только в том случае, если:
- Пользователь выполняет жест пользовательского интерфейса, например нажатие кнопки, чтобы указать, что они хотят повторить процедуру или когда пользователь переходит к новому компоненту.
- Дополнительная логика, которая выполняется, также очищает исключение. Если компонент перерисовывается, ошибка не повторяется.
В следующем примере пользователь может восстановиться из исключения с помощью кнопки:
<ErrorBoundary @ref="errorBoundary">
<ChildContent>
<EmbeddedCounter />
</ChildContent>
<ErrorContent>
<div class="alert alert-danger" role="alert">
<p class="fs-3 fw-bold">😈 A rotten gremlin got us. Sorry!</p>
<p>@context.HelpLink</p>
<button class="btn btn-info" @onclick="_ => errorBoundary?.Recover()">
Clear
</button>
</div>
</ErrorContent>
</ErrorBoundary>
@code {
private ErrorBoundary? errorBoundary;
}
Кроме того, можно переопределить OnErrorAsyncподкласс ErrorBoundary для пользовательской обработки. Следующий пример просто регистрирует ошибку, но вы можете реализовать любой нужный код обработки ошибок. Вы можете удалить строку, CompletedTask возвращающую код, если код ожидает асинхронную задачу.
CustomErrorBoundary.razor
:
@inherits ErrorBoundary
@inject ILogger<CustomErrorBoundary> Logger
@if (CurrentException is null)
{
@ChildContent
}
else if (ErrorContent is not null)
{
@ErrorContent(CurrentException)
}
@code {
protected override Task OnErrorAsync(Exception ex)
{
Logger.LogError(ex, "😈 A rotten gremlin got us. Sorry!");
return Task.CompletedTask;
}
}
Предыдущий пример также можно реализовать как класс.
CustomErrorBoundary.cs
:
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
namespace BlazorSample;
public class CustomErrorBoundary : ErrorBoundary
{
[Inject]
ILogger<CustomErrorBoundary> Logger { get; set; } = default!;
protected override Task OnErrorAsync(Exception ex)
{
Logger.LogError(ex, "😈 A rotten gremlin got us. Sorry!");
return Task.CompletedTask;
}
}
Любой из предыдущих реализаций, используемых в компоненте:
<CustomErrorBoundary>
...
</CustomErrorBoundary>
Альтернативная глобальная обработка исключений
Подход, описанный в этом разделе, применяется к Blazor Server, Blazor WebAssemblyи Blazor Web Apps, которые применяют глобальный интерактивный режим отрисовки (InteractiveServer
, InteractiveWebAssembly
или InteractiveAuto
). Подход не работает с Blazor Web Appтем, что применяет режимы отрисовки на странице или компоненте или статический серверный отрисовка (статический SSR), так как этот подход зависит CascadingValue
/CascadingParameter
от подхода, который не работает в границах режима отрисовки или с компонентами, которые принимают статический SSR.
Альтернативой использованию границ ошибок (ErrorBoundary) является передача пользовательского компонента ошибки в качестве CascadingValue
дочерним компонентам. Преимущество использования компонента по сравнению с использованием внедренной службы или реализации пользовательского средства ведения журнала заключается в том, что при возникновении ошибки каскадный компонент может отрисовывать содержимое и применять стили CSS.
В следующем примере компонента ProcessError
просто регистрируются ошибки, но методы компонента могут обрабатывать ошибки любым способом, требуемым для приложения, в том числе с использованием нескольких методов обработки ошибок.
ProcessError.razor
:
@inject ILogger<ProcessError> Logger
<CascadingValue Value="this">
@ChildContent
</CascadingValue>
@code {
[Parameter]
public RenderFragment? ChildContent { get; set; }
public void LogError(Exception ex)
{
Logger.LogError("ProcessError.LogError: {Type} Message: {Message}",
ex.GetType(), ex.Message);
// Call StateHasChanged if LogError directly participates in
// rendering. If LogError only logs or records the error,
// there's no need to call StateHasChanged.
//StateHasChanged();
}
}
Примечание.
Дополнительные сведения о RenderFragment см. в статье Компоненты Razor ASP.NET Core.
При использовании этого подхода откройте Blazor Web AppRoutes
компонент и заключите Router компонент (<Router>...</Router>
) с ProcessError
помощью компонента. Благодаря этому компонент ProcessError
сможет переходить к любому компоненту приложения, в котором компонент ProcessError
получен как CascadingParameter
.
В Routes.razor
:
<ProcessError>
<Router ...>
...
</Router>
</ProcessError>
При использовании этого подхода в Blazor Server Blazor WebAssembly приложении откройте App
компонент, заключите Router компонент (<Router>...</Router>
) с ProcessError
помощью компонента. Благодаря этому компонент ProcessError
сможет переходить к любому компоненту приложения, в котором компонент ProcessError
получен как CascadingParameter
.
В App.razor
:
<ProcessError>
<Router ...>
...
</Router>
</ProcessError>
Чтобы обработать ошибки в компоненте, выполните приведенные ниже действия.
Назначьте компонент
ProcessError
какCascadingParameter
в блоке@code
. В примере компонентаCounter
в приложении, основанном на шаблоне проекта Blazor, добавьте следующее свойствоProcessError
:[CascadingParameter] public ProcessError? ProcessError { get; set; }
Вызовите метод обработки ошибок в любом блоке
catch
с соответствующим типом исключения. В примере компонентаProcessError
есть только один методLogError
. Однако компонент обработки ошибок может предоставить любое количество соответствующих методов, чтобы удовлетворить альтернативные требования к обработке ошибок во всем приложении.Counter
Следующий пример блока компонентов@code
включаетProcessError
каскадный параметр и перехватывает исключение для ведения журнала, если число больше пяти:@code { private int currentCount = 0; [CascadingParameter] public ProcessError? ProcessError { get; set; } private void IncrementCount() { try { currentCount++; if (currentCount > 5) { throw new InvalidOperationException("Current count is over five!"); } } catch (Exception ex) { ProcessError?.LogError(ex); } } }
Ошибка, зарегистрированная в журнале:
fail: {COMPONENT NAMESPACE}.ProcessError[0]
ProcessError.LogError: System.InvalidOperationException Message: Current count is over five!
Если метод LogError
напрямую участвует в отрисовке, например показывает пользовательскую панель сообщений об ошибках или изменяет стили CSS отрисованных элементов, вызовите StateHasChanged
в конце метода LogError
для повторной отрисовки пользовательского интерфейса.
Так как подходы в этом разделе обрабатывают ошибки с оператором try-catch
, подключение приложения SignalR между клиентом и сервером не нарушается при возникновении ошибки, и канал остается живым. Другие необработанные исключения остаются неустранимыми для канала. Дополнительные сведения см. в разделе о том, как канал реагирует на необработанные исключения.
Приложение может использовать компонент обработки ошибок в качестве каскадного значения для обработки ошибок централизованно.
Следующий компонент ProcessError
передает себя дочерним компонентам в качестве CascadingValue
. В следующем примере просто регистрируется ошибка, но методы компонента могут обрабатывать ошибки любым способом, требуемым для приложения, в том числе с использованием нескольких методов обработки ошибок. Преимущество использования компонента по сравнению с использованием внедренной службы или реализации пользовательского средства ведения журнала заключается в том, что при возникновении ошибки каскадный компонент может отрисовывать содержимое и применять стили CSS.
ProcessError.razor
:
@using Microsoft.Extensions.Logging
@inject ILogger<ProcessError> Logger
<CascadingValue Value="this">
@ChildContent
</CascadingValue>
@code {
[Parameter]
public RenderFragment ChildContent { get; set; }
public void LogError(Exception ex)
{
Logger.LogError("ProcessError.LogError: {Type} Message: {Message}",
ex.GetType(), ex.Message);
}
}
Примечание.
Дополнительные сведения о RenderFragment см. в статье Компоненты Razor ASP.NET Core.
В компоненте App
создайте программу-оболочку для компонента Router с помощью компонента ProcessError
. Благодаря этому компонент ProcessError
сможет переходить к любому компоненту приложения, в котором компонент ProcessError
получен как CascadingParameter
.
App.razor
:
<ProcessError>
<Router ...>
...
</Router>
</ProcessError>
Чтобы обработать ошибки в компоненте, выполните приведенные ниже действия.
Назначьте компонент
ProcessError
какCascadingParameter
в блоке@code
:[CascadingParameter] public ProcessError ProcessError { get; set; }
Вызовите метод обработки ошибок в любом блоке
catch
с соответствующим типом исключения. В примере компонентаProcessError
есть только один методLogError
. Однако компонент обработки ошибок может предоставить любое количество соответствующих методов, чтобы удовлетворить альтернативные требования к обработке ошибок во всем приложении.try { ... } catch (Exception ex) { ProcessError.LogError(ex); }
Используя компонент ProcessError
и метод LogError
из предыдущего примера, консоль инструментов разработчика обозревателя указывает на перехваченную, зарегистрированную ошибку:
fail: {COMPONENT NAMESPACE}.Shared.ProcessError[0]
ProcessError.LogError: System.NullReferenceException Message: Object reference not set to an instance of an object.
Если метод LogError
напрямую участвует в отрисовке, например показывает пользовательскую панель сообщений об ошибках или изменяет стили CSS отрисованных элементов, вызовите StateHasChanged
в конце метода LogError
для повторной отрисовки пользовательского интерфейса.
Так как подходы в этом разделе предусматривают обработку ошибок с помощью инструкции try-catch
, соединение SignalR приложения Blazor между клиентом и сервером не прерывается при возникновении ошибки и канал остается активным. Необработанное исключение является неустранимым для канала. Дополнительные сведения см. в разделе о том, как канал реагирует на необработанные исключения.
Регистрация ошибок с помощью постоянного поставщика
При возникновении необработанного исключения оно заносится в журнал экземпляров ILogger, настроенных в контейнере службы. Blazor Выходные данные консоли журнала приложений с поставщиком ведения журнала консоли. Рассмотрите возможность ведения журнала в расположение на сервере (или серверном веб-API для клиентских приложений) с помощью поставщика, который управляет размером журнала и поворотом журнала. Кроме того, приложение может использовать службу управления производительностью приложений (APM), например Azure Application Insights (Azure Monitor).
Примечание.
Функции Native Application Insights для поддержки клиентских приложений и собственной Blazor платформы поддержки Google Analytics могут стать доступными в будущих выпусках этих технологий. Дополнительные сведения см. в статьях Поддержка App Insights на стороне клиента Blazor WASM (microsoft/ApplicationInsights-dotnet #2143) и Веб-аналитика и диагностика (содержит ссылки на реализации сообщества) (dotnet/aspnetcore #5461). В то же время клиентское приложение может использовать пакет SDK JavaScript Для Application Insights с взаимодействием для JS регистрации ошибок непосредственно в Application Insights из клиентского приложения.
Во время разработки в Blazor приложении, работающем над каналом, приложение обычно отправляет полные сведения об исключениях в консоль браузера, чтобы помочь в отладке. В рабочей среде подробные сообщения об ошибках не отправляются клиентам, но все сведения об исключениях записываются на сервер.
Необходимо решить, какие инциденты следует заносить в журнал, и выбрать уровень серьезности регистрируемых инцидентов. Злоумышленники могут попытаться вызывать ошибки намеренно. Например, не заносите в журнал инцидент в результате ошибки, когда в URL-адресе компонента, отображающего сведения о продукте, указан неизвестный ProductId
. Не все ошибки следует рассматривать как инциденты, подлежащие записи в журнал.
Дополнительные сведения см. в следующих статьях:
- Ведение журналов BlazorASP.NET Core
- Обработка ошибок в ASP.NET Core‡
- Создание веб-API с помощью ASP.NET Core
.Применяется к серверным приложениям и другим серверным Blazor приложениям ASP.NET Core, которые являются внутренними приложениями веб-API для Blazor. Клиентские приложения могут перехватывать и отправлять сведения об ошибках клиента в веб-API, который записывает сведения об ошибке в постоянный поставщик ведения журнала.
При возникновении необработанного исключения оно заносится в журнал экземпляров ILogger, настроенных в контейнере службы. Blazor Выходные данные консоли журнала приложений с поставщиком ведения журнала консоли. Возможно, следует сохранять записи журнала в более постоянное расположение на сервере, отправляя сведения об ошибке в серверный веб-API, который использует поставщик ведения журнала с возможностью управления размером журнала и ротации журналов. Кроме того, серверное приложение веб-API может использовать службу управления производительностью приложений (APM), например Azure Application Insights (Azure Monitor)†, для записи сведений об ошибках, получаемых от клиентов.
Необходимо решить, какие инциденты следует заносить в журнал, и выбрать уровень серьезности регистрируемых инцидентов. Злоумышленники могут попытаться вызывать ошибки намеренно. Например, не заносите в журнал инцидент в результате ошибки, когда в URL-адресе компонента, отображающего сведения о продукте, указан неизвестный ProductId
. Не все ошибки следует рассматривать как инциденты, подлежащие записи в журнал.
Дополнительные сведения см. в следующих статьях:
- Ведение журналов BlazorASP.NET Core
- Обработка ошибок в ASP.NET Core‡
- Создание веб-API с помощью ASP.NET Core
функции †Native Application Insights для поддержки клиентских приложений и собственной Blazor платформы поддержки Google Analytics могут стать доступными в будущих выпусках этих технологий. Дополнительные сведения см. в статьях Поддержка App Insights на стороне клиента Blazor WASM (microsoft/ApplicationInsights-dotnet #2143) и Веб-аналитика и диагностика (содержит ссылки на реализации сообщества) (dotnet/aspnetcore #5461). В то же время клиентское приложение может использовать пакет SDK JavaScript Для Application Insights с взаимодействием для JS регистрации ошибок непосредственно в Application Insights из клиентского приложения.
‡Применяется к приложениям ASP.NET Core на стороне сервера, которые являются серверными приложениями веб-API для приложений Blazor. Приложения на стороне клиента регистрируют и отправляют сведения об ошибках в веб-API, который регистрирует сведения об ошибке в постоянном поставщике ведения журнала.
Места, где могут возникнуть ошибки
Код платформы и приложения может активировать необработанные исключения в любом из следующих расположений, которые описаны в следующих разделах этой статьи.
Экземпляр компонента
Когда Blazor создает экземпляр компонента:
- Вызывается конструктор компонента.
- Вызываются конструкторы служб DI, которые передаются в конструктор компонента с помощью директивы
@inject
или атрибута[Inject]
.
Ошибка в выполненном конструкторе или методе задания для любого свойства [Inject]
приводит к возникновению необработанного исключения и предотвращает создание экземпляра компонента на платформе. Если приложение работает над каналом, канал завершается ошибкой. Если логика конструктора может вызывать исключения, приложение должно выполнить перехват исключений с помощью инструкции try-catch
с обработкой ошибок и ведением журнала.
Методы жизненного цикла
В течение времени существования компонента Blazor вызывает методы жизненного цикла. Если какой-либо метод жизненного цикла вызывает исключение (синхронно или асинхронно), такое исключение является неустранимым для канала . Для компонентов, обрабатывающих ошибки в методах жизненного цикла, добавьте логику обработки ошибок.
В следующем примере OnParametersSetAsync вызывает метод для получения продукта:
- Исключение, вызванное в методе
ProductRepository.GetProductByIdAsync
, обрабатывается инструкциейtry-catch
. catch
При выполнении блока:loadFailed
получает значениеtrue
, которое используется для вывода сообщения об ошибке пользователю.- Ошибка регистрируется в журнале.
@page "/product-details/{ProductId:int?}"
@inject ILogger<ProductDetails> Logger
@inject IProductRepository Product
<PageTitle>Product Details</PageTitle>
<h1>Product Details Example</h1>
@if (details != null)
{
<h2>@details.ProductName</h2>
<p>
@details.Description
<a href="@details.Url">Company Link</a>
</p>
}
else if (loadFailed)
{
<h1>Sorry, we could not load this product due to an error.</h1>
}
else
{
<h1>Loading...</h1>
}
@code {
private ProductDetail? details;
private bool loadFailed;
[Parameter]
public int ProductId { get; set; }
protected override async Task OnParametersSetAsync()
{
try
{
loadFailed = false;
// Reset details to null to display the loading indicator
details = null;
details = await Product.GetProductByIdAsync(ProductId);
}
catch (Exception ex)
{
loadFailed = true;
Logger.LogWarning(ex, "Failed to load product {ProductId}", ProductId);
}
}
public class ProductDetail
{
public string? ProductName { get; set; }
public string? Description { get; set; }
public string? Url { get; set; }
}
/*
* Register the service in Program.cs:
* using static BlazorSample.Components.Pages.ProductDetails;
* builder.Services.AddScoped<IProductRepository, ProductRepository>();
*/
public interface IProductRepository
{
public Task<ProductDetail> GetProductByIdAsync(int id);
}
public class ProductRepository : IProductRepository
{
public Task<ProductDetail> GetProductByIdAsync(int id)
{
return Task.FromResult(
new ProductDetail()
{
ProductName = "Flowbee ",
Description = "The Revolutionary Haircutting System You've Come to Love!",
Url = "https://flowbee.com/"
});
}
}
}
@page "/product-details/{ProductId:int?}"
@inject ILogger<ProductDetails> Logger
@inject IProductRepository Product
<PageTitle>Product Details</PageTitle>
<h1>Product Details Example</h1>
@if (details != null)
{
<h2>@details.ProductName</h2>
<p>
@details.Description
<a href="@details.Url">Company Link</a>
</p>
}
else if (loadFailed)
{
<h1>Sorry, we could not load this product due to an error.</h1>
}
else
{
<h1>Loading...</h1>
}
@code {
private ProductDetail? details;
private bool loadFailed;
[Parameter]
public int ProductId { get; set; }
protected override async Task OnParametersSetAsync()
{
try
{
loadFailed = false;
// Reset details to null to display the loading indicator
details = null;
details = await Product.GetProductByIdAsync(ProductId);
}
catch (Exception ex)
{
loadFailed = true;
Logger.LogWarning(ex, "Failed to load product {ProductId}", ProductId);
}
}
public class ProductDetail
{
public string? ProductName { get; set; }
public string? Description { get; set; }
public string? Url { get; set; }
}
/*
* Register the service in Program.cs:
* using static BlazorSample.Components.Pages.ProductDetails;
* builder.Services.AddScoped<IProductRepository, ProductRepository>();
*/
public interface IProductRepository
{
public Task<ProductDetail> GetProductByIdAsync(int id);
}
public class ProductRepository : IProductRepository
{
public Task<ProductDetail> GetProductByIdAsync(int id)
{
return Task.FromResult(
new ProductDetail()
{
ProductName = "Flowbee ",
Description = "The Revolutionary Haircutting System You've Come to Love!",
Url = "https://flowbee.com/"
});
}
}
}
@page "/product-details/{ProductId:int}"
@using Microsoft.Extensions.Logging
@inject ILogger<ProductDetails> Logger
@inject IProductRepository ProductRepository
@if (details != null)
{
<h1>@details.ProductName</h1>
<p>@details.Description</p>
}
else if (loadFailed)
{
<h1>Sorry, we could not load this product due to an error.</h1>
}
else
{
<h1>Loading...</h1>
}
@code {
private ProductDetail? details;
private bool loadFailed;
[Parameter]
public int ProductId { get; set; }
protected override async Task OnParametersSetAsync()
{
try
{
loadFailed = false;
// Reset details to null to display the loading indicator
details = null;
details = await ProductRepository.GetProductByIdAsync(ProductId);
}
catch (Exception ex)
{
loadFailed = true;
Logger.LogWarning(ex, "Failed to load product {ProductId}", ProductId);
}
}
public class ProductDetail
{
public string? ProductName { get; set; }
public string? Description { get; set; }
}
public interface IProductRepository
{
public Task<ProductDetail> GetProductByIdAsync(int id);
}
}
@page "/product-details/{ProductId:int}"
@using Microsoft.Extensions.Logging
@inject ILogger<ProductDetails> Logger
@inject IProductRepository ProductRepository
@if (details != null)
{
<h1>@details.ProductName</h1>
<p>@details.Description</p>
}
else if (loadFailed)
{
<h1>Sorry, we could not load this product due to an error.</h1>
}
else
{
<h1>Loading...</h1>
}
@code {
private ProductDetail? details;
private bool loadFailed;
[Parameter]
public int ProductId { get; set; }
protected override async Task OnParametersSetAsync()
{
try
{
loadFailed = false;
// Reset details to null to display the loading indicator
details = null;
details = await ProductRepository.GetProductByIdAsync(ProductId);
}
catch (Exception ex)
{
loadFailed = true;
Logger.LogWarning(ex, "Failed to load product {ProductId}", ProductId);
}
}
public class ProductDetail
{
public string? ProductName { get; set; }
public string? Description { get; set; }
}
public interface IProductRepository
{
public Task<ProductDetail> GetProductByIdAsync(int id);
}
}
@page "/product-details/{ProductId:int}"
@using Microsoft.Extensions.Logging
@inject ILogger<ProductDetails> Logger
@inject IProductRepository ProductRepository
@if (details != null)
{
<h1>@details.ProductName</h1>
<p>@details.Description</p>
}
else if (loadFailed)
{
<h1>Sorry, we could not load this product due to an error.</h1>
}
else
{
<h1>Loading...</h1>
}
@code {
private ProductDetail details;
private bool loadFailed;
[Parameter]
public int ProductId { get; set; }
protected override async Task OnParametersSetAsync()
{
try
{
loadFailed = false;
// Reset details to null to display the loading indicator
details = null;
details = await ProductRepository.GetProductByIdAsync(ProductId);
}
catch (Exception ex)
{
loadFailed = true;
Logger.LogWarning(ex, "Failed to load product {ProductId}", ProductId);
}
}
public class ProductDetail
{
public string ProductName { get; set; }
public string Description { get; set; }
}
public interface IProductRepository
{
public Task<ProductDetail> GetProductByIdAsync(int id);
}
}
@page "/product-details/{ProductId:int}"
@using Microsoft.Extensions.Logging
@inject ILogger<ProductDetails> Logger
@inject IProductRepository ProductRepository
@if (details != null)
{
<h1>@details.ProductName</h1>
<p>@details.Description</p>
}
else if (loadFailed)
{
<h1>Sorry, we could not load this product due to an error.</h1>
}
else
{
<h1>Loading...</h1>
}
@code {
private ProductDetail details;
private bool loadFailed;
[Parameter]
public int ProductId { get; set; }
protected override async Task OnParametersSetAsync()
{
try
{
loadFailed = false;
// Reset details to null to display the loading indicator
details = null;
details = await ProductRepository.GetProductByIdAsync(ProductId);
}
catch (Exception ex)
{
loadFailed = true;
Logger.LogWarning(ex, "Failed to load product {ProductId}", ProductId);
}
}
public class ProductDetail
{
public string ProductName { get; set; }
public string Description { get; set; }
}
public interface IProductRepository
{
public Task<ProductDetail> GetProductByIdAsync(int id);
}
}
Логика отрисовки
Декларативная разметка в файле компонента Razor (.razor
) компилируется в метод C# с именем BuildRenderTree. Когда компонент отрисовывается, BuildRenderTree выполняет и создает структуру данных, описывающую элементы, текст и дочерние компоненты отрисованного компонента.
Логика отрисовки может вызывать исключение. Пример этого сценария происходит, когда @someObject.PropertyName
вычисляется, но @someObject
имеет значение null
. Для Blazor приложений, работающих над каналом, необработанное исключение, вызываемое логикой отрисовки, является неустранимым для канала приложения.
Чтобы предотвратить NullReferenceException в логике отрисовки, проверьте наличие объекта null
перед обращением к его элементам. В следующем примере свойства person.Address
недоступны, если person.Address
имеет значение null
:
@if (person.Address != null)
{
<div>@person.Address.Line1</div>
<div>@person.Address.Line2</div>
<div>@person.Address.City</div>
<div>@person.Address.Country</div>
}
В приведенном выше коде предполагается, что person
не имеет значение null
. Часто структура кода гарантирует, что объект существует на момент отрисовки компонента. В таких случаях нет необходимости проверять наличие null
в логике отрисовки. В предыдущем примере person
может гарантированно существовать, так как person
создается при создании экземпляра компонента, как показано в следующем примере:
@code {
private Person person = new();
...
}
Обработчики событий
Код на стороне клиента активирует вызовы кода C# при создании обработчиков событий с помощью:
@onclick
@onchange
- Другие атрибуты
@on...
@bind
В этих сценариях код обработчика событий может вызывать необработанное исключение.
Если приложение вызывает код, который может завершиться ошибкой по внешним причинам, перехватите исключения с помощью инструкции try-catch
с обработкой ошибок и ведением журнала.
Если обработчик событий вызывает необработанное исключение (например, запрос к базе данных завершается ошибкой), который не перехватывается и не обрабатывается кодом разработчика:
- Платформа регистрирует исключение.
- Blazor В приложении, работающем над каналом, исключение является смертельным для канала приложения.
Удаление компонентов
Компонент может быть удален из пользовательского интерфейса, например, потому, что пользователь перешел на другую страницу. Когда компонент, реализующий System.IDisposable, удаляется из пользовательского интерфейса, платформа вызывает метод Dispose компонента.
Если метод компонента Dispose
создает необработанное исключение в приложении, работающем над Blazor каналом, исключение неустранимо для канала приложения.
Если логика удаления может вызывать исключения, приложение должно выполнить перехват исключений с помощью инструкции try-catch
с обработкой ошибок и ведением журнала.
Дополнительные сведения об удалении компонентов см. в разделе Жизненный цикл компонента RazorASP.NET Core.
Взаимодействие с JavaScript
IJSRuntime регистрируется платформой Blazor. IJSRuntime.InvokeAsync позволяет коду .NET выполнять асинхронные вызовы к среде выполнения JavaScript (JS) в браузере пользователя.
К обработке ошибок с помощью InvokeAsync применяются следующие условия:
- Если вызов к InvokeAsync выполняется синхронно, возникает исключение .NET. Вызов к InvokeAsync может завершиться ошибкой, например, поскольку передаваемые аргументы не могут быть сериализованы. Код разработчика должен перехватить исключение. Если код приложения в обработчике событий или методе жизненного цикла компонентов не обрабатывает исключение в Blazor приложении, работающем над каналом, результирующее исключение является неустранимым для канала приложения.
- Если вызов InvokeAsync асинхронно завершается сбоем, .NET Task также завершается сбоем. Вызов к InvokeAsync может завершиться ошибкой, например, потому что код на стороне JS вызывает исключение или возвращает
Promise
, который был завершен какrejected
. Код разработчика должен перехватить исключение. При использовании оператораawait
рекомендуется заключить вызов метода в инструкциюtry-catch
с обработкой ошибок и ведением журнала. В противном случае в Blazor приложении, работающем над каналом, неисправный код приводит к необработанным исключениям, которое является неустранимым для канала приложения. - InvokeAsync Вызовы должны выполняться в течение определенного периода или другого времени ожидания вызова. Период времени ожидания по умолчанию составляет одну минуту. Время ожидания защищает код от потери сетевого подключения или кода JS, который никогда не возвращает сообщение о завершении. Если время ожидания вызова истекает, полученный элемент System.Threading.Tasks завершается с исключением OperationCanceledException. Перехватите и обработайте исключение с ведением журнала.
Аналогичным образом код JS может инициировать вызовы методов .NET, указанных в атрибуте [JSInvokable]
. Если эти методы .NET создают необработанное исключение:
- Blazor В приложении, работающем над каналом, исключение не рассматривается как неустранимая для канала приложения.
Promise
на стороне JS отклоняется.
Вы можете использовать код обработки ошибок либо на стороне .NET, либо на стороне JS вызова метода.
Дополнительные сведения см. в следующих статьях:
- Вызов функций JavaScript из методов .NET в ASP.NET Core Blazor
- Вызов методов .NET из функций JavaScript в Blazor ASP.NET Core
Предварительная отрисовка
Razor Компоненты по умолчанию предопределяются таким образом, чтобы их отрисовка HTML-разметки возвращалась в рамках первоначального HTTP-запроса пользователя.
В приложении, работающем над каналом Blazor , предварительная отрисовка работает следующим образом:
- Создается новый канал для всех предварительно отрисованных компонентов, которые являются частью одной страницы.
- Создается исходный HTML-код.
- Канал рассматривается как
disconnected
, пока браузер пользователя не установит подключение SignalR к тому же серверу. Если соединение установлено, взаимодействие с каналом возобновляется, а HTML-разметка компонентов обновляется.
Для предварительно подготовленных клиентских компонентов предварительная отрисовка работает следующим образом:
- Создается исходный HTML-код на сервере для всех предварительно отображенных компонентов, которые являются частью одной страницы.
- Компонент делается интерактивным на клиенте после того, как браузер загрузил скомпилированный код приложения и среду выполнения .NET (если она еще не загружена) в фоновом режиме.
Если компонент создает необработанное исключение во время предварительной отрисовки, например во время метода жизненного цикла или в логике отрисовки:
- В приложении, работающем Blazor над каналом, исключение неустранимо для канала. Для предопределенных клиентских компонентов исключение предотвращает отрисовку компонента.
- Исключение вызывается в стеке вызовов из ComponentTagHelper.
В обычных обстоятельствах при сбое предварительной отрисовки продолжение сборки и отрисовки компонента не имеет смысла, так как невозможно отрисовать рабочий компонент.
Чтобы допускать ошибки, которые могут возникнуть во время предварительной отрисовки, логика обработки ошибок должна размещаться внутри компонента, который может вызывать исключения. Используйте инструкции try-catch
с обработкой ошибок и ведением журнала. Вместо того чтобы упаковывать ComponentTagHelper в инструкцию try-catch
, поместите логику обработки ошибок в компонент, отрисовываемый ComponentTagHelper.
Расширенные сценарии
Рекурсивная отрисовка
Компоненты можно вкладывать друг в друга рекурсивно. Это полезно для представления рекурсивных структур данных. Например, компонент TreeNode
может отрисовывать другие компоненты TreeNode
для каждого из дочерних элементов узла.
При рекурсивной отрисовке избегайте шаблонов кодирования, которые приводят к бесконечной рекурсии:
- Не следует рекурсивно отрисовывать структуру данных, содержащую цикл. Например, не следует отрисовывать узел дерева, дочерние элементы которого содержат сами себя.
- Не создавайте цепочку макетов, содержащих цикл. Например, не создавайте макет, макетом которого является он сам.
- Не разрешайте пользователю нарушать инварианты рекурсии (правила) с помощью ввода вредоносных данных или вызовов взаимодействия JavaScript.
Бесконечные циклы во время отрисовки:
- Приводят к тому, что процесс отрисовки будет выполняться бесконечно.
- Эквивалентны созданию незавершенного цикла.
В этих сценариях происходит сбой Blazor и обычно выполняется попытка:
- Использовать столько процессорных ресурсов, сколько разрешено операционной системой, в неограниченном объеме.
- Использовать неограниченный объем памяти. Использование неограниченного объема памяти эквивалентно сценарию, в котором незавершенный цикл добавляет записи в коллекцию при каждой итерации.
Чтобы избежать бесконечного шаблона рекурсии, убедитесь, что рекурсивный код отрисовки содержит подходящие условия остановки.
Пользовательская логика дерева отрисовки
Большинство компонентов Razor реализуются как файлы компонента Razor (.razor
) и компилируются платформой для создания логики, которая работает в RenderTreeBuilder для отрисовки выходных данных. Разработчик может вручную реализовать логику RenderTreeBuilder с помощью процедурного кода C#. Дополнительные сведения см. в разделе «Расширенные сценарии ASP.NET Core Blazor (построение дерева визуализации)».
Предупреждение
Использование выполняемой вручную логики построителя дерева считается сложным и небезопасным сценарием, не рекомендуемым для обычной разработки компонентов.
При написании кода RenderTreeBuilder разработчик должен гарантировать правильность кода. Например, разработчик должен убедиться в том, что:
- Вызовы к OpenElement и CloseElement правильно сбалансированы.
- Атрибуты добавляются только в нужные места.
Неправильная логика построителя деревьев отрисовки вручную может вызвать произвольное неопределенное поведение, включая сбои, приложение или сервер для остановки реагирования и уязвимостей системы безопасности.
Считайте, что выполняемая вручную логика построителя дерева отрисовки имеет тот же уровень сложности и опасности, что и написание кода сборки или инструкций MSIL вручную.
Дополнительные ресурсы
†Применяется к серверным веб-API ASP.NET Core, которые клиентские приложения Blazor используют для ведения журнала.
ASP.NET Core