Udostępnij za pomocą


Obsługa błędów w aplikacjach ASP.NET Core Blazor

Uwaga

Nie jest to najnowsza wersja tego artykułu. Aby zapoznać się z aktualną wersją, zobacz artykuł w wersji .NET 10.

Ostrzeżenie

Ta wersja ASP.NET Core nie jest już obsługiwana. Aby uzyskać więcej informacji, zobacz zasady pomocy technicznej platformy .NET i platformy .NET Core. Aby zapoznać się z bieżącym wydaniem, zobacz wersję tego artykułu dla .NET 9.

W tym artykule opisano sposób Blazor zarządzania nieobsługiwanymi wyjątkami oraz tworzenia aplikacji, które wykrywają błędy i obsługują je.

Szczegółowe błędy podczas programowania

Gdy aplikacja nie działa prawidłowo podczas tworzenia, otrzymanie szczegółowych informacji o błędzie z aplikacji pomaga w rozwiązywaniu problemów. Gdy wystąpi błąd, Blazor aplikacje wyświetlają jasnożółty pasek w dolnej części ekranu:

  • Podczas programowania pasek kieruje Cię do konsoli przeglądarki, gdzie można zobaczyć wyjątek.
  • W środowisku produkcyjnym pasek powiadamia użytkownika o wystąpieniu błędu i zaleca odświeżenie przeglądarki.

Interfejs użytkownika dla tego środowiska obsługi błędów jest częścią Blazor szablonów projektów. Nie wszystkie wersje Blazor szablonów projektów używają atrybutu data-nosnippet aby zasygnalizować przeglądarkom, żeby nie przechowywać w pamięci podręcznej zawartości interfejsu użytkownika błędu, ale atrybut stosują wszystkie wersje Blazor dokumentacji.

W elemencie Blazor Web App dostosuj doświadczenie w składniku MainLayout. Ponieważ pomocnik tagów środowiska (na przykład <environment include="Production">...</environment>) nie jest obsługiwany w Razor składnikach, poniższy przykład wstrzykuje IHostEnvironment w celu skonfigurowania komunikatów o błędach dla różnych środowisk.

W górnej części elementu MainLayout.razor:

@inject IHostEnvironment HostEnvironment

Utwórz lub zmodyfikuj znacznik błędu interfejsu użytkownika 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>

W aplikacji Blazor Server dostosuj środowisko w pliku Pages/_Host.cshtml. W poniższym przykładzie użyto Pomocnika Tagów Środowiska do skonfigurowania komunikatów o błędach dla różnych środowisk.

W aplikacji Blazor Server dostosuj środowisko w pliku Pages/_Layout.cshtml. W poniższym przykładzie użyto Pomocnika Tagów Środowiska do skonfigurowania komunikatów o błędach dla różnych środowisk.

W aplikacji Blazor Server dostosuj środowisko w pliku Pages/_Host.cshtml. W poniższym przykładzie użyto Pomocnika Tagów Środowiska do skonfigurowania komunikatów o błędach dla różnych środowisk.

Utwórz lub zmodyfikuj znacznik błędu interfejsu użytkownika 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>

W aplikacji Blazor WebAssembly, dostosuj doświadczenie w pliku 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>

Element blazor-error-ui jest zwykle ukryty ze względu na obecność display: none stylu blazor-error-ui klasy CSS w automatycznie wygenerowanym arkuszu stylów aplikacji. W przypadku wystąpienia błędu framework stosuje display: block do elementu.

Element blazor-error-ui jest zwykle ukryty ze względu na obecność display: none stylu blazor-error-ui klasy CSS w arkuszu stylów witryny w folderze wwwroot/css . W przypadku wystąpienia błędu framework stosuje display: block do elementu.

Szczegółowe błędy obwodu

Ta sekcja dotyczy Blazor Web Appoperacji na obwodzie.

Ta sekcja dotyczy Blazor Server aplikacji.

Błędy po stronie klienta nie zawierają stosu wywołań i nie zawierają szczegółowych informacji na temat przyczyny błędu, ale dzienniki serwera zawierają takie informacje. Na potrzeby rozwoju poufne informacje o błędach obwodu można udostępnić klientowi, włączając szczegółowe informacje o błędach.

Ustaw wartość opcji CircuitOptions.DetailedErrors na true. Aby uzyskać więcej informacji i przykład, zobacz wskazówki dotyczące platformy ASP.NET CoreBlazorSignalR.

Alternatywą dla ustawienia CircuitOptions.DetailedErrors jest ustawienie klucza konfiguracji DetailedErrors na true w pliku ustawień środowiska Development aplikacji (appsettings.Development.json). Ponadto ustaw SignalR rejestrowanie po stronie serwera (Microsoft.AspNetCore.SignalR) na Debug lub Trace aby uzyskać szczegółowe SignalR rejestrowanie.

appsettings.Development.json:

{
  "DetailedErrors": true,
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information",
      "Microsoft.AspNetCore.SignalR": "Debug"
    }
  }
}

DetailedErrors Klucz konfiguracji można również ustawić na true używając zmiennej środowiskowej ASPNETCORE_DETAILEDERRORS z wartością true na serwerach środowiskowych Development/Staging lub w systemie lokalnym.

Ostrzeżenie

Należy zawsze unikać uwidaczniania informacji o błędach klientom w Internecie, co jest zagrożeniem bezpieczeństwa.

Szczegółowe błędy renderowania składnika Razor po stronie serwera

Ta sekcja dotyczy Blazor Web Apps.

Użyj opcji RazorComponentsServiceOptions.DetailedErrors, aby kontrolować tworzenie szczegółowych informacji na temat błędów renderowania po stronie serwera dla składnika Razor. Domyślna wartość to false.

Poniższy przykład umożliwia szczegółowe raportowanie błędów.

builder.Services.AddRazorComponents(options => 
    options.DetailedErrors = builder.Environment.IsDevelopment());

Ostrzeżenie

Tylko włącz szczegółowe błędy w Development środowisku. Szczegółowe błędy mogą zawierać poufne informacje o aplikacji, których złośliwi użytkownicy mogą używać w ataku.

Powyższy przykład zapewnia stopień bezpieczeństwa, ustawiając wartość na podstawie wartości DetailedErrors zwracanej przez IsDevelopment. Gdy aplikacja znajduje się w Development środowisku, DetailedErrors jest ustawione na truewartość. Takie podejście nie jest niezawodne, ponieważ istnieje możliwość hostowania aplikacji produkcyjnej na serwerze publicznym w Development środowisku.

Zarządzanie nieobsługiwanymi wyjątkami w kodzie dewelopera

Aby aplikacja mogła kontynuować działanie po błędzie, aplikacja musi mieć logikę obsługi błędów. W kolejnych sekcjach tego artykułu opisano potencjalne źródła nieobsługiwanych wyjątków.

W środowisku produkcyjnym nie renderuj komunikatów wyjątków struktury ani śladów stosu w interfejsie użytkownika. Renderowanie komunikatów wyjątków lub śladów stosu może:

  • Ujawnianie poufnych informacji użytkownikom końcowym.
  • Pomóż złośliwemu użytkownikowi wykryć słabe strony w aplikacji, która może naruszyć bezpieczeństwo aplikacji, serwera lub sieci.

Nieobsługiwane wyjątki dla obwodów

Ta sekcja dotyczy aplikacji po stronie serwera działających w ramach połączenia.

Razor składników z włączoną interaktywnością serwera utrzymują stan na serwerze. Podczas gdy użytkownicy wchodzą w interakcję ze składnikiem na serwerze, utrzymują połączenie z serwerem, które nazywamy obwodem. Obwód przechowuje aktywne wystąpienia komponentów, a także wiele innych aspektów stanu, takich jak:

  • Najnowsze renderowane dane wyjściowe składników.
  • Bieżący zestaw delegatów do obsługi zdarzeń, które mogą być aktywowane przez zdarzenia po stronie klienta.

Jeśli użytkownik otworzy aplikację na wielu kartach przeglądarki, użytkownik utworzy wiele niezależnych obwodów.

Blazor traktuje większość nieobsługiwanych wyjątków jako krytyczne dla obwodu, w którym występują. Jeśli obwód zostanie przerwany z powodu nieobsługiwanego wyjątku, użytkownik może nadal wchodzić w interakcję z aplikacją, ładując ponownie stronę w celu utworzenia nowego obwodu. Obwody inne niż ten, który został zakończony, czyli obwody dla innych użytkowników lub innych kart przeglądarki, nie są dotknięte. Ten scenariusz jest podobny do aplikacji klasycznej, która ulega awarii. Aplikacja, która uległa awarii, musi zostać ponownie uruchomiona, ale inne aplikacje nie są tym dotknięte.

Platforma zamyka obwód, gdy wystąpi nieobsługiwany wyjątek z następujących powodów:

  • Nieobsługiwany wyjątek często pozostawia obwód w stanie niezdefiniowanym.
  • Normalne działanie aplikacji nie może być gwarantowane po nieobsługiwanym wyjątku.
  • Luki w zabezpieczeniach mogą pojawić się w aplikacji, jeśli obwód będzie nadal w stanie niezdefiniowanym.

Globalna obsługa wyjątków

Aby zapoznać się z podejściami do globalnego obsługi wyjątków, zobacz następujące sekcje:

  • Granice błędów: dotyczy wszystkich Blazor aplikacji.
  • Alternatywna globalna obsługa wyjątków: dotyczy Blazor Serverelementów, Blazor WebAssembly i Blazor Web App (8.0 lub nowszych), które przyjmują globalny tryb renderowania interaktywnego.

Granice błędów

Granice błędów zapewniają wygodne podejście do obsługi wyjątków. Składnik ErrorBoundary :

  • Renderuje jego zawartość podrzędną, gdy nie wystąpił błąd.
  • Wyświetla interfejs użytkownika błędu, gdy nieobsługiwany wyjątek jest zgłaszany przez dowolny składnik w obszarze błędu.

Aby zdefiniować granicę błędu, należy użyć ErrorBoundary komponentu do owinięcia co najmniej jednego innego komponentu. Granica błędów zarządza nieobsługiwanymi wyjątkami zgłaszanymi przez składniki, które obejmuje.

<ErrorBoundary>
    ...
</ErrorBoundary>

Aby zaimplementować granicę błędu w sposób globalny, dodaj granicę wokół zawartości treści głównego układu aplikacji.

W pliku MainLayout.razor:

<article class="content px-4">
    <ErrorBoundary>
        @Body
    </ErrorBoundary>
</article>

W Blazor Web Apps z granicą błędu zastosowaną tylko do statycznego komponentu MainLayout, granica jest aktywna tylko podczas statycznego renderowania po stronie serwera (statyczne SSR). Granica nie aktywuje się tylko dlatego, że składnik dalej w hierarchii składników jest interaktywny.

Nie można zastosować trybu renderowania interakcyjnego MainLayout do składnika, ponieważ parametr składnika Body jest delegatem RenderFragment , który jest dowolnym kodem i nie można go serializować. Aby ogólnie włączyć interakcyjność dla składnika MainLayout i pozostałych składników w dalszej części hierarchii składników, aplikacja musi przyjąć globalny tryb renderowania interaktywnego, stosując go do wystąpień składników HeadOutlet i Routes w składniku głównym aplikacji, który zazwyczaj jest składnikiem App. Poniższy przykład stosuje tryb renderowania interactive server (InteractiveServer) globalnie.

W pliku Components/App.razor:

<HeadOutlet @rendermode="InteractiveServer" />

...

<Routes @rendermode="InteractiveServer" />

Jeśli nie chcesz włączać globalnej interakcyjności, umieść granicę błędu w dół hierarchii składników. Ważne pojęcia, które należy wziąć pod uwagę, to że niezależnie od tego, gdzie znajduje się granica błędu:

  • Jeśli składnik, w którym znajduje się granica błędu, nie jest interakcyjny, granica błędu jest w stanie uaktywnić się tylko na serwerze podczas statycznego routingu SSR. Na przykład granica może uaktywnić się po wystąpieniu błędu w metodzie cyklu życia składnika, ale nie w przypadku zdarzenia wyzwalanego przez interakcyjność użytkownika w składniku, na przykład błąd zgłaszany przez procedurę obsługi kliknięć przycisku.
  • Jeśli składnik, w którym znajduje się granica błędu, jest interaktywny, granica błędu może działać dla opakowywanych przez nią składników interaktywnych.

Uwaga

Powyższe zagadnienia nie są istotne dla aplikacji autonomicznych Blazor WebAssembly , ponieważ renderowanie po stronie klienta (CSR) Blazor WebAssembly aplikacji jest całkowicie interaktywne.

Rozważmy poniższy przykład, w którym wyjątek zgłaszany przez osadzony składnik licznika jest przechwycony przez granicę błędu w składniku Home , który przyjmuje tryb renderowania interaktywnego.

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>

Rozważmy poniższy przykład, w którym wyjątek zgłaszany przez osadzony składnik licznika jest przechwycony przez granicę błędu w składniku 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>

Jeśli nieobsługiwany wyjątek zostanie zgłoszony dla currentCount powyżej pięciu:

  • Błąd jest rejestrowany normalnie (System.InvalidOperationException: Current count is too big!).
  • Wyjątek jest obsługiwany przez granicę błędu.
  • Domyślny interfejs użytkownika błędu jest renderowany przez granicę błędów.

Składnik ErrorBoundary renderuje pusty element <div> przy użyciu klasy CSS blazor-error-boundary dla treści błędu. Kolory, tekst i ikona domyślnego interfejsu użytkownika są definiowane w arkuszu stylów aplikacji w folderze wwwroot , więc możesz dostosować interfejs użytkownika błędu.

Domyślny interfejs użytkownika błędu renderowany przez granicę błędu, która ma czerwone tło, tekst

Aby zmienić domyślną treść błędu

  • Owijaj składniki granicy błędu w właściwości ChildContent.
  • Ustaw właściwość ErrorContent na zawartość błędu.

Poniższy przykład opakowuje EmbeddedCounter komponent i dostarcza niestandardową zawartość błędu:

<ErrorBoundary>
    <ChildContent>
        <EmbeddedCounter />
    </ChildContent>
    <ErrorContent>
        <p class="errorUI">😈 A rotten gremlin got us. Sorry!</p>
    </ErrorContent>
</ErrorBoundary>

W poprzednim przykładzie arkusz stylów aplikacji prawdopodobnie zawiera klasę errorUI CSS do stylu zawartości. Zawartość błędu jest renderowana z właściwości ErrorContent bez użycia elementu na poziomie bloku. Element na poziomie bloku, taki jak dywizja (<div>) lub element akapitu (<p>), może opakowywać znacznik zawartości błędu, ale nie jest to konieczne.

Opcjonalnie użyj kontekstu @context elementu ErrorContent, aby uzyskać dane o błędach.

<ErrorContent>
    @context.HelpLink
</ErrorContent>

Element ErrorContent może również nazwać kontekst. W poniższym przykładzie kontekst ma nazwę exception:

<ErrorContent Context="exception">
    @exception.HelpLink
</ErrorContent>

Ostrzeżenie

Należy zawsze unikać uwidaczniania informacji o błędach klientom w Internecie, co jest zagrożeniem bezpieczeństwa.

Jeśli granica występowania błędu jest zdefiniowana w układzie aplikacji, błędu UI jest widoczny niezależnie od strony, do której użytkownik przechodzi po wystąpieniu błędu. W większości scenariuszy zalecamy określenie zakresu granic błędów. Jeśli szeroko ustalisz granicę błędu, możesz zresetować ją do stanu bezbłędnego przy kolejnych nawigacjach stron, wywołując metodę Recover granicy błędu.

W pliku MainLayout.razor:

  • Dodaj pole ErrorBoundary, aby zarejestrować odwołanie do niego za pomocą dyrektywy atrybutu @ref.
  • W metodzie OnParameterSet cyklu życia można wyzwolić odzyskiwanie w obszarze błędu za pomocą Recover, aby wyczyścić błąd, gdy użytkownik przejdzie do innego składnika.
...

<ErrorBoundary @ref="errorBoundary">
    @Body
</ErrorBoundary>

...

@code {
    private ErrorBoundary? errorBoundary;

    protected override void OnParametersSet()
    {
        errorBoundary?.Recover();
    }
}

Aby uniknąć nieskończonej pętli, w której odzyskiwanie jedynie ponownie renderuje komponent, który ponownie zgłasza błąd, nie należy wywoływać Recover z logiki renderowania. Wywołaj tylko Recover wtedy, gdy:

  • Użytkownik wykonuje gest interfejsu użytkownika, taki jak wybranie przycisku, aby wskazać, że chce ponowić próbę wykonania procedury lub gdy użytkownik przejdzie do nowego składnika.
  • Dodatkowa logika wykonywana również usuwa wyjątek. Gdy składnik jest ponownie renderowany, błąd nie pojawia się ponownie.

Poniższy przykład umożliwia użytkownikowi odzyskanie sprawności po wyjątku za pomocą przycisku:

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

Można również rozszerzyć klasę ErrorBoundary dla przetwarzania niestandardowego, przez nadpisanie OnErrorAsync. Poniższy przykład rejestruje tylko błąd, ale można zaimplementować dowolny kod obsługi błędów. Możesz usunąć wiersz, który zwraca CompletedTask, jeśli twój kod czeka na zadanie asynchroniczne.

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

Powyższy przykład można również zaimplementować jako klasę.

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

Jedną z poprzednich implementacji używanych w składniku:

<CustomErrorBoundary>
    ...
</CustomErrorBoundary>

Alternatywna globalna obsługa wyjątków

Podejście opisane w tej sekcji dotyczy Blazor Server, Blazor WebAssembly oraz Blazor Web App, które przyjmują globalny, interaktywny tryb renderowania (InteractiveServer, InteractiveWebAssembly lub InteractiveAuto). Takie podejście nie działa z komponentami Blazor Web App, które przyjmują tryby renderowania poszczególnych stron/składników ani statyczne renderowanie po stronie serwera (statyczne SSR), ponieważ podejście opiera się na CascadingValue/CascadingParameter, które nie działają na przekroju granic trybu renderowania ani ze składnikami, które przyjmują statyczne SSR.

Alternatywą dla używania granice błędów (ErrorBoundary) jest przekazanie niestandardowego składnika błędu jako CascadingValue dla składników podrzędnych. Zaletą korzystania ze składnika zamiast korzystania z usługi wstrzykniętej lub niestandardowej implementacji rejestratora jest to, że składnik kaskadowy może renderować zawartość i stosować style CSS, gdy wystąpi błąd.

Poniższy ProcessError przykład składnika jedynie rejestruje błędy, ale metody składnika mogą przetwarzać błędy w dowolny sposób wymagany przez aplikację, w tym za pomocą wielu metod przetwarzania błędów.

ProcessError.razor:

@inject ILogger<ProcessError> Logger

<CascadingValue Value="this" IsFixed="true">
    @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();
    }
}

Uwaga

Aby uzyskać więcej informacji na temat , zobacz komponenty ASP.NET Core .

CascadingValue<TValue>.IsFixed służy do wskazywania, że parametr kaskadowy nie zmienia się po zainicjowaniu.

W przypadku korzystania z tego podejścia w elemencie Blazor Web App, otwórz Routes komponent i opakuj komponent Router (<Router>...</Router>) za pomocą komponentu ProcessError. Pozwala to na kaskadowe przekazywanie składnika ProcessError do dowolnego składnika aplikacji, w której składnik ProcessError jest odbierany jako CascadingParameter.

W pliku Routes.razor:

<ProcessError>
    <Router ...>
        ...
    </Router>
</ProcessError>

W przypadku korzystania z tego podejścia w aplikacji Blazor Server lub Blazor WebAssembly otwórz składnik App, opakuj składnik Router (<Router>...</Router>) składnikiem ProcessError. Pozwala to na kaskadowe przekazywanie składnika ProcessError do dowolnego składnika aplikacji, w której składnik ProcessError jest odbierany jako CascadingParameter.

W pliku App.razor:

<ProcessError>
    <Router ...>
        ...
    </Router>
</ProcessError>

Aby przetworzyć błędy w składniku:

  • Wyznacz komponent ProcessError jako CascadingParameter w bloku @code. W przykładowym Counter składniku aplikacji na podstawie Blazor szablonu projektu dodaj następującą ProcessError właściwość:

    [CascadingParameter]
    private ProcessError? ProcessError { get; set; }
    
  • Wywołaj metodę przetwarzania błędów w dowolnym catch bloku z odpowiednim typem wyjątku. Przykładowy ProcessError składnik oferuje tylko jedną LogError metodę, ale składnik przetwarzania błędów może zapewnić dowolną liczbę metod przetwarzania błędów w celu rozwiązania alternatywnych wymagań dotyczących przetwarzania błędów w całej aplikacji. Counter Poniższy przykład bloku komponentu @code zawiera ProcessError parametr kaskadowy i wyłapuje wyjątek do rejestrowania, gdy liczba jest większa niż pięć:

    @code {
        private int currentCount = 0;
    
        [CascadingParameter]
        private 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);
            }
        }
    }
    

Zarejestrowany błąd:

fail: {COMPONENT NAMESPACE}.ProcessError[0]
ProcessError.LogError: System.InvalidOperationException Message: Current count is over five!

Jeśli metoda LogError bezpośrednio uczestniczy w renderowaniu, takie jak wyświetlanie niestandardowego paska komunikatu o błędzie lub zmiana stylów CSS renderowanych elementów, wywołaj StateHasChanged na końcu metody LogError, aby ponownie renderować interfejs użytkownika.

Ponieważ podejścia w tej sekcji obsługują błędy za pomocą instrukcji try-catch, połączenie aplikacji SignalR między klientem a serwerem nie jest przerywane, gdy wystąpi błąd, a obwód pozostaje żywy. Inne nieobsługiwane wyjątki pozostają śmiertelne dla obwodu. Aby uzyskać więcej informacji, zobacz sekcję dotyczącą reagowania obwodu na nieobsługiwane wyjątki.

Aplikacja może używać składnika przetwarzania błędów jako wartości kaskadowej do przetwarzania błędów w scentralizowany sposób.

Następujący składnik ProcessError przekazuje się jako CascadingValue do komponentów podrzędnych. Poniższy przykład jedynie rejestruje błąd, ale metody składnika mogą przetwarzać błędy w dowolny sposób wymagany przez aplikację, w tym za pomocą wielu metod przetwarzania błędów. Zaletą korzystania ze składnika zamiast korzystania z usługi wstrzykniętej lub niestandardowej implementacji rejestratora jest to, że składnik kaskadowy może renderować zawartość i stosować style CSS, gdy wystąpi błąd.

ProcessError.razor:

@using Microsoft.Extensions.Logging
@inject ILogger<ProcessError> Logger

<CascadingValue Value="this" IsFixed="true">
    @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);
    }
}

Uwaga

Aby uzyskać więcej informacji na temat , zobacz komponenty ASP.NET Core .

CascadingValue<TValue>.IsFixed służy do wskazywania, że parametr kaskadowy nie zmienia się po zainicjowaniu.

W składniku App opakuj Router składnik za pomocą ProcessError składnika. Pozwala to na kaskadowe przekazywanie składnika ProcessError do dowolnego składnika aplikacji, w której składnik ProcessError jest odbierany jako CascadingParameter.

App.razor:

<ProcessError>
    <Router ...>
        ...
    </Router>
</ProcessError>

Aby przetworzyć błędy w składniku:

  • Wyznaczanie ProcessError składnika jako elementu CascadingParameter w @code bloku:

    [CascadingParameter]
    private ProcessError ProcessError { get; set; }
    
  • Wywołaj metodę przetwarzania błędów w dowolnym catch bloku z odpowiednim typem wyjątku. Przykładowy ProcessError składnik oferuje tylko jedną LogError metodę, ale składnik przetwarzania błędów może zapewnić dowolną liczbę metod przetwarzania błędów w celu rozwiązania alternatywnych wymagań dotyczących przetwarzania błędów w całej aplikacji.

    try
    {
        ...
    }
    catch (Exception ex)
    {
        ProcessError.LogError(ex);
    }
    

Korzystając z poprzedniego przykładu ProcessError składnika oraz LogError metody, konsola narzędzi deweloperskich przeglądarki wskazuje wychwycony, zapisany błąd.

fail: {COMPONENT NAMESPACE}.Shared.ProcessError[0]
ProcessError.LogError: System.NullReferenceException Message: Object reference not set to an instance of an object.

Jeśli metoda LogError bezpośrednio uczestniczy w renderowaniu, takie jak wyświetlanie niestandardowego paska komunikatu o błędzie lub zmiana stylów CSS renderowanych elementów, wywołaj StateHasChanged na końcu metody LogError, aby ponownie renderować interfejs użytkownika.

Ponieważ podejścia w tej sekcji obsługują błędy za pomocą instrukcji try-catch, połączenie aplikacji Blazor między klientem a serwerem nie zostaje zerwane, gdy wystąpi błąd, a obwód pozostaje aktywny. Każdy nieobsługiwany wyjątek jest śmiertelny dla obwodu. Aby uzyskać więcej informacji, zobacz sekcję dotyczącą reagowania obwodu na nieobsługiwane wyjątki.

Rejestrowanie błędów z trwałym dostawcą

Jeśli wystąpi nieobsługiwany wyjątek, jest on rejestrowany w wystąpieniach ILogger, które są skonfigurowane w kontenerze usługi. Blazor aplikacje rejestrują dane wyjściowe konsoli za pomocą dostawcy rejestrowania konsoli. Rozważ zapisywanie logów w lokalizacji na serwerze (lub internetowym interfejsie API zaplecza dla aplikacji po stronie klienta) z dostawcą, który zarządza rozmiarem dzienników i ich rotacją. Alternatywnie aplikacja może używać usługi zarządzania wydajnością aplikacji (APM), takiej jak aplikacja systemu Azure Insights (Azure Monitor).

Uwaga

Natywne funkcje usługi Application Insights do obsługi aplikacji po stronie klienta i natywnej Blazor obsługi platformy dla usługi Google Analytics mogą stać się dostępne w przyszłych wersjach tych technologii. Aby uzyskać więcej informacji, zobacz Blazor oraz microsoft/ApplicationInsights-dotnet W międzyczasie aplikacja kliencka może korzystać z zestawu Application Insights JavaScript SDK wraz z JS interop, aby rejestrować błędy bezpośrednio w usłudze Application Insights.

Podczas tworzenia w aplikacji działającej w obwodzie Blazor aplikacja zwykle wysyła wszystkie szczegóły wyjątków do konsoli przeglądarki, aby ułatwić debugowanie. W środowisku produkcyjnym szczegółowe błędy nie są wysyłane do klientów, ale pełne szczegóły wyjątku są rejestrowane na serwerze.

Musisz zdecydować, które zdarzenia mają być rejestrowane, oraz poziom ważności zarejestrowanych zdarzeń. Wrogi użytkownicy mogą celowo wyzwalać błędy. Na przykład, nie rejestruj incydentu z powodu błędu, w którym nieznany ProductId jest podany w adresie URL składnika, który wyświetla szczegóły produktu. Nie wszystkie błędy powinny być traktowane jako zdarzenia rejestrowania.

Aby uzyskać więcej informacji, zobacz następujące artykuły:

‹Dotyczy aplikacji po stronie Blazor serwera i innych aplikacji po stronie serwera ASP.NET Core, które są aplikacjami zaplecza internetowego interfejsu API dla programu Blazor. Aplikacje po stronie klienta mogą wychwytywać i wysyłać informacje o błędach do webowego interfejsu API, który rejestruje informacje o błędzie u dostawcy trwałego rejestrowania.

Jeśli wystąpi nieobsługiwany wyjątek, jest on rejestrowany w wystąpieniach ILogger, które są skonfigurowane w kontenerze usługi. Blazor aplikacje rejestrują dane wyjściowe konsoli za pomocą dostawcy rejestrowania konsoli. Rozważ rejestrowanie w bardziej trwałej lokalizacji na serwerze, wysyłając informacje o błędach do interfejsu API zaplecza, który używa dostawcy logowania z zarządzaniem rozmiarem dziennika i rotacją dzienników. Alternatywnie, aplikacja internetowego interfejsu API zaplecza może używać usługi Application Performance Management (APM), takiej jak Azure Application Insights (Azure Monitor)†, do rejestrowania odbieranych od klientów informacji o błędach.

Musisz zdecydować, które zdarzenia mają być rejestrowane, oraz poziom ważności zarejestrowanych zdarzeń. Wrogi użytkownicy mogą celowo wyzwalać błędy. Na przykład, nie rejestruj incydentu z powodu błędu, w którym nieznany ProductId jest podany w adresie URL składnika, który wyświetla szczegóły produktu. Nie wszystkie błędy powinny być traktowane jako zdarzenia rejestrowania.

Aby uzyskać więcej informacji, zobacz następujące artykuły:

Natywne funkcje Application Insights wspierające aplikacje po stronie klienta oraz natywne wsparcie platformy Blazor mogą być dostępne w przyszłych wersjach tych technologii. Aby uzyskać więcej informacji, zobacz Blazor oraz microsoft/ApplicationInsights-dotnet W międzyczasie aplikacja kliencka może korzystać z zestawu Application Insights JavaScript SDK wraz z JS interop, aby rejestrować błędy bezpośrednio w usłudze Application Insights.

Dotyczy aplikacji ASP.NET Core API sieci Web po stronie serwera dla aplikacji Blazor. Aplikacje po stronie klienta wychwytują i wysyłają informacje o błędach do webowego interfejsu API, który rejestruje te informacje o błędach w systemie trwałego rejestrowania.

Miejsca, w których mogą wystąpić błędy

Kod platformy i aplikacji może wyzwalać nieobsługiwane wyjątki w dowolnej z następujących lokalizacji, które zostały opisane w poniższych sekcjach tego artykułu:

Instancjowanie składnika

Gdy Blazor tworzy wystąpienie składnika:

  • Wywoływany jest konstruktor składnika.
  • Konstruktory usług DI dostarczonych do konstruktora składnika za pośrednictwem @inject dyrektywy lub atrybutu [Inject] są wywoływane.

Błąd w uruchomionym konstruktorze lub metodzie ustawiającej dla dowolnej [Inject] właściwości powoduje nieobsługiwany wyjątek i uniemożliwia frameworkowi utworzenie instancji komponentu. Jeśli aplikacja działa w oparciu o obwód, obwód przestaje działać. Jeśli logika konstruktora może zgłaszać wyjątki, aplikacja powinna wychwytywać te wyjątki, korzystając z instrukcji try-catch z obsługą błędów i rejestrowaniem.

Metody cyklu życia

Podczas istnienia składnika Blazor wywołuje metody cyklu życia. Jeśli jakakolwiek metoda cyklu życia zgłasza wyjątek, synchronicznie lub asynchronicznie, wyjątek jest katastrofalny dla działania obwodu. Aby składniki mogły obsługiwać błędy w metodach cyklu życia, wprowadź logikę obsługi błędów.

W poniższym przykładzie, w którym OnParametersSetAsync wywołuje metodę w celu uzyskania produktu:

  • Wyjątek zgłaszany w metodzie ProductRepository.GetProductByIdAsync jest obsługiwany przez instrukcję try-catch .
  • Gdy blok catch zostanie wykonany:
    • loadFailed jest ustawione na true, które służy do wyświetlania użytkownikowi komunikatu o błędzie.
    • Błąd jest rejestrowany.
@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);
    }
}

Logika renderowania

Znacznik deklaratywny w Razor pliku składnika (.razor) jest kompilowany w metodzie języka C# o nazwie BuildRenderTree. Gdy składnik renderuje, BuildRenderTree wykonuje i tworzy strukturę danych opisującą elementy, tekst i składniki podrzędne renderowanego składnika.

Logika renderowania może zgłosić wyjątek. Przykład tego scenariusza pojawia się, gdy @someObject.PropertyName jest oceniany, ale @someObject jawi się jako null. W przypadku Blazor aplikacji działających w obwodzie nieobsługiwany wyjątek rzucany przez logikę renderowania jest śmiertelny dla obwodu aplikacji.

Aby zapobiec problemowi NullReferenceException w logice renderowania, sprawdź, czy obiekt null istnieje przed uzyskaniem dostępu do jego elementów. W poniższym przykładzie, jeśli person.Address jest person.Address, właściwości null nie są używane.

@if (person.Address != null)
{
    <div>@person.Address.Line1</div>
    <div>@person.Address.Line2</div>
    <div>@person.Address.City</div>
    <div>@person.Address.Country</div>
}

Powyższy kod zakłada, że person nie jest null. Często struktura kodu gwarantuje, że obiekt istnieje w momencie renderowania składnika. W takich przypadkach nie jest konieczne sprawdzanie null w logice renderowania. W poprzednim przykładzie person może być gwarantowane istnienie, ponieważ person jest tworzona przy instancjonowaniu komponentu, jak pokazano w poniższym przykładzie:

@code {
    private Person person = new();

    ...
}

Obsługiwacze zdarzeń

Kod po stronie klienta wyzwala wywołania kodu języka C#, gdy programy obsługi zdarzeń są tworzone przy użyciu:

  • @onclick
  • @onchange
  • Inne @on... atrybuty
  • @bind

Procedura obsługi zdarzeń może zgłosić nieobsługiwany wyjątek w tych scenariuszach.

Jeśli aplikacja wywołuje kod, który może zakończyć się niepowodzeniem ze względów zewnętrznych, przechwytuj wyjątki za pomocą instrukcji try-catch z obsługą błędów i rejestrowaniem.

Jeśli obsługa zdarzeń zgłasza nieobsługiwany wyjątek (na przykład, gdy zapytanie bazy danych kończy się niepowodzeniem), który nie jest przechwycony i obsłużony przez kod programisty:

  • Struktura rejestruje wyjątek.
  • W aplikacji działającej Blazor w obwodzie, wyjątek jest śmiertelny dla obwodu aplikacji.

Usuwanie składników

Składnik może zostać usunięty z interfejsu użytkownika, na przykład dlatego, że użytkownik przeszedł do innej strony. Gdy składnik implementujący System.IDisposable zostanie usunięty z interfejsu użytkownika, struktura wywołuje metodę składnika Dispose .

Jeśli metoda składnika Dispose zgłasza nieobsługiwany wyjątek w aplikacji operującej w obwodzie Blazor, wyjątek ten jest krytyczny dla obwodu aplikacji.

Jeśli logika usuwania może zgłaszać wyjątki, aplikacja powinna wychwytywać wyjątki za pomocą instrukcji try-catch z obsługą błędów i rejestrowaniem.

Aby uzyskać więcej informacji na temat usuwania komponentów, zobacz ASP.NET Core Razor komponentów.

Międzyoperacyjność w języku JavaScript

Framework IJSRuntime jest zarejestrowany przez platformę Blazor. IJSRuntime.InvokeAsync Umożliwia programowi .NET wykonywanie wywołań asynchronicznych do środowiska uruchomieniowego JavaScript (JS) w przeglądarce użytkownika.

Następujące warunki dotyczą obsługi błędów za pomocą polecenia InvokeAsync:

  • Jeśli podczas wywoływania InvokeAsync dojdzie do synchronicznego niepowodzenia, wystąpi wyjątek platformy .NET. Wywołanie polecenia może zakończyć się InvokeAsync niepowodzeniem, na przykład dlatego, że podanych argumentów nie można serializować. Kod dewelopera musi przechwycić wyjątek. Jeśli kod aplikacji w obsłudze zdarzeń lub metodzie cyklu życia składnika nie obsługuje wyjątku w aplikacji działającej w ramach obwodu, wynikowy wyjątek jest fatalny dla obwodu aplikacji.
  • Jeśli wywołanie InvokeAsync nie powiedzie się w trybie asynchronicznym, .NET Task zakończy działanie z niepowodzeniem. Wywołanie InvokeAsync może zakończyć się niepowodzeniem, na przykład dlatego, że kod po stronie JS zgłasza wyjątek lub zwraca Promise ukończony jako rejected. Kod dewelopera musi przechwycić wyjątek. Jeśli używasz await operatora, rozważ zawijanie wywołania metody w instrukcji z obsługą try-catch błędów i rejestrowaniem. W przeciwnym razie w aplikacji działającej na obwodzie Blazor kod, który kończy się niepowodzeniem, powoduje nieobsługiwany wyjątek, który jest krytyczny dla obwodu aplikacji.
  • Wywołania do InvokeAsync muszą zostać ukończone w określonym przedziale czasu, w przeciwnym razie zostanie przekroczony limit czasu. Domyślny okres limitu wynosi jedną minutę. Ograniczenie czasu chroni kod przed utratą łączności sieciowej lub JS kodem, który nigdy nie wysyła komunikatu o ukończeniu. Jeśli wywołanie przekroczy limit czasu, wynik System.Threading.Tasks zakończy się niepowodzeniem z parametrem OperationCanceledException. Wychwycenie i przetwarzanie wyjątku oraz jego rejestrowanie.

Podobnie, kod może rozpoczynać wywołania metod platformy .NET wskazywanych przez atrybut . Jeśli te metody .NET zgłaszają nieobsługiwany wyjątek:

  • W aplikacji Blazor, która działa w ramach obwodu, wyjątek nie jest traktowany jako krytyczny dla tego obwodu.
  • Strona JSPromise jest odrzucona.

Możesz użyć kodu obsługi błędów po stronie platformy .NET lub po stronie metody wywołującej.

Aby uzyskać więcej informacji, zobacz następujące artykuły:

Wstępne renderowanie

Razor składniki są domyślnie wstępnie renderowane, dzięki czemu ich renderowane znaczniki HTML są zwracane w ramach początkowego żądania HTTP użytkownika.

W aplikacji działającej Blazor na obwodzie prerendering działa poprzez:

  • Utworzenie nowego układu dla wszystkich wstępnie renderowanych składników, które są częścią tej samej strony.
  • Generowanie początkowego kodu HTML.
  • Traktowanie obwodu jako disconnected do momentu nawiązania połączenia z powrotem przez przeglądarkę SignalR użytkownika z tym samym serwerem. Po nawiązaniu połączenia interakcyjność w obwodzie jest wznawiana, a znaczniki HTML składników są aktualizowane.

W przypadku prerenderowanych komponentów po stronie klienta, prerendering działa w następujący sposób:

  • Generowanie początkowego kodu HTML na serwerze dla wszystkich wstępnie wstępnie utworzonych składników, które są częścią tej samej strony.
  • W tle czynienie składnika interaktywnym po stronie klienta odbywa się po załadowaniu przez przeglądarkę skompilowanego kodu aplikacji oraz środowiska uruchomieniowego .NET (jeśli jeszcze nie zostało załadowane).

Jeśli komponent wyrzuca nieobsługiwany wyjątek podczas prerenderingu, na przykład w metodzie cyklu życia lub w logice renderowania:

  • W aplikacji działającej Blazor w obwodzie wyjątek jest fatalny dla obwodu. W przypadku wstępnie utworzonych składników po stronie klienta wyjątek uniemożliwia renderowanie składnika.
  • Wyjątek jest zgłaszany w stosie wywołań z obiektu ComponentTagHelper.

W normalnych okolicznościach, gdy wstępne przetwarzanie nie powiedzie się, kontynuowanie kompilowania i renderowania składnika nie ma sensu, ponieważ nie można renderować składnika roboczego.

Aby tolerować błędy, które mogą wystąpić podczas prerenderingu, logika obsługi błędów musi zostać umieszczona wewnątrz składnika, który może zgłaszać wyjątki. Używaj try-catch instrukcji z obsługą błędów i rejestrowaniem. Zamiast umieszczać ComponentTagHelper w instrukcji try-catch, umieść logikę obsługi błędów w składniku renderowanym przez ComponentTagHelper.

Zaawansowane scenariusze

Renderowanie cykliczne

Składniki mogą być zagnieżdżane rekurencyjnie. Jest to przydatne do reprezentowania cyklicznych struktur danych. Na przykład komponent TreeNode może renderować więcej komponentów TreeNode dla każdego z podrzędnych elementów węzła.

Podczas renderowania cyklicznego unikaj wzorców kodowania, które powodują nieskończoną rekursję:

  • Nie rekursywnie renderuj struktury danych, która zawiera cykl. Na przykład nie renderuj węzła drzewa, którego elementy podrzędne zawierają jego samego.
  • Nie twórz łańcucha układów zawierających cykl. Na przykład nie twórz układu, którego struktura się powtarza.
  • Nie zezwalaj użytkownikowi końcowemu na naruszenie inwariantów rekursji (zasad) za pośrednictwem złośliwych danych wprowadzanych lub wywołań międzyoperacyjnych JavaScript.

Pętle nieskończone podczas renderowania:

  • Powoduje, że proces renderowania będzie kontynuowany na zawsze.
  • Jest odpowiednikiem tworzenia nieokreślonej pętli.

W tych scenariuszach Blazor ulega awarii i zazwyczaj próbuje:

  • Zużywaj tyle czasu procesora CPU, ile jest dozwolone przez system operacyjny, na czas nieokreślony.
  • Zużywa nieograniczoną ilość pamięci. Korzystanie z nieograniczonej ilości pamięci jest równoważne scenariuszowi, w którym nieokreślona pętla dodaje wpisy do kolekcji w każdej iteracji.

Aby uniknąć nieskończonych wzorców rekursji, upewnij się, że kod renderowania cyklicznego zawiera odpowiednie warunki zatrzymywania.

Niestandardowa logika drzewa renderowania

Większość Razor składników jest implementowana jako Razor pliki składników (.razor) i kompilowana przez platformę, aby tworzyć logikę działającą na RenderTreeBuilder w celu renderowania ich danych wyjściowych. Deweloper może jednak ręcznie zaimplementować RenderTreeBuilder logikę przy użyciu kodu proceduralnego języka C#. Aby uzyskać więcej informacji, zobacz ASP.NET Core Blazor zaawansowane scenariusze (konstrukcja drzewa renderującego).

Ostrzeżenie

Korzystanie z ręcznej logiki konstruktora drzewa renderowania jest uznawane za zaawansowany i niebezpieczny scenariusz, który nie jest zalecany w przypadku ogólnego tworzenia składników.

Jeśli RenderTreeBuilder kod jest napisany, deweloper musi zagwarantować poprawność kodu. Na przykład deweloper musi upewnić się, że:

  • Wywołania do OpenElement i CloseElement są prawidłowo wyważone.
  • Atrybuty są dodawane tylko w odpowiednich miejscach.

Niepoprawna ręczna logika konstruktora drzewa renderowania może powodować dowolne nieokreślone działania, w tym awarie, zawieszenie aplikacji lub serwera oraz luki w zabezpieczeniach.

Rozważ ręczne renderowanie logiki konstruktora drzewa na tym samym poziomie złożoności i z tym samym poziomem zagrożenia co pisanie kodu zestawu lub instrukcji języka Microsoft Intermediate Language (MSIL) ręcznie.

Dodatkowe zasoby

Dotyczy zaplecza aplikacji internetowych ASP.NET Core API, które są używane przez aplikacje po stronie Blazor klienta do rejestrowania.