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

Uwaga

Nie jest to najnowsza wersja tego artykułu. Aby zapoznać się z bieżącą wersją, zapoznaj się z wersją tego artykułu platformy .NET 8.

Ważne

Te informacje odnoszą się do produktu w wersji wstępnej, który może zostać znacząco zmodyfikowany, zanim zostanie wydany komercyjnie. Firma Microsoft nie udziela żadnych gwarancji, jawnych lub domniemanych, w odniesieniu do informacji podanych w tym miejscu.

Aby zapoznać się z bieżącą wersją, zapoznaj się z wersją tego artykułu platformy .NET 8.

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

Blazor Gdy aplikacja nie działa prawidłowo podczas programowania, otrzymuje szczegółowe informacje o błędzie z aplikacji, pomagając w rozwiązywaniu problemów i rozwiązywaniu problemu. 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ą atrybutudata-nosnippet do sygnalizatora przeglądarek, aby nie buforować zawartości interfejsu użytkownika błędu, ale wszystkie wersje Blazor dokumentacji stosują atrybut.

Blazor W aplikacji internetowej dostosuj środowisko w składnikuMainLayout. Ponieważ pomocnik tagów środowiska (na przykład <environment include="Production">...</environment>) nie jest obsługiwany w Razor składnikach, poniższy przykład wprowadza w celu skonfigurowania komunikatów IHostEnvironment o błędach dla różnych środowisk.

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

@inject IHostEnvironment HostEnvironment

Utwórz lub zmodyfikuj znaczniki interfejsu użytkownika błędu 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 W aplikacji dostosuj środowisko w Pages/_Host.cshtml pliku. W poniższym przykładzie użyto pomocnika tagów środowiska do skonfigurowania komunikatów o błędach dla różnych środowisk.

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

Blazor Server W aplikacji dostosuj środowisko w Pages/_Host.cshtml pliku. 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 znaczniki interfejsu użytkownika błędu 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 W aplikacji dostosuj środowisko w wwwroot/index.html pliku:

<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 struktura ma zastosowanie 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 struktura ma zastosowanie display: block do elementu .

Szczegółowe błędy obwodu

Ta sekcja dotyczy Blazor usługi Web Apps działających w 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. W celach programistycznych poufne informacje o błędach obwodu można udostępnić klientowi, włączając szczegółowe błędy.

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 DetailedErrors klucza konfiguracji na true wartość w pliku ustawień środowiska aplikacji Development (appsettings.Development.json). Ponadto ustaw SignalR rejestrowanie po stronie serwera (Microsoft.AspNetCore.SignalR) na debugowanie lub śledzenie w celu uzyskania szczegółowego SignalR rejestrowania.

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życie zmiennej środowiskowej ASPNETCORE_DETAILEDERRORS z wartością true na Development/Staging serwerach środowiskowych 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 Razor po stronie serwera składnika

Ta sekcja dotyczy Blazor usługi Web Apps.

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

Poniższy przykład umożliwia szczegółowe błędy:

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

Ostrzeżenie

Włącz tylko 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 ustawiona 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ługiwane wyjątki.

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 obwodzie.

Razor składniki z włączonym interakcyjnością serwera są stanowe na serwerze. Podczas gdy użytkownicy wchodzą w interakcję z składnikiem na serwerze, utrzymują połączenie z serwerem znanym jako obwód. Obwód przechowuje aktywne wystąpienia składników, a także wiele innych aspektów stanu, takich jak:

  • Najnowsze renderowane dane wyjściowe składników.
  • Bieżący zestaw delegatów obsługi zdarzeń, które mogą być wyzwalane 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 najbardziej nieobsługiwane wyjątki 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 poza tym, które zostały zakończone, które są obwodami dla innych użytkowników lub innych kart przeglądarki, nie mają wpływu. Ten scenariusz jest podobny do aplikacji klasycznej, która ulega awarii. Awaria aplikacji musi zostać ponownie uruchomiona, ale nie ma to wpływu na inne aplikacje.

Struktura kończy 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 uzyskać globalną obsługę wyjątków, zobacz następujące sekcje:

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 wystąpił błąd.
  • Renderuje interfejs użytkownika błędu, gdy zgłaszany jest nieobsługiwany wyjątek.

Aby zdefiniować granicę błędu, użyj ErrorBoundary składnika do opakowania istniejącej zawartości. Aplikacja nadal działa normalnie, ale granica błędu obsługuje nieobsługiwane wyjątki.

<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 usłudze Web Apps z granicą błędu zastosowaną tylko do składnika statycznego MainLayout granica jest aktywna tylko podczas fazy renderowania statycznego po stronie serwera (statycznego SSR). Granica nie aktywuje się tylko dlatego, że składnik dalej w hierarchii składników jest interaktywny. Aby ogólnie włączyć interakcyjność dla MainLayout składnika i reszty składników w dalszej części hierarchii składników, włącz interaktywne renderowanie HeadOutletRoutes wystąpień składników w składniku App (Components/App.razor). W poniższym przykładzie jest wdrażany tryb renderowania Interactive Server (InteractiveServer):

<HeadOutlet @rendermode="InteractiveServer" />

...

<Routes @rendermode="InteractiveServer" />

Jeśli nie chcesz włączać interakcyjności serwera w całej aplikacji ze Routes składnika, umieść granicę błędu w dalszej części hierarchii składników. Na przykład umieść granicę błędu wokół znaczników w poszczególnych składnikach, które umożliwiają interakcyjność, a nie w głównym układzie aplikacji. Ważne pojęcia, które należy wziąć pod uwagę, to to, że wszędzie tam, gdzie znajduje się granica błędu:

  • Jeśli granica błędu nie jest interaktywna, można ją aktywować tylko na serwerze podczas renderowania statycznego. Na przykład granica może aktywować się po wystąpieniu błędu w metodzie cyklu życia składnika.
  • Jeśli granica błędu jest interaktywna, można ją aktywować dla składników renderowanych na serwerze interaktywnym, które opakowuje.

Rozważmy poniższy przykład, w którym Counter składnik zgłasza wyjątek, jeśli liczba zwiększa się w ciągu ostatnich pięciu.

W pliku Counter.razor:

private void IncrementCount()
{
    currentCount++;

    if (currentCount > 5)
    {
        throw new InvalidOperationException("Current count is too big!");
    }
}

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

  • Błąd jest rejestrowany normalnie (System.InvalidOperationException: Current count is too big!).
  • Wyjątek jest obsługiwany przez granicę błędu.
  • Interfejs użytkownika błędu jest renderowany przez granicę błędu z następującym domyślnym komunikatem o błędzie: An error has occurred.

Domyślnie ErrorBoundary składnik renderuje pusty <div> element z klasą CSS pod kątem blazor-error-boundary zawartości błędu. Kolory, tekst i ikona domyślnego interfejsu użytkownika są definiowane przy użyciu arkuszy CSS w arkuszu stylów aplikacji w folderze wwwroot , więc możesz dostosować interfejs użytkownika błędu.

Zmień domyślną zawartość błędu, ustawiając ErrorContent właściwość:

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

Ponieważ granica błędu jest zdefiniowana w układzie w poprzednich przykładach, interfejs użytkownika błędu jest widoczny niezależnie od strony, do której przechodzi użytkownik po wystąpieniu błędu. W większości scenariuszy zalecamy określenie zakresu granic błędów. Jeśli w szerokim zakresie granica błędu, możesz zresetować ją do stanu nieumyślnego w kolejnych zdarzeniach nawigacji stron, wywołując metodę granicy Recover błędu.

W pliku MainLayout.razor:

...

<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 tylko rerenders składnika, 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 usuwa również wyjątek. Gdy składnik jest rerendered, błąd nie jest powtarzany.

Alternatywna globalna obsługa wyjątków

Alternatywą dla używania granic błędów (ErrorBoundary) jest przekazanie niestandardowego składnika błędu jako CascadingValue składników podrzędnych. Zaletą korzystania ze składnika za pomocą wprowadzonej usługi lub niestandardowej implementacji rejestratora jest to, że składnik kaskadowy może renderować zawartość i stosować style CSS w przypadku wystąpienia błędu.

Poniższy Error 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.

Error.razor:

@inject ILogger<Error> Logger

<CascadingValue Value="this">
    @ChildContent
</CascadingValue>

@code {
    [Parameter]
    public RenderFragment? ChildContent { get; set; }

    public void ProcessError(Exception ex)
    {
        Logger.LogError("Error:ProcessError - Type: {Type} Message: {Message}", 
            ex.GetType(), ex.Message);

        // Call StateHasChanged if ProcessError directly participates in 
        // rendering. If ProcessError only logs or records the error,
        // there's no need to call StateHasChanged.
        //StateHasChanged();
    }
}

Uwaga

Aby uzyskać więcej informacji na temat RenderFragmentprogramu , zobacz składniki ASP.NET CoreRazor.

W składniku Routes opakuj Router składnik (<Router>...</Router>) za Error pomocą składnika. Error Umożliwia to składnikowi kaskadowe działanie dowolnej aplikacji, w której Error składnik jest odbierany jako CascadingParameter.

W pliku Routes.razor:

<Error>
    <Router ...>
        ...
    </Router>
</Error>

W składniku App opakuj Router składnik (<Router>...</Router>) za Error pomocą składnika. Error Umożliwia to składnikowi kaskadowe działanie dowolnej aplikacji, w której Error składnik jest odbierany jako CascadingParameter.

W pliku App.razor:

<Error>
    <Router ...>
        ...
    </Router>
</Error>

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

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

    [CascadingParameter]
    public Error? Error { get; set; }
    
  • Wywołaj metodę przetwarzania błędów w dowolnym catch bloku z odpowiednim typem wyjątku. Przykładowy Error składnik oferuje tylko jedną ProcessError 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. W poniższym Counter przykładzie składnika zgłaszany jest wyjątek i uwięziony, gdy liczba jest większa niż pięć:

    @code {
        private int currentCount = 0;
    
        [CascadingParameter]
        public Error? Error { get; set; }
    
        private void IncrementCount()
        {
            try
            {
                currentCount++;
    
                if (currentCount > 5)
                {
                    throw new InvalidOperationException("Current count is over five!");
                }
            }
            catch (Exception ex)
            {
                Error?.ProcessError(ex);
            }
        }
    }
    

Korzystając z poprzedniego składnika z poprzednimi Error zmianami wprowadzonych w Counter składniku, konsola narzędzi deweloperskich przeglądarki wskazuje uwięziony, zarejestrowany błąd:

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

ProcessError Jeśli metoda bezpośrednio uczestniczy w renderowaniu, takich jak wyświetlanie niestandardowego paska komunikatu o błędzie lub zmiana stylów CSS renderowanych elementów, wywołaj StateHasChanged na końcu ProcessErrors metody , aby rerender interfejsu użytkownika.

Ponieważ metody w tej sekcji obsługują błędy z instrukcją try-catch , połączenie aplikacji SignalR między klientem a serwerem nie jest uszkodzone, gdy wystąpi błąd, a obwód pozostaje aktywny. Inne nieobsługiwane wyjątki pozostają krytyczne 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 Error składnik przekazuje się jako CascadingValue składnik podrzędny. 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 za pomocą wprowadzonej usługi lub niestandardowej implementacji rejestratora jest to, że składnik kaskadowy może renderować zawartość i stosować style CSS w przypadku wystąpienia błędu.

Error.razor:

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

<CascadingValue Value="this">
    @ChildContent
</CascadingValue>

@code {
    [Parameter]
    public RenderFragment ChildContent { get; set; }

    public void ProcessError(Exception ex)
    {
        Logger.LogError("Error:ProcessError - Type: {Type} Message: {Message}", 
            ex.GetType(), ex.Message);
    }
}

Uwaga

Aby uzyskać więcej informacji na temat RenderFragmentprogramu , zobacz składniki ASP.NET CoreRazor.

W składniku App opakuj Router składnik za pomocą Error składnika. Error Umożliwia to składnikowi kaskadowe działanie dowolnej aplikacji, w której Error składnik jest odbierany jako CascadingParameter.

App.razor:

<Error>
    <Router ...>
        ...
    </Router>
</Error>

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

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

    [CascadingParameter]
    public Error Error { get; set; }
    
  • Wywołaj metodę przetwarzania błędów w dowolnym catch bloku z odpowiednim typem wyjątku. Przykładowy Error składnik oferuje tylko jedną ProcessError 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)
    {
        Error.ProcessError(ex);
    }
    

Korzystając z poprzedniego przykładowego Error składnika i ProcessError metody, konsola narzędzi deweloperskich przeglądarki wskazuje uwięziony, zarejestrowany błąd:

fail: BlazorSample.Shared.Error[0] Error:ProcessError — typ: System.NullReferenceException Message: Odwołanie do obiektu nie jest ustawione na wystąpienie obiektu.

ProcessError Jeśli metoda bezpośrednio uczestniczy w renderowaniu, takich jak wyświetlanie niestandardowego paska komunikatu o błędzie lub zmiana stylów CSS renderowanych elementów, wywołaj StateHasChanged na końcu ProcessErrors metody , aby rerender interfejsu użytkownika.

Ponieważ metody w tej sekcji obsługują błędy z instrukcją try-catch , Blazor połączenie aplikacji SignalR między klientem a serwerem nie jest uszkodzone, gdy wystąpi błąd, a obwód pozostaje aktywny. Każdy nieobsługiwany wyjątek jest krytyczny 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, wyjątek jest rejestrowany w ILogger wystąpieniach skonfigurowanych w kontenerze usługi. Domyślnie Blazor aplikacje logują się do danych wyjściowych konsoli za pomocą dostawcy rejestrowania konsoli. Rozważ rejestrowanie w lokalizacji na serwerze (lub internetowym interfejsie API zaplecza dla aplikacji po stronie klienta) z dostawcą, który zarządza rozmiarem dziennika i rotacją dzienników. Alternatywnie aplikacja może używać usługi zarządzania wydajnością aplikacji (APM), takiej jak aplikacja systemu Azure Szczegółowe informacje (Azure Monitor).

Uwaga

Funkcje natywnej aplikacji Szczegółowe informacje 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 Support App Szczegółowe informacje in Blazor WASM Client Side (microsoft/Application Szczegółowe informacje-dotnet #2143) oraz Web Analytics and diagnostics (zawiera linki do implementacji społeczności) (dotnet/aspnetcore #5461). W międzyczasie aplikacja po stronie klienta może używać zestawu SDK aplikacji Szczegółowe informacje JavaScript ze współdziałaniem JS w celu rejestrowania błędów bezpośrednio w aplikacji Szczegółowe informacje z aplikacji po stronie klienta.

Podczas programowania w aplikacji działającej Blazor w obwodzie aplikacja zwykle wysyła pełne 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 zdarzenia z 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ą wychwytować i wysyłać informacje o błędach na kliencie do internetowego interfejsu API, który rejestruje informacje o błędzie u dostawcy trwałego rejestrowania.

Jeśli wystąpi nieobsługiwany wyjątek, wyjątek jest rejestrowany w ILogger wystąpieniach skonfigurowanych w kontenerze usługi. Domyślnie Blazor aplikacje logują się do danych wyjściowych konsoli za pomocą dostawcy rejestrowania konsoli. Rozważ rejestrowanie w bardziej trwałej lokalizacji na serwerze, wysyłając informacje o błędach do internetowego interfejsu API zaplecza, który używa dostawcy rejestrowania z zarządzaniem rozmiarem dziennika i rotacją dzienników. Alternatywnie aplikacja internetowego interfejsu API zaplecza może używać usługi Zarządzania wydajnością aplikacji (APM), takiej jak aplikacja systemu Azure Szczegółowe informacje (Azure Monitor)†, do rejestrowania informacji o błędach odbieranych od klientów.

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 zdarzenia z 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:

†Native Application Szczegółowe informacje features to support client-side apps and native Blazor framework support for Google Analytics może stać się dostępny w przyszłych wersjach tych technologii. Aby uzyskać więcej informacji, zobacz Support App Szczegółowe informacje in Blazor WASM Client Side (microsoft/Application Szczegółowe informacje-dotnet #2143) oraz Web Analytics and diagnostics (zawiera linki do implementacji społeczności) (dotnet/aspnetcore #5461). W międzyczasie aplikacja po stronie klienta może używać zestawu SDK aplikacji Szczegółowe informacje JavaScript ze współdziałaniem JS w celu rejestrowania błędów bezpośrednio w aplikacji Szczegółowe informacje z aplikacji po stronie klienta.

‹Dotyczy aplikacji zaplecza interfejsu API sieci Web ASP.NET Core po stronie serwera dla Blazor aplikacji. Aplikacje po stronie klienta wychwytywania i wysyłania informacji o błędach do internetowego interfejsu API, który rejestruje informacje o błędzie u dostawcy 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:

Tworzenie wystąpienia składnika

Podczas Blazor tworzenia wystąpienia 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 wykonanym konstruktorze lub ustawieniu dla dowolnej [Inject] właściwości powoduje nieobsługiwany wyjątek i uniemożliwia utworzenie wystąpienia składnika przez strukturę. Jeśli aplikacja działa w obwodzie, obwód ulegnie awarii. Jeśli logika konstruktora może zgłaszać wyjątki, aplikacja powinna wychwycić wyjątki przy użyciu instrukcji z obsługą try-catch błędów i rejestrowaniem.

Metody cyklu życia

W okresie 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 krytyczny dla obwodu. Aby składniki obsługiwały błędy w metodach cyklu życia, dodaj 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 .
  • Po wykonaniu catch bloku:
    • loadFailed jest ustawiona na truewartość , która 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}"
@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 występuje, gdy @someObject.PropertyName jest obliczany, ale @someObject to null. W przypadku Blazor aplikacji działających w obwodzie nieobsługiwany wyjątek zgłaszany przez logikę renderowania jest krytyczny dla obwodu aplikacji.

Aby zapobiec NullReferenceException logice renderowania, sprawdź null obiekt przed uzyskaniem dostępu do jego elementów członkowskich. W poniższym przykładzie właściwości nie są dostępne, person.Address jeśli person.Address to 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>
}

Powyższy kod zakłada, że person nie nulljest to . Często struktura kodu gwarantuje, że obiekt istnieje w momencie renderowania składnika. W takich przypadkach nie jest konieczne sprawdzenie null w logice renderowania. W poprzednim przykładzie może istnieć gwarancja istnienia, person ponieważ person jest tworzona po utworzeniu wystąpienia składnika, jak pokazano w poniższym przykładzie:

@code {
    private Person person = new();

    ...
}

Procedury obsługi 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

Kod procedury 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, wychwytuje wyjątki przy użyciu instrukcji z obsługą try-catch błędów i rejestrowaniem.

Jeśli program obsługi zdarzeń zgłasza nieobsługiwany wyjątek (na przykład zapytanie bazy danych kończy się niepowodzeniem), które nie jest uwięzione i obsługiwane przez kod dewelopera:

  • Struktura rejestruje wyjątek.
  • W aplikacji działającej Blazor w obwodzie wyjątek jest krytyczny 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 działającej Blazor w obwodzie, wyjątek jest krytyczny dla obwodu aplikacji.

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

Aby uzyskać więcej informacji na temat usuwania składników, zobacz ASP.NET Core component lifecycle (Cykl życia składnika podstawowego ASP.NET).Razor

Międzyoperacyjność w języku JavaScript

IJSRuntime program 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 wywołanie InvokeAsync nie powiedzie się synchronicznie, 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 procedurze obsługi zdarzeń lub cyklu życia składnika nie obsługuje wyjątku w aplikacji działającej Blazor w obwodzie, wynikowy wyjątek jest krytyczny dla obwodu aplikacji.
  • Jeśli wywołanie InvokeAsync nie powiedzie się asynchronicznie, platforma .NET Task zakończy się niepowodzeniem. Wywołanie polecenia może zakończyć się InvokeAsync niepowodzeniem, na przykład dlatego, że JSkod -side zgłasza wyjątek lub zwraca wartość ukończoną Promise 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 Blazor w obwodzie kod, który kończy się niepowodzeniem, powoduje nieobsługiwany wyjątek krytyczny dla obwodu aplikacji.
  • Domyślnie wywołania muszą InvokeAsync zostać ukończone w określonym przedziale czasu lub w przeciwnym razie limit czasu wywołania. Domyślny okres limitu czasu to jedna minuta. Limit czasu chroni kod przed utratą łączności sieciowej lub JS kodu, który nigdy nie wysyła komunikatu ukończenia. Jeśli wywołanie zostanie upłynął, wynik System.Threading.Tasks zakończy się niepowodzeniem z parametrem OperationCanceledException. Pułapka i przetwarzanie wyjątku z rejestrowaniem.

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

  • W aplikacji działającej Blazor w obwodzie wyjątek nie jest traktowany jako krytyczny dla obwodu aplikacji.
  • Strona JS- Promise jest odrzucana.

Istnieje możliwość użycia kodu obsługi błędów po stronie platformy .NET lub JS po stronie wywołania metody.

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

Prerendering

Razor składniki są domyślnie wstępnie obsługiwane, 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 wstępne działanie działa przez:

  • Utworzenie nowego obwodu dla wszystkich wstępnie wstępnie zainstalowanych 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 wstępnie utworzonych składników po stronie klienta prerendering działa przez:

  • 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.
  • Interakcyjny składnik na kliencie po załadowaniu przez przeglądarkę skompilowanego kodu aplikacji i środowiska uruchomieniowego platformy .NET (jeśli jeszcze nie został załadowany) w tle.

Jeśli składnik zgłasza nieobsługiwany wyjątek podczas prerenderingu, na przykład podczas metody cyklu życia lub w logice renderowania:

  • W aplikacji działającej Blazor w obwodzie wyjątek jest krytyczny 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żywanie try-catch instrukcji z obsługą błędów i rejestrowaniem. Zamiast zawijać w ComponentTagHelper instrukcji, umieść logikę try-catch obsługi błędów w składniku ComponentTagHelperrenderowany przez element .

Zaawansowane scenariusze

Renderowanie cykliczne

Składniki mogą być zagnieżdżone rekursywnie. Jest to przydatne do reprezentowania cyklicznych struktur danych. Na przykład TreeNode składnik może renderować więcej TreeNode składników dla każdego elementu podrzędnego 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ą się.
  • Nie twórz łańcucha układów zawierających cykl. Na przykład nie twórz układu, którego układ jest sam.
  • Nie zezwalaj użytkownikowi końcowemu na naruszenie rekursji (reguł) za pośrednictwem złośliwych wpisów danych lub wywołań międzyoperacyjnych języka 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 awaria kończy się niepowodzeniem Blazor i zwykle próbuje wykonać następujące czynności:

  • 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 jest kompilowana przez platformę w celu utworzenia logiki, która działa w RenderTreeBuilder celu renderowania 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 advanced scenarios (renderowanie konstrukcji drzewa).

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 spowodować dowolne niezdefiniowane zachowanie, w tym awarie, zawieszanie się 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

†Applies do zaplecza ASP.NET Core internetowych aplikacji interfejsu API używanych przez aplikacje po stronie Blazor klienta do rejestrowania.