Sdílet prostřednictvím


Zpracování chyb v aplikacích ASP.NET Core Blazor

Poznámka:

Toto není nejnovější verze tohoto článku. Aktuální verzi najdete ve verzi .NET 8 tohoto článku.

Upozorňující

Tato verze ASP.NET Core se už nepodporuje. Další informace najdete v tématu .NET a .NET Core Zásady podpory. Aktuální verzi najdete ve verzi .NET 8 tohoto článku.

Důležité

Tyto informace se týkají předběžného vydání produktu, který může být podstatně změněn před komerčním vydáním. Microsoft neposkytuje žádné záruky, výslovné ani předpokládané, týkající se zde uváděných informací.

Aktuální verzi najdete ve verzi .NET 8 tohoto článku.

Tento článek popisuje, jak spravovat neošetřené výjimky a jak Blazor vyvíjet aplikace, které detekují a zpracovávají chyby.

Podrobné chyby během vývoje

Blazor Pokud aplikace během vývoje nefunguje správně, pomůže vám při řešení potíží a opravě problému získat podrobné informace o chybách. Když dojde k chybě, Blazor aplikace zobrazí světle žlutý pruh v dolní části obrazovky:

  • Během vývoje vás panel přesměruje na konzolu prohlížeče, kde se zobrazí výjimka.
  • V produkčním prostředí panel upozorní uživatele, že došlo k chybě, a doporučí aktualizaci prohlížeče.

Uživatelské rozhraní pro toto zpracování chyb je součástí Blazor šablon projektů. Ne všechny verze Blazor šablon projektů používají data-nosnippet atribut k signálu prohlížečům, aby neukázely obsah uživatelského rozhraní chyby, ale všechny verze Blazor dokumentace tento atribut používají.

Blazor Ve webové aplikaci přizpůsobte prostředí komponentyMainLayout. Protože pomocné rutiny značek prostředí (například<environment include="Production">...</environment>) nejsou v Razor komponentách podporované, následující příklad vloží IHostEnvironment do konfigurace chybových zpráv pro různá prostředí.

V horní části :MainLayout.razor

@inject IHostEnvironment HostEnvironment

Vytvořte nebo upravte Blazor kód uživatelského rozhraní chyby:

<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 V aplikaci upravte prostředí v Pages/_Host.cshtml souboru. Následující příklad používá pomocníka značky prostředí ke konfiguraci chybových zpráv pro různá prostředí.

Blazor Server V aplikaci upravte prostředí v Pages/_Layout.cshtml souboru. Následující příklad používá pomocníka značky prostředí ke konfiguraci chybových zpráv pro různá prostředí.

Blazor Server V aplikaci upravte prostředí v Pages/_Host.cshtml souboru. Následující příklad používá pomocníka značky prostředí ke konfiguraci chybových zpráv pro různá prostředí.

Vytvořte nebo upravte Blazor kód uživatelského rozhraní chyby:

<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 V aplikaci přizpůsobte prostředí v wwwroot/index.html souboru:

<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 je obvykle skrytý z důvodu přítomnosti display: none stylu blazor-error-ui třídy CSS v automaticky generované šabloně stylů aplikace. Pokud dojde k chybě, architektura se vztahuje display: block na prvek.

Element blazor-error-ui je obvykle skrytý kvůli přítomnosti display: none stylu blazor-error-ui třídy CSS v šabloně stylů webu ve wwwroot/css složce. Pokud dojde k chybě, architektura se vztahuje display: block na prvek.

Podrobné chyby okruhu

Tato část se vztahuje na Blazor webové aplikace provozující přes okruh.

Tato část se týká Blazor Server aplikací.

Chyby na straně klienta nezahrnují zásobník volání a neposkytují podrobnosti o příčině chyby, ale protokoly serveru tyto informace obsahují. Pro účely vývoje mohou být informace o chybách citlivého okruhu zpřístupněny klientovi povolením podrobných chyb.

Nastavte CircuitOptions.DetailedErrors na hodnotu true. Další informace a příklad najdete v ASP.NET BlazorSignalR základních doprovodných materiálech.

Alternativou k nastavení CircuitOptions.DetailedErrors je nastavení konfiguračního DetailedErrors klíče v true souboru nastavení prostředí aplikace Development (appsettings.Development.json). Kromě toho nastavte SignalR protokolování na straně serveru (Microsoft.AspNetCore.SignalR) na ladění nebo trasování pro podrobné SignalR protokolování.

appsettings.Development.json:

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

Konfigurační DetailedErrors klíč lze také nastavit tak, aby true používal ASPNETCORE_DETAILEDERRORS proměnnou prostředí s hodnotou true na Development/Staging serverech prostředí nebo v místním systému.

Upozorňující

Vždy se vyhněte zveřejnění informací o chybách klientům na internetu, což je bezpečnostní riziko.

Podrobné chyby pro Razor vykreslování na straně serveru komponent

Tato část platí pro Blazor Web Apps.

RazorComponentsServiceOptions.DetailedErrors Pomocí možnosti můžete řídit vytváření podrobných informací o chybách při Razor vykreslování na straně serveru. Výchozí hodnota je false.

Následující příklad umožňuje podrobné chyby:

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

Upozorňující

Povolte v prostředí jenom podrobné chyby Development . Podrobné chyby můžou obsahovat citlivé informace o aplikaci, kterou můžou uživatelé se zlými úmysly použít při útoku.

Předchozí příklad poskytuje stupeň bezpečnosti nastavením hodnoty DetailedErrors na základě hodnoty vrácené IsDevelopment. Když je aplikace v Development prostředí, DetailedErrors je nastavená na truehodnotu . Tento přístup není hloupý, protože je možné hostovat produkční aplikaci na veřejném serveru v Development prostředí.

Správa neošetřených výjimek v kódu vývojáře

Aby aplikace pokračovala po chybě, musí mít aplikace logiku zpracování chyb. Další části tohoto článku popisují potenciální zdroje neošetřených výjimek.

V produkčním prostředí nevykreslujte zprávy o výjimce architektury ani trasování zásobníku v uživatelském rozhraní. Vykreslování zpráv výjimek nebo trasování zásobníku může:

  • Zveřejnit citlivé informace koncovým uživatelům.
  • Pomozte uživateli se zlými úmysly zjistit slabá místa v aplikaci, která může ohrozit zabezpečení aplikace, serveru nebo sítě.

Neošetřené výjimky pro okruhy

Tato část se týká aplikací na straně serveru, které fungují přes okruh.

Razor komponenty s povolenou interaktivitou serveru jsou na serveru stavové. Zatímco uživatelé pracují s komponentou na serveru, udržují připojení k serveru známému jako okruh. Okruh obsahuje aktivní instance komponent a mnoho dalších aspektů stavu, například:

  • Nejnovější vykreslený výstup komponent.
  • Aktuální sada delegátů zpracování událostí, které by mohly být aktivovány událostmi na straně klienta.

Pokud uživatel aplikaci otevře na několika kartách prohlížeče, uživatel vytvoří více nezávislých okruhů.

Blazor považuje většinu neošetřených výjimek za závažnou pro okruh, ve kterém k nim dochází. Pokud se okruh ukončí kvůli neošetřené výjimce, uživatel může s aplikací dál pracovat, a to tak, že stránku znovu načte a vytvoří nový okruh. Okruhy mimo okruhy ukončené, což jsou okruhy pro jiné uživatele nebo jiné karty prohlížeče, to neovlivní. Tento scénář je podobný desktopové aplikaci, která se chybově ukončí. Aplikace s chybovým ukončením se musí restartovat, ale jiné aplikace to neovlivní.

Architektura ukončí okruh, pokud dojde k neošetřené výjimce z následujících důvodů:

  • Neošetřená výjimka často opouští okruh v nedefinovaném stavu.
  • Normální provoz aplikace není možné zaručit po neošetřené výjimce.
  • Pokud okruh pokračuje v nedefinovaném stavu, můžou se v aplikaci objevit ohrožení zabezpečení.

Globální zpracování výjimek

Přístupy k globálnímu zpracování výjimek najdete v následujících částech:

  • Hranice chyb: Platí pro všechny Blazor aplikace.
  • Alternativní globální zpracování výjimek: Platí pro Blazor Server, Blazor WebAssemblya Blazor Web Apps (8.0 nebo novější), které přijímají globální interaktivní režim vykreslování.

Hranice chyb

Hranice chyb poskytují pohodlný přístup pro zpracování výjimek. Komponenta ErrorBoundary :

  • Vykreslí podřízený obsah v případě, že nedošlo k chybě.
  • Vykreslí uživatelské rozhraní chyby, pokud je neošetřená výjimka vyvolán libovolnou komponentou v rámci hranice chyby.

Chcete-li definovat hranici chyby, použijte komponentu ErrorBoundary k zabalení jedné nebo více dalších komponent. Hranice chyby spravuje neošetřené výjimky vyvolané komponentami, které zabalí.

<ErrorBoundary>
    ...
</ErrorBoundary>

Pokud chcete implementovat hranici chyb v globálním měřítku, přidejte hranici kolem základního obsahu hlavního rozložení aplikace.

V MainLayout.razor:

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

Ve Blazor službě Web Apps s hranici chyby použitou pouze pro statickou MainLayout komponentu je hranice aktivní pouze při vykreslování na straně statického serveru (statické SSR). Hranice se neaktivuje, protože komponenta dále v hierarchii komponent je interaktivní.

Interaktivní režim vykreslení nelze u MainLayout komponenty použít, protože parametr komponenty Body je RenderFragment delegát, což je libovolný kód a nedá se serializovat. Aby byla interaktivita pro komponentu MainLayout a zbytek komponent dál mimo hierarchii komponent, musí aplikace přijmout globální interaktivní režim vykreslování tím, že použije interaktivní režim vykreslování na HeadOutlet instance a Routes instance komponent v kořenové komponentě aplikace, což je obvykle komponenta App . Následující příklad přijímá globálně režim vykreslování Interactive Server (InteractiveServer).

V Components/App.razor:

<HeadOutlet @rendermode="InteractiveServer" />

...

<Routes @rendermode="InteractiveServer" />

Pokud nechcete povolit globální interaktivitu, umístěte hranici chyby dále dolů v hierarchii komponent. Důležité koncepty, které je potřeba mít na paměti, jsou, že všude, kde je umístěna hranice chyby:

  • Pokud komponenta, kde je umístěna hranice chyby, není interaktivní, je hranice chyby schopna aktivovat pouze na serveru během statického SSR. Hranice se například může aktivovat, když dojde k chybě v metodě životního cyklu komponenty, ale ne pro událost aktivovanou interaktivitou uživatele v rámci komponenty, například chybu vyvolanou obslužnou rutinou kliknutí na tlačítko.
  • Pokud je komponenta, ve které je umístěna hranice chyby, interaktivní, je hranice chyby schopna aktivovat interaktivní komponenty, které zabalí.

Poznámka:

Předchozí aspekty nejsou relevantní pro samostatné Blazor WebAssembly aplikace, protože vykreslování aplikace na straně klienta (CSR) Blazor WebAssembly je zcela interaktivní.

Podívejte se na následující příklad, kdy výjimka vyvolaná vloženou komponentou čítače je zachycena hranici chyby v Home komponentě, která přijímá interaktivní režim vykreslování.

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>

Podívejte se na následující příklad, kde výjimka vyvolaná vloženou komponentou čítače je zachycena hranici chyby v komponentě 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>

Pokud je neošetřená výjimka vyvolán pro currentCount více než pět:

  • Chyba se protokoluje normálně (System.InvalidOperationException: Current count is too big!).
  • Výjimku zpracovává hranice chyby.
  • Výchozí uživatelské rozhraní chyby se vykreslí podle hranice chyby.

Ve výchozím nastavení komponenta ErrorBoundary vykresluje prázdný <div> prvek pomocí blazor-error-boundary třídy CSS pro jeho chybový obsah. Barvy, text a ikona výchozího uživatelského rozhraní jsou definované v šabloně stylů aplikace ve wwwroot složce, takže můžete uživatelské rozhraní chyby přizpůsobit.

Výchozí uživatelské rozhraní chyby vykreslené hranicí chyby, která má červené pozadí, text

Změna výchozího obsahu chyby:

Následující příklad zabalí komponentu EmbeddedCounter a dodává vlastní obsah chyb:

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

V předchozím příkladu pravděpodobně šablona stylů aplikace obsahuje errorUI třídu CSS pro styl obsahu. Obsah chyby se vykreslí z ErrorContent vlastnosti bez elementu na úrovni bloku. Prvek na úrovni bloku, například dělení (<div>) nebo prvek odstavce () může<p> zabalit kód obsahu chyby, ale není povinný.

Pokud je hranice chyby definovaná v rozložení aplikace, uživatelské rozhraní chyby se zobrazí bez ohledu na to, na kterou stránku uživatel přejde po chybě. Ve většině scénářů doporučujeme zúžit hranice chyb. Pokud v širokém rozsahu rozsahu hranice chyby, můžete ho resetovat do stavu, který není chybový u následných událostí navigace na stránce volání metody hranice Recover chyby.

V MainLayout.razor:

...

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

...

@code {
    private ErrorBoundary? errorBoundary;

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

Abyste se vyhnuli nekonečné smyčce, kdy obnovení pouze rerenderuje komponentu, která chybu znovu vyvolá, nezavolejte Recover z logiky vykreslování. Zavolat Recover pouze v případech:

  • Uživatel provede gesto uživatelského rozhraní, například výběrem tlačítka, které označuje, že chce zopakovat proceduru nebo když uživatel přejde na novou komponentu.
  • Další logika, která provede, také vymaže výjimku. Pokud je komponenta znovu vysunutá, chyba se znovu nezobrazí.

Alternativní globální zpracování výjimek

Přístup popsaný v této části se týká Blazor Serveraplikací , Blazor WebAssemblya Blazor Web Apps, které přijímají globální interaktivní režim vykreslování (InteractiveServer, InteractiveWebAssemblynebo InteractiveAuto). Tento přístup nefunguje s Webovými aplikacemi Blazor , které přijímají režimy vykreslování jednotlivých stránek nebo komponent nebo statické vykreslování na straně serveru (statická služba SSR), protože přístup závisí na CascadingValue/CascadingParametertom, který nefunguje přes hranice režimu vykreslování nebo s komponentami, které přijímají statické SSR.

Alternativou k použití hranic chyb (ErrorBoundary) je předání vlastní chybové komponenty jako podřízeným komponentám CascadingValue . Výhodou použití komponenty oproti použití vložené služby nebo vlastní implementace protokolovacího modulu je, že kaskádová komponenta může vykreslit obsah a použít styly CSS v případě, že dojde k chybě.

Následující ProcessError příklad komponenty pouze protokoluje chyby, ale metody komponenty mohou zpracovávat chyby jakýmkoli způsobem vyžadovaným aplikací, včetně použití více metod zpracování chyb.

ProcessError.razor:

@inject ILogger<ProcessError> Logger

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

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

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

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

Poznámka:

Další informace o RenderFragmentkomponentách ASP.NET CoreRazor.

Při použití tohoto přístupu ve Blazor webové aplikaci otevřete komponentu Routes a zabalte komponentu Router (<Router>...</Router>) do ProcessError komponenty. To umožňuje komponentě ProcessError kaskádovitě dolů na libovolnou komponentu aplikace, kde ProcessError je komponenta přijata jako CascadingParameter.

V Routes.razor:

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

Při použití tohoto přístupu v Blazor Server aplikaci nebo Blazor WebAssembly aplikaci komponentu App otevřete, zabalte komponentu Router (<Router>...</Router>) do ProcessError komponenty. To umožňuje komponentě ProcessError kaskádovitě dolů na libovolnou komponentu aplikace, kde ProcessError je komponenta přijata jako CascadingParameter.

V App.razor:

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

Zpracování chyb v komponentě:

  • Označte komponentu ProcessError jako CascadingParameter součást v @code bloku. Do ukázkové Counter komponenty v aplikaci založené na Blazor šabloně projektu přidejte následující ProcessError vlastnost:

    [CascadingParameter]
    public ProcessError? ProcessError { get; set; }
    
  • Volejte metodu zpracování chyb v libovolném catch bloku s odpovídajícím typem výjimky. Ukázková ProcessError komponenta nabízí pouze jednu LogError metodu, ale komponenta zpracování chyb může poskytnout libovolný počet metod zpracování chyb, které řeší alternativní požadavky na zpracování chyb v celé aplikaci. Následující Counter příklad bloku komponenty @code obsahuje ProcessError kaskádový parametr a sloučí výjimku pro protokolování, pokud je počet větší než pět:

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

Zaprotokolovaná chyba:

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

Pokud se LogError metoda přímo účastní vykreslování, například zobrazení vlastního panelu chybových zpráv nebo změna stylů CSS vykreslených prvků, volání StateHasChanged na konci LogError metody pro opětovné vykreslení uživatelského rozhraní.

Protože přístupy v této části zpracovávají chyby pomocí try-catch příkazu, připojení aplikace SignalR mezi klientem a serverem není přerušeno, když dojde k chybě a okruh zůstane aktivní. Ostatní neošetřené výjimky zůstávají pro okruh závažnou. Další informace najdete v části o tom, jak okruh reaguje na neošetřené výjimky.

Aplikace může použít komponentu zpracování chyb jako kaskádovou hodnotu ke zpracování chyb centralizovaným způsobem.

Následující ProcessError komponenta se předává jako podřízené CascadingValue komponenty. Následující příklad pouze protokoluje chybu, ale metody komponenty mohou zpracovávat chyby jakýmkoli způsobem vyžadovaným aplikací, včetně použití více metod zpracování chyb. Výhodou použití komponenty oproti použití vložené služby nebo vlastní implementace protokolovacího modulu je, že kaskádová komponenta může vykreslit obsah a použít styly CSS v případě, že dojde k chybě.

ProcessError.razor:

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

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

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

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

Poznámka:

Další informace o RenderFragmentkomponentách ASP.NET CoreRazor.

V této komponentě App zabalte Router komponentu s komponentou ProcessError . To umožňuje komponentě ProcessError kaskádovitě dolů na libovolnou komponentu aplikace, kde ProcessError je komponenta přijata jako CascadingParameter.

App.razor:

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

Zpracování chyb v komponentě:

  • Označte komponentu ProcessError jako CascadingParameter součást v @code bloku:

    [CascadingParameter]
    public ProcessError ProcessError { get; set; }
    
  • Volejte metodu zpracování chyb v libovolném catch bloku s odpovídajícím typem výjimky. Ukázková ProcessError komponenta nabízí pouze jednu LogError metodu, ale komponenta zpracování chyb může poskytnout libovolný počet metod zpracování chyb, které řeší alternativní požadavky na zpracování chyb v celé aplikaci.

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

Pomocí předchozí ukázkové ProcessError komponenty a LogError metody označuje konzola vývojářských nástrojů prohlížeče zachycenou zaprotokolovanou chybu:

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

Pokud se LogError metoda přímo účastní vykreslování, například zobrazení vlastního panelu chybových zpráv nebo změna stylů CSS vykreslených prvků, volání StateHasChanged na konci LogError metody pro opětovné vykreslení uživatelského rozhraní.

Protože přístupy v této části zpracovávají chyby pomocí try-catch příkazu, připojení aplikace SignalR mezi klientem a serverem není přerušeno, Blazor když dojde k chybě a okruh zůstane aktivní. Jakákoli neošetřená výjimka je pro okruh závažná. Další informace najdete v části o tom, jak okruh reaguje na neošetřené výjimky.

Chyby protokolu s trvalým zprostředkovatelem

Pokud dojde k neošetřené výjimce, zaprotokoluje ILogger se do instancí nakonfigurovaných v kontejneru služby. Ve výchozím nastavení Blazor se aplikace protokolují do výstupu konzoly pomocí zprostředkovatele protokolování konzoly. Zvažte protokolování do umístění na serveru (nebo back-endovém webovém rozhraní API pro aplikace na straně klienta) s poskytovatelem, který spravuje velikost protokolu a obměnu protokolů. Případně může aplikace používat službu Application Performance Management (APM), například Aplikace Azure lication Insights (Azure Monitor).

Poznámka:

Nativní funkce Application Insights , které podporují aplikace na straně klienta a nativní Blazor podporu rozhraní pro Google Analytics , můžou být dostupné v budoucích verzích těchto technologií. Další informace najdete v tématu Podpora App Insights na Blazor straně klienta WASM (microsoft/ApplicationInsights-dotnet #2143) a webovou analýzu a diagnostiku (včetně odkazů na komunitní implementace) (dotnet/aspnetcore #5461). Mezitím může aplikace na straně klienta používat sadu Application Insights JavaScript SDK s JS interoperabilitou k protokolování chyb přímo do Application Insights z aplikace na straně klienta.

Během vývoje aplikace Blazor , která pracuje přes okruh, aplikace obvykle odesílá úplné podrobnosti o výjimkách do konzoly prohlížeče, aby pomohla při ladění. V produkčním prostředí se podrobné chyby neodesílají klientům, ale úplné podrobnosti o výjimce se protokolují na serveru.

Musíte se rozhodnout, které incidenty se mají protokolovat, a úroveň závažnosti protokolovaných incidentů. Nehostinní uživatelé můžou záměrně aktivovat chyby. Například nezaznamujte incident z chyby, kdy je v adrese URL komponenty, která zobrazuje podrobnosti o produktu, zadaná neznámá ProductId . Ne všechny chyby by se měly považovat za incidenty pro protokolování.

Další informace najdete v následujících článcích:

•Platí pro aplikace na straně Blazor serveru a další aplikace na straně serveru ASP.NET Core, které jsou back-endové aplikace webového rozhraní API pro Blazor. Aplikace na straně klienta můžou vystihot a odesílat informace o chybách klienta do webového rozhraní API, které protokoluje informace o chybách do trvalého zprostředkovatele protokolování.

Pokud dojde k neošetřené výjimce, zaprotokoluje ILogger se do instancí nakonfigurovaných v kontejneru služby. Ve výchozím nastavení Blazor se aplikace protokolují do výstupu konzoly pomocí zprostředkovatele protokolování konzoly. Zvažte protokolování do trvalejšího umístění na serveru odesláním informací o chybách do back-endového webového rozhraní API, které používá zprostředkovatele protokolování se správou velikostí protokolu a obměnou protokoly. Případně může back-endová webová aplikace API používat službu Application Performance Management (APM), jako je Aplikace Azure lication Insights (Azure Monitor)†, k zaznamenávání informací o chybách, které obdrží od klientů.

Musíte se rozhodnout, které incidenty se mají protokolovat, a úroveň závažnosti protokolovaných incidentů. Nehostinní uživatelé můžou záměrně aktivovat chyby. Například nezaznamujte incident z chyby, kdy je v adrese URL komponenty, která zobrazuje podrobnosti o produktu, zadaná neznámá ProductId . Ne všechny chyby by se měly považovat za incidenty pro protokolování.

Další informace najdete v následujících článcích:

†Nativní funkce Application Insights , které podporují aplikace na straně klienta a nativní Blazor podporu rozhraní pro Google Analytics , mohou být dostupné v budoucích verzích těchto technologií. Další informace najdete v tématu Podpora App Insights na Blazor straně klienta WASM (microsoft/ApplicationInsights-dotnet #2143) a webovou analýzu a diagnostiku (včetně odkazů na komunitní implementace) (dotnet/aspnetcore #5461). Mezitím může aplikace na straně klienta používat sadu Application Insights JavaScript SDK s JS interoperabilitou k protokolování chyb přímo do Application Insights z aplikace na straně klienta.

•Platí pro aplikace na straně serveru ASP.NET Core, které jsou back-endové aplikace webového rozhraní API pro Blazor aplikace. Aplikace na straně klienta soutisknou a odesílají informace o chybách do webového rozhraní API, které protokoluje informace o chybě do trvalého zprostředkovatele protokolování.

Místa, kde mohou nastat chyby

Kód architektury a aplikace může aktivovat neošetřené výjimky v některém z následujících umístění, které jsou popsány dále v následujících částech tohoto článku:

Vytváření instancí komponent

Při Blazor vytváření instance komponenty:

  • Vyvolá se konstruktor komponenty.
  • Konstruktory služeb DI poskytované konstruktoru komponenty prostřednictvím @inject direktivy nebo atributu [Inject] jsou vyvolány.

Chyba v spuštěném konstruktoru nebo setter pro libovolnou [Inject] vlastnost má za následek neošetřenou výjimku a zastaví architekturu v vytvoření instance komponenty. Pokud aplikace pracuje přes okruh, okruh selže. Pokud logika konstruktoru může vyvolat výjimky, měla by aplikace výjimku spojit pomocí try-catch příkazu s zpracováním chyb a protokolováním.

Metody životního cyklu

Během životnosti komponenty Blazor vyvolá metody životního cyklu. Pokud nějaká metoda životního cyklu vyvolá výjimku, synchronně nebo asynchronně, je výjimka pro okruh závažná. Aby komponenty řešily chyby v metodách životního cyklu, přidejte logiku zpracování chyb.

V následujícím příkladu, kde OnParametersSetAsync volá metodu pro získání produktu:

  • Výjimku vyvolanou v ProductRepository.GetProductByIdAsync metodě zpracovává try-catch příkaz.
  • catch Při spuštění bloku:
    • loadFailed je nastavena na truehodnotu , která slouží k zobrazení chybové zprávy uživateli.
    • Chyba se zaprotokoluje.
@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 vykreslování

Deklarativní kód v Razor souboru komponenty (.razor) je zkompilován do metody jazyka C# volaný BuildRenderTree. Když se komponenta vykreslí, BuildRenderTree spustí a sestaví datovou strukturu popisující prvky, text a podřízené součásti vykreslené komponenty.

Logika vykreslování může vyvolat výjimku. Příkladem tohoto scénáře je @someObject.PropertyName vyhodnocení, ale @someObject je null. U Blazor aplikací, které pracují přes okruh, je neošetřená výjimka vyvolaná logikou vykreslování pro okruh aplikace závažná.

Pokud chcete zabránit NullReferenceException v logice vykreslování, zkontrolujte null objekt před přístupem ke svým členům. V následujícím příkladu nejsou vlastnosti přístupné, person.Address pokud person.Address je 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>
}

Předchozí kód předpokládá, že person není null. Struktura kódu často zaručuje, že objekt existuje v době vykreslení komponenty. V takových případech není nutné zkontrolovat null logiku vykreslování. V předchozím příkladu může být zaručeno, že existuje, person protože person se vytvoří při vytvoření instance komponenty, jak ukazuje následující příklad:

@code {
    private Person person = new();

    ...
}

Obslužné rutiny událostí

Kód na straně klienta aktivuje vyvolání kódu V# při vytváření obslužných rutin událostí pomocí:

  • @onclick
  • @onchange
  • Další @on... atributy
  • @bind

Kód obslužné rutiny události může v těchto scénářích vyvolat neošetřenou výjimku.

Pokud aplikace volá kód, který by mohl selhat z externích důvodů, vyvolá výjimku pomocí try-catch příkazu s zpracováním chyb a protokolováním.

Pokud obslužná rutina události vyvolá neošetřenou výjimku (například databázový dotaz selže), která není zachycená a zpracována kódem vývojáře:

  • Architektura zaznamená výjimku.
  • Blazor V aplikaci, která pracuje přes okruh, je výjimka pro okruh aplikace závažná.

Vyřazení součástí

Součást může být například odebrána z uživatelského rozhraní, protože uživatel přešel na jinou stránku. Pokud je komponenta, která implementuje System.IDisposable , odebrána z uživatelského rozhraní, volá rozhraní metodu Dispose komponenty.

Pokud metoda komponenty Dispose vyvolá neošetřenou výjimku v Blazor aplikaci, která pracuje přes okruh, je pro okruh aplikace závažná.

Pokud logika odstranění může vyvolat výjimky, měla by aplikace výjimku spojit pomocí try-catch příkazu s zpracováním chyb a protokolováním.

Další informace o odstranění součástí najdete v tématu ASP.NET životní Razor cyklus základních komponent.

Interoperabilita JavaScriptu

IJSRuntime je registrován v Blazor rámci. IJSRuntime.InvokeAsync umožňuje kódu .NET provádět asynchronní volání modulu runtime JavaScriptu (JS) v prohlížeči uživatele.

Následující podmínky platí pro zpracování chyb s InvokeAsync:

  • Pokud volání selže InvokeAsync synchronně, dojde k výjimce .NET. Volání InvokeAsync může například selhat, protože zadané argumenty nelze serializovat. Vývojářský kód musí zachytit výjimku. Pokud kód aplikace v obslužné rutině události nebo metodě životního cyklu komponent nezpracuje výjimku v aplikaci, která Blazor pracuje přes okruh, je výsledná výjimka pro okruh aplikace závažná.
  • Pokud volání selže InvokeAsync asynchronně, rozhraní .NET Task selže. Volání může selhat InvokeAsync , například proto, že JSkód -side vyvolá výjimku nebo vrátí dokončené Promise jako rejected. Vývojářský kód musí zachytit výjimku. Pokud používáte await operátor, zvažte zabalení volání metody do try-catch příkazu s zpracováním chyb a protokolováním. Jinak v Blazor aplikaci, která pracuje přes okruh, vede chybný kód k neošetřené výjimce, která je závažná pro okruh aplikace.
  • Ve výchozím nastavení musí volání InvokeAsync dokončit během určitého období nebo jinak vyprší časový limit volání. Výchozí časový limit je jedna minuta. Časový limit chrání kód před ztrátou síťového připojení nebo JS kódu, který nikdy neodesílá zprávu o dokončení. Pokud vyprší časový limit volání, výsledek System.Threading.Tasks selže s chybou OperationCanceledException. Soutisk a zpracování výjimky s protokolováním

JS Podobně může kód inicializovat volání metod .NET označených atributem[JSInvokable]. Pokud tyto metody .NET vyvolá neošetřenou výjimku:

  • V aplikaci, která Blazor pracuje přes okruh, není výjimka považována za závažnou pro okruh aplikace.
  • Strana JS-side Promise je odmítnuta.

Máte možnost použít kód zpracování chyb na straně .NET nebo JS na straně volání metody.

Další informace najdete v následujících článcích:

Předkreslování

Razor komponenty jsou ve výchozím nastavení předem vytyčovány tak, aby se jejich vykreslené kódy HTML vrátily jako součást počátečního požadavku HTTP uživatele.

Blazor V aplikaci, která pracuje přes okruh, funguje prerendering podle:

  • Vytvoření nového okruhu pro všechny předem připravené součásti, které jsou součástí stejné stránky.
  • Generuje se počáteční kód HTML.
  • S okruhem zacházíte tak, dokud disconnected prohlížeč uživatele nenaváže SignalR připojení zpět ke stejnému serveru. Po navázání připojení se obnoví interaktivita okruhu a aktualizuje se kód HTML komponent.

U předem vytyčovaných komponent na straně klienta funguje předkreslování:

  • Generování počátečního kódu HTML na serveru pro všechny předsdílené komponenty, které jsou součástí stejné stránky.
  • Vytvoření interaktivní komponenty na klientovi po načtení zkompilovaného kódu aplikace a modulu runtime .NET (pokud ještě není načteno) na pozadí.

Pokud komponenta při předřazení vyvolá neošetřenou výjimku, například během metody životního cyklu nebo v logice vykreslování:

  • Blazor V aplikaci, která pracuje přes okruh, je výjimka pro okruh závažná. U předsekvenovaných komponent na straně klienta výjimka zabraňuje vykreslení komponenty.
  • Výjimka vyvolá zásobník volání z objektu ComponentTagHelper.

Za normálních okolností, když se předdekretování nezdaří, nebude nadále sestavovat a vykreslovat komponentu, protože pracovní komponentu nelze vykreslit.

Aby bylo možné tolerovat chyby, ke kterým může dojít během předkreslování, musí být logika zpracování chyb umístěna uvnitř komponenty, která může vyvolat výjimky. Používejte try-catch příkazy se zpracováním chyb a protokolováním. Místo zabalení příkazu ComponentTagHelper try-catch do příkazu umístěte logiku zpracování chyb v komponentě vykreslené ComponentTagHelpersadou .

Pokročilé scénáře

Rekurzivní vykreslování

Komponenty se dají vnořit rekurzivně. To je užitečné pro reprezentaci rekurzivních datových struktur. Například komponenta TreeNode může vykreslit více TreeNode komponent pro každou podřízenou položku uzlu.

Při rekurzivním vykreslování se vyhněte vzorům kódování, které vedou k nekonečné rekurzi:

  • Nevykreslujte rekurzivně datovou strukturu, která obsahuje cyklus. Například nevykreslujte uzel stromu, jehož podřízené položky se zahrnují.
  • Nevytvávejte řetězec rozložení, která obsahují cyklus. Například nevytvořte rozložení, jehož rozložení je samotné.
  • Nepovolujte koncovému uživateli porušení invariantů rekurze (pravidel) prostřednictvím škodlivých datových zadávání nebo volání interop JavaScriptu.

Nekonečné smyčky během vykreslování:

  • Způsobí, že proces vykreslování bude pokračovat navždy.
  • Je ekvivalentní vytvoření neukončené smyčky.

V těchto scénářích se Blazor selhání a obvykle se pokouší:

  • Spotřebovávejte tolik času procesoru, kolik povoluje operační systém, neomezeně dlouho.
  • Spotřebovávat neomezené množství paměti. Využívání neomezené paměti odpovídá scénáři, kdy neukončená smyčka přidává položky do kolekce při každé iteraci.

Abyste se vyhnuli nekonečným rekurzivním vzorcům, ujistěte se, že rekurzivní kód vykreslování obsahuje vhodné podmínky zastavení.

Vlastní logika stromu vykreslování

Většina Razor komponent se implementuje jako Razor soubory komponent (.razor) a jsou zkompilovány architekturou za účelem vytvoření logiky, která pracuje s vykreslením RenderTreeBuilder jejich výstupu. Vývojář však může ručně implementovat RenderTreeBuilder logiku pomocí procedurálního kódu jazyka C#. Další informace najdete v tématu ASP.NET Blazor pokročilých scénářích (vytváření stromové struktury vykreslování).

Upozorňující

Použití logiky ručního tvůrce stromové struktury vykreslování se považuje za pokročilý a nebezpečný scénář, nedoporučuje se pro obecný vývoj komponent.

Pokud RenderTreeBuilder je kód napsaný, vývojář musí zaručit správnost kódu. Vývojář například musí zajistit, aby:

  • Volání a OpenElement CloseElement jsou správně vyvážená.
  • Atributy se přidají pouze na správných místech.

Nesprávná logika ručního tvůrce stromové struktury může způsobit libovolné nedefinované chování, včetně chybových ukončení, zablokování aplikace nebo serveru a ohrožení zabezpečení.

Zvažte ruční logiku tvůrce stromové struktury na stejné úrovni složitosti a se stejnou úrovní nebezpečí jako ruční psaní kódu sestavení nebo pokynů jazyka MSIL (Microsoft Intermediate Language).

Další materiály

†Applies k back-endu ASP.NET aplikacím core webového rozhraní API, které aplikace na straně Blazor klienta používají k protokolování.