Condividi tramite


Gestire gli errori nelle app core Blazor ASP.NET

Nota

Questa non è la versione più recente di questo articolo. Per la versione corrente, vedere la versione .NET 8 di questo articolo.

Avviso

Questa versione di ASP.NET Core non è più supportata. Per altre informazioni, vedere Criteri di supporto di .NET e .NET Core. Per la versione corrente, vedere la versione .NET 8 di questo articolo.

Importante

Queste informazioni si riferiscono a un prodotto non definitive che può essere modificato in modo sostanziale prima che venga rilasciato commercialmente. Microsoft non riconosce alcuna garanzia, espressa o implicita, in merito alle informazioni qui fornite.

Per la versione corrente, vedere la versione .NET 8 di questo articolo.

Questo articolo descrive come Blazor gestire le eccezioni non gestite e come sviluppare app che rilevano e gestiscono gli errori.

Errori dettagliati durante lo sviluppo

Quando un'app Blazor non funziona correttamente durante lo sviluppo, riceve informazioni dettagliate sull'errore dall'app per facilitare la risoluzione e la risoluzione del problema. Quando si verifica un errore, Blazor le app visualizzano una barra gialla chiara nella parte inferiore dello schermo:

  • Durante lo sviluppo, la barra indirizza l'utente alla console del browser, in cui è possibile visualizzare l'eccezione.
  • Nell'ambiente di produzione, la barra notifica all'utente che si è verificato un errore e consiglia di aggiornare il browser.

L'interfaccia utente per questa esperienza di gestione degli errori fa parte dei modelli di Blazor progetto. Non tutte le versioni dei Blazor modelli di progetto usano l'attributo data-nosnippet per segnalare ai browser di non memorizzare nella cache il contenuto dell'interfaccia utente degli errori, ma tutte le versioni della Blazor documentazione applicano l'attributo .

In un'app Blazor Web personalizzare l'esperienza nel MainLayout componente. Poiché l'helper tag di ambiente (ad esempio , <environment include="Production">...</environment>) non è supportato nei Razor componenti, l'esempio seguente inserisce per configurare i IHostEnvironment messaggi di errore per ambienti diversi.

Nella parte superiore di MainLayout.razor:

@inject IHostEnvironment HostEnvironment

Creare o modificare il markup dell'interfaccia utente degli Blazor errori:

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

In un'app Blazor Server personalizzare l'esperienza nel Pages/_Host.cshtml file. Nell'esempio seguente viene usato l'helper tag di ambiente per configurare i messaggi di errore per ambienti diversi.

In un'app Blazor Server personalizzare l'esperienza nel Pages/_Layout.cshtml file. Nell'esempio seguente viene usato l'helper tag di ambiente per configurare i messaggi di errore per ambienti diversi.

In un'app Blazor Server personalizzare l'esperienza nel Pages/_Host.cshtml file. Nell'esempio seguente viene usato l'helper tag di ambiente per configurare i messaggi di errore per ambienti diversi.

Creare o modificare il markup dell'interfaccia utente degli Blazor errori:

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

In un'app Blazor WebAssembly personalizzare l'esperienza nel wwwroot/index.html file:

<div id="blazor-error-ui" data-nosnippet>
    An unhandled error has occurred.
    <a href="" class="reload">Reload</a>
    <a class="dismiss">🗙</a>
</div>

L'elemento blazor-error-ui è in genere nascosto a causa della presenza dello display: none stile della blazor-error-ui classe CSS nel foglio di stile generato automaticamente dell'app. Quando si verifica un errore, il framework si applica display: block all'elemento .

L'elemento blazor-error-ui è in genere nascosto a causa della presenza dello display: none stile della blazor-error-ui classe CSS nel foglio di stile del sito nella wwwroot/css cartella . Quando si verifica un errore, il framework si applica display: block all'elemento .

Errori dettagliati del circuito

Questa sezione si applica a Blazor App Web funzionamento su un circuito.

Questa sezione si applica alle Blazor Server app.

Gli errori sul lato client non includono lo stack di chiamate e non forniscono dettagli sulla causa dell'errore, ma i log del server contengono tali informazioni. A scopo di sviluppo, è possibile rendere disponibili al client informazioni riservate sugli errori del circuito abilitando errori dettagliati.

Impostare CircuitOptions.DetailedErrors su true. Per altre informazioni e un esempio, vedere ASP.NET Linee guida di baseBlazorSignalR.

Un'alternativa all'impostazione CircuitOptions.DetailedErrors consiste nell'impostare la DetailedErrors chiave di configurazione su true nel file di impostazioni dell'ambiente dell'app Development (appsettings.Development.json). Impostare inoltre la registrazione lato server (Microsoft.AspNetCore.SignalR) su Debug o Traccia per la registrazione dettagliataSignalR.SignalR

appsettings.Development.json:

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

La DetailedErrors chiave di configurazione può anche essere impostata su true usando la ASPNETCORE_DETAILEDERRORS variabile di ambiente con un valore di true nei Development/Staging server di ambiente o nel sistema locale.

Avviso

Evitare sempre di esporre informazioni sugli errori ai client su Internet, che è un rischio per la sicurezza.

Errori dettagliati per il Razor rendering lato server del componente

Questa sezione si applica a Blazor App Web.

Usare l'opzione RazorComponentsServiceOptions.DetailedErrors per controllare la produzione di informazioni dettagliate sugli errori per Razor il rendering lato server dei componenti. Il valore predefinito è false.

Nell'esempio seguente vengono attivati errori dettagliati:

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

Avviso

Abilitare solo errori dettagliati nell'ambiente Development . Gli errori dettagliati possono contenere informazioni riservate sull'app che gli utenti malintenzionati possono usare in un attacco.

L'esempio precedente fornisce un grado di sicurezza impostando il valore di DetailedErrors in base al valore restituito da IsDevelopment. Quando l'app si trova nell'ambiente Development , DetailedErrors è impostata truesu . Questo approccio non è infallibile perché è possibile ospitare un'app di produzione in un server pubblico nell'ambiente Development .

Gestire le eccezioni non gestite nel codice per sviluppatori

Affinché un'app continui dopo un errore, l'app deve avere la logica di gestione degli errori. Le sezioni successive di questo articolo descrivono le potenziali origini di eccezioni non gestite.

Nell'ambiente di produzione, non eseguire il rendering dei messaggi di eccezione del framework o delle tracce dello stack nell'interfaccia utente. Il rendering dei messaggi di eccezione o delle tracce dello stack può:

  • Divulgare informazioni riservate agli utenti finali.
  • Aiutare un utente malintenzionato a individuare i punti deboli in un'app che può compromettere la sicurezza dell'app, del server o della rete.

Eccezioni non gestite per i circuiti

Questa sezione si applica alle app lato server che operano su un circuito.

Razor i componenti con interattività server abilitata sono con stato nel server. Mentre gli utenti interagiscono con il componente nel server, mantengono una connessione al server noto come circuito. Il circuito contiene istanze del componente attive, oltre a molti altri aspetti dello stato, ad esempio:

  • Output di componenti di cui è stato eseguito il rendering più recente.
  • Set corrente di delegati di gestione degli eventi che possono essere attivati da eventi lato client.

Se un utente apre l'app in più schede del browser, l'utente crea più circuiti indipendenti.

Blazor considera la maggior parte delle eccezioni non gestite come irreversibili per il circuito in cui si verificano. Se un circuito viene terminato a causa di un'eccezione non gestita, l'utente può continuare a interagire solo con l'app ricaricando la pagina per creare un nuovo circuito. I circuiti esterni a quello terminato, che sono circuiti per altri utenti o altre schede del browser, non sono interessati. Questo scenario è simile a un'app desktop che si arresta in modo anomalo. L'app arrestata in modo anomalo deve essere riavviata, ma altre app non sono interessate.

Il framework termina un circuito quando si verifica un'eccezione non gestita per i motivi seguenti:

  • Un'eccezione non gestita spesso lascia il circuito in uno stato non definito.
  • Il normale funzionamento dell'app non può essere garantito dopo un'eccezione non gestita.
  • Le vulnerabilità di sicurezza possono essere visualizzate nell'app se il circuito continua in uno stato non definito.

Gestione globale delle eccezioni

Per gli approcci alla gestione globale delle eccezioni, vedere le sezioni seguenti:

  • Limiti di errore: si applica a tutte le Blazor app.
  • Gestione alternativa delle eccezioni globali: si applica a Blazor Server, Blazor WebAssemblye Blazor App Web (8.0 o versione successiva) che adottano una modalità di rendering interattiva globale.

Limiti di errore

I limiti degli errori offrono un approccio pratico per la gestione delle eccezioni. Componente ErrorBoundary :

  • Esegue il rendering del contenuto figlio quando non si è verificato un errore.
  • Esegue il rendering dell'interfaccia utente degli errori quando un'eccezione non gestita viene generata da qualsiasi componente all'interno del limite di errore.

Per definire un limite di errore, usare il componente per eseguire il ErrorBoundary wrapping di uno o più componenti. Il limite di errore gestisce le eccezioni non gestite generate dai componenti di cui esegue il wrapping.

<ErrorBoundary>
    ...
</ErrorBoundary>

Per implementare un limite di errore in modo globale, aggiungere il limite intorno al contenuto del corpo del layout principale dell'app.

In MainLayout.razor:

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

In Blazor App Web con il limite di errore applicato solo a un componente staticoMainLayout, il limite è attivo solo durante il rendering statico lato server (SSR statico). Il limite non viene attivato solo perché un componente più in basso nella gerarchia dei componenti è interattivo.

Non è possibile applicare una modalità di rendering interattiva al MainLayout componente perché il parametro del Body componente è un RenderFragment delegato, ovvero codice arbitrario e non può essere serializzato. Per abilitare l'interattività su larga scala per il MainLayout componente e il resto dei componenti più in basso nella gerarchia dei componenti, l'app deve adottare una modalità di rendering interattiva globale applicando la modalità di rendering interattiva alle istanze del HeadOutlet componente e Routes nel componente radice dell'app, che in genere è il App componente. L'esempio seguente adotta la modalità di rendering Interactive Server (InteractiveServer) a livello globale.

In Components/App.razor:

<HeadOutlet @rendermode="InteractiveServer" />

...

<Routes @rendermode="InteractiveServer" />

Se si preferisce non abilitare l'interattività globale, posizionare il limite di errore più lontano dalla gerarchia dei componenti. I concetti importanti da tenere presente sono che ovunque si trovi il limite di errore:

  • Se il componente in cui viene posizionato il limite di errore non è interattivo, il limite di errore è in grado di attivarsi solo nel server durante il ssr statico. Ad esempio, il limite può essere attivato quando viene generato un errore in un metodo del ciclo di vita del componente, ma non per un evento attivato dall'interattività dell'utente all'interno del componente, ad esempio un errore generato da un gestore di clic del pulsante.
  • Se il componente in cui viene posizionato il limite di errore è interattivo, il limite di errore è in grado di attivare per i componenti interattivi a capo.

Nota

Le considerazioni precedenti non sono rilevanti per le app autonome Blazor WebAssembly perché il rendering lato client di un'app Blazor WebAssembly è completamente interattivo.

Si consideri l'esempio seguente, in cui un'eccezione generata da un componente contatore incorporato viene intercettata da un limite di errore nel Home componente, che adotta una modalità di rendering interattiva.

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>

Si consideri l'esempio seguente, in cui un'eccezione generata da un componente contatore incorporato viene intercettata da un limite di errore nel Home componente.

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>

Se l'eccezione non gestita viene generata per un currentCount valore superiore a cinque:

  • L'errore viene registrato normalmente (System.InvalidOperationException: Current count is too big!).
  • L'eccezione viene gestita dal limite di errore.
  • Il rendering dell'interfaccia utente dell'errore predefinito viene eseguito dal limite di errore.

Per impostazione predefinita, il componente esegue il ErrorBoundary rendering di un elemento vuoto <div> usando la classe CSS per il blazor-error-boundary relativo contenuto di errore. I colori, il testo e l'icona per l'interfaccia utente predefinita sono definiti nel foglio di stile dell'app nella wwwroot cartella, quindi è possibile personalizzare l'interfaccia utente dell'errore.

Interfaccia utente di errore predefinita sottoposta a rendering da un limite di errore, con sfondo rosso, il testo

Per modificare il contenuto di errore predefinito:

  • Eseguire il wrapping dei componenti del limite di errore nella ChildContent proprietà .
  • Impostare la ErrorContent proprietà sul contenuto dell'errore.

Nell'esempio seguente viene eseguito il wrapping del EmbeddedCounter componente e viene fornito contenuto di errore personalizzato:

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

Per l'esempio precedente, il foglio di stile dell'app include presumibilmente una errorUI classe CSS per applicare uno stile al contenuto. Il rendering del contenuto dell'errore ErrorContent viene eseguito dalla proprietà senza un elemento a livello di blocco. Un elemento a livello di blocco, ad esempio una divisione (<div>) o un elemento di paragrafo (<p>), può eseguire il wrapping del markup del contenuto degli errori, ma non è obbligatorio.

Se il limite di errore viene definito nel layout dell'app, l'interfaccia utente dell'errore viene visualizzata indipendentemente dalla pagina a cui si sposta l'utente dopo che si verifica l'errore. È consigliabile definire in modo ristretto i limiti degli errori nella maggior parte degli scenari. Se si definisce un ambito generale di un limite di errore, è possibile ripristinarlo in uno stato non di errore negli eventi di spostamento di pagina successivi chiamando il metodo del limite di Recover errore.

In MainLayout.razor:

...

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

...

@code {
    private ErrorBoundary? errorBoundary;

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

Per evitare il ciclo infinito in cui il recupero esegue semplicemente il rerendere un componente che genera nuovamente l'errore, non chiamare Recover dalla logica di rendering. Chiama Recover solo quando:

  • L'utente esegue un movimento dell'interfaccia utente, ad esempio selezionando un pulsante per indicare che desidera ripetere una procedura o quando l'utente passa a un nuovo componente.
  • Logica aggiuntiva che esegue cancella anche l'eccezione. Quando il componente viene rerendered, l'errore non viene eseguito di nuovo.

Gestione alternativa delle eccezioni globali

L'approccio descritto in questa sezione si applica a Blazor Server, Blazor WebAssemblye Blazor App Web che adottano una modalità di rendering interattiva globale (InteractiveServer, InteractiveWebAssemblyo InteractiveAuto). L'approccio non funziona con Blazor App Web che adottano modalità di rendering per pagina/componente o rendering statico lato server (SSR statico) perché l'approccio si basa su un CascadingValue/CascadingParameter, che non funziona attraverso i limiti della modalità di rendering o con componenti che adottano SSR statico.

Un'alternativa all'uso dei limiti di errore (ErrorBoundary) consiste nel passare un componente di errore personalizzato come componente CascadingValue ai componenti figlio. Un vantaggio dell'uso di un componente rispetto all'uso di un servizio inserito o di un'implementazione di logger personalizzata è che un componente a catena può eseguire il rendering del contenuto e applicare stili CSS quando si verifica un errore.

L'esempio di componente seguente ProcessError registra semplicemente gli errori, ma i metodi del componente possono elaborare gli errori in qualsiasi modo richiesto dall'app, incluso l'uso di più metodi di elaborazione degli errori.

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

Nota

Per altre informazioni su RenderFragment, vedere ASP.NET Componenti di baseRazor.

Quando si usa questo approccio in un'app Blazor Web, aprire il Routes componente ed eseguire il wrapping del Router componente (<Router>...</Router>) con il ProcessError componente . Ciò consente al ProcessError componente di propagarsi verso il basso a qualsiasi componente dell'app in cui il ProcessError componente viene ricevuto come .CascadingParameter

In Routes.razor:

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

Quando si usa questo approccio in un'app Blazor Server o Blazor WebAssembly , aprire il componente, eseguire il App wrapping del Router componente (<Router>...</Router>) con il ProcessError componente . Ciò consente al ProcessError componente di propagarsi verso il basso a qualsiasi componente dell'app in cui il ProcessError componente viene ricevuto come .CascadingParameter

In App.razor:

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

Per elaborare gli errori in un componente:

  • Designare il ProcessError componente come oggetto CascadingParameter nel @code blocco . In un componente di esempio Counter in un'app basata su un Blazor modello di progetto aggiungere la proprietà seguente ProcessError :

    [CascadingParameter]
    public ProcessError? ProcessError { get; set; }
    
  • Chiamare un metodo di elaborazione degli errori in qualsiasi catch blocco con un tipo di eccezione appropriato. Il componente di esempio ProcessError offre un solo LogError metodo, ma il componente di elaborazione degli errori può fornire un numero qualsiasi di metodi di elaborazione degli errori per soddisfare i requisiti di elaborazione degli errori alternativi in tutta l'app. L'esempio di blocco di componenti @code seguente Counter include il ProcessError parametro a catena e intercettare un'eccezione per la registrazione quando il conteggio è maggiore di cinque:

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

Errore registrato:

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

Se il LogError metodo partecipa direttamente al rendering, ad esempio la visualizzazione di una barra dei messaggi di errore personalizzata o la modifica degli stili CSS degli elementi di cui è stato eseguito il rendering, chiamare StateHasChanged alla fine del LogError metodo per ripristinare l'interfaccia utente.

Poiché gli approcci in questa sezione gestiscono gli errori con un'istruzione, la connessione di SignalR un'app try-catch tra il client e il server non viene interrotta quando si verifica un errore e il circuito rimane attivo. Altre eccezioni non gestite rimangono fatali per un circuito. Per altre informazioni, vedere la sezione relativa alla reazione di un circuito a eccezioni non gestite.

Un'app può usare un componente di elaborazione degli errori come valore a catena per elaborare gli errori in modo centralizzato.

Il componente seguente ProcessError viene passato come oggetto CascadingValue ai componenti figlio. L'esempio seguente registra semplicemente l'errore, ma i metodi del componente possono elaborare gli errori in qualsiasi modo richiesto dall'app, incluso l'uso di più metodi di elaborazione degli errori. Un vantaggio dell'uso di un componente rispetto all'uso di un servizio inserito o di un'implementazione di logger personalizzata è che un componente a catena può eseguire il rendering del contenuto e applicare stili CSS quando si verifica un errore.

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

Nota

Per altre informazioni su RenderFragment, vedere ASP.NET Componenti di baseRazor.

App Nel componente eseguire il wrapping del Router componente con il ProcessError componente . Ciò consente al ProcessError componente di propagarsi verso il basso a qualsiasi componente dell'app in cui il ProcessError componente viene ricevuto come .CascadingParameter

App.razor:

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

Per elaborare gli errori in un componente:

  • Designare il ProcessError componente come nel CascadingParameter @code blocco:

    [CascadingParameter]
    public ProcessError ProcessError { get; set; }
    
  • Chiamare un metodo di elaborazione degli errori in qualsiasi catch blocco con un tipo di eccezione appropriato. Il componente di esempio ProcessError offre un solo LogError metodo, ma il componente di elaborazione degli errori può fornire un numero qualsiasi di metodi di elaborazione degli errori per soddisfare i requisiti di elaborazione degli errori alternativi in tutta l'app.

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

Usando il componente e LogError il metodo di esempio ProcessError precedenti, la console degli strumenti di sviluppo del browser indica l'errore bloccato registrato:

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

Se il LogError metodo partecipa direttamente al rendering, ad esempio la visualizzazione di una barra dei messaggi di errore personalizzata o la modifica degli stili CSS degli elementi di cui è stato eseguito il rendering, chiamare StateHasChanged alla fine del LogError metodo per ripristinare l'interfaccia utente.

Poiché gli approcci in questa sezione gestiscono gli errori con un'istruzionetry-catch, la connessione di SignalR un'app Blazor tra il client e il server non viene interrotta quando si verifica un errore e il circuito rimane attivo. Qualsiasi eccezione non gestita è irreversibile per un circuito. Per altre informazioni, vedere la sezione relativa alla reazione di un circuito a eccezioni non gestite.

Registrare gli errori con un provider persistente

Se si verifica un'eccezione non gestita, l'eccezione viene registrata nelle ILogger istanze configurate nel contenitore del servizio. Per impostazione predefinita, Blazor le app accedono all'output della console con il provider di registrazione della console. Prendere in considerazione la registrazione in un percorso nel server (o nell'API Web back-end per le app lato client) con un provider che gestisce le dimensioni del log e la rotazione dei log. In alternativa, l'app può usare un servizio APM (Application Performance Management), ad esempio app Azure lication Insights (Monitoraggio di Azure).

Nota

Le funzionalità native di Application Insights per supportare le app sul lato client e il supporto del framework nativo Blazor per Google Analytics potrebbero diventare disponibili nelle versioni future di queste tecnologie. Per altre informazioni, vedere Supporto di App Insights sul Blazor lato client WASM (microsoft/ApplicationInsights-dotnet #2143) e analisi Web e diagnostica (include collegamenti alle implementazioni della community) (dotnet/aspnetcore #5461). Nel frattempo, un'app lato client può usare Application Insights JavaScript SDK con JS interoperabilità per registrare gli errori direttamente in Application Insights da un'app lato client.

Durante lo sviluppo in un'app Blazor che opera su un circuito, l'app in genere invia i dettagli completi delle eccezioni alla console del browser per facilitare il debug. Nell'ambiente di produzione, gli errori dettagliati non vengono inviati ai client, ma i dettagli completi di un'eccezione vengono registrati nel server.

È necessario decidere quali eventi imprevisti registrare e il livello di gravità degli eventi imprevisti registrati. Gli utenti ostili potrebbero essere in grado di attivare deliberatamente gli errori. Ad esempio, non registrare un evento imprevisto da un errore in cui viene fornito un sconosciuto ProductId nell'URL di un componente che visualizza i dettagli del prodotto. Non tutti gli errori devono essere considerati come eventi imprevisti per la registrazione.

Per altre informazioni, vedere gli articoli seguenti:

*Si applica alle app lato Blazor server e ad altre app ASP.NET Core lato server che sono app back-end dell'API Web per Blazor. Le app sul lato client possono intercettare e inviare informazioni sugli errori sul client a un'API Web, che registra le informazioni sull'errore a un provider di registrazione permanente.

Se si verifica un'eccezione non gestita, l'eccezione viene registrata nelle ILogger istanze configurate nel contenitore del servizio. Per impostazione predefinita, Blazor le app accedono all'output della console con il provider di registrazione della console. Valutare la possibilità di accedere a una posizione più permanente nel server inviando informazioni sugli errori a un'API Web back-end che usa un provider di registrazione con gestione delle dimensioni dei log e rotazione dei log. In alternativa, l'app per le API Web back-end può usare un servizio APM (Application Performance Management), ad esempio app Azure lication Insights (Monitoraggio di Azure)†, per registrare le informazioni sugli errori ricevute dai client.

È necessario decidere quali eventi imprevisti registrare e il livello di gravità degli eventi imprevisti registrati. Gli utenti ostili potrebbero essere in grado di attivare deliberatamente gli errori. Ad esempio, non registrare un evento imprevisto da un errore in cui viene fornito un sconosciuto ProductId nell'URL di un componente che visualizza i dettagli del prodotto. Non tutti gli errori devono essere considerati come eventi imprevisti per la registrazione.

Per altre informazioni, vedere gli articoli seguenti:

† Funzionalità di Application Insights per supportare le app sul lato client e il supporto del framework nativo Blazor per Google Analytics potrebbero diventare disponibili nelle versioni future di queste tecnologie. Per altre informazioni, vedere Supporto di App Insights sul Blazor lato client WASM (microsoft/ApplicationInsights-dotnet #2143) e analisi Web e diagnostica (include collegamenti alle implementazioni della community) (dotnet/aspnetcore #5461). Nel frattempo, un'app lato client può usare Application Insights JavaScript SDK con JS interoperabilità per registrare gli errori direttamente in Application Insights da un'app lato client.

*Si applica alle app di base ASP.NET lato server che sono app back-end dell'API Web per Blazor le app. Le app sul lato client intercettare e inviare informazioni sugli errori a un'API Web, che registra le informazioni sull'errore a un provider di registrazione permanente.

Posizioni in cui è possibile che si verifichino errori

Il framework e il codice dell'app possono attivare eccezioni non gestite in una delle posizioni seguenti, descritte più avanti nelle sezioni seguenti di questo articolo:

Creazione di un'istanza del componente

Quando Blazor crea un'istanza di un componente:

  • Viene richiamato il costruttore del componente.
  • I costruttori dei servizi di inserimento delle dipendenze forniti al costruttore del componente tramite la @inject direttiva o l'attributo [Inject] vengono richiamati.

Un errore in un costruttore eseguito o un setter per qualsiasi [Inject] proprietà genera un'eccezione non gestita e impedisce al framework di creare un'istanza del componente. Se l'app funziona su un circuito, il circuito ha esito negativo. Se la logica del costruttore può generare eccezioni, l'app deve intercettare le eccezioni usando un'istruzione try-catch con la gestione e la registrazione degli errori.

Metodi del ciclo di vita

Durante la durata di un componente, Blazor richiama i metodi del ciclo di vita. Se un metodo del ciclo di vita genera un'eccezione, in modo sincrono o asincrono, l'eccezione è irreversibile per un circuito. Per consentire ai componenti di gestire gli errori nei metodi del ciclo di vita, aggiungere la logica di gestione degli errori.

Nell'esempio seguente in cui OnParametersSetAsync chiama un metodo per ottenere un prodotto:

  • Un'eccezione generata nel ProductRepository.GetProductByIdAsync metodo viene gestita da un'istruzione try-catch .
  • Quando viene eseguito il catch blocco:
    • loadFailed è impostato su true, che viene usato per visualizzare un messaggio di errore all'utente.
    • Viene registrato l'errore.
@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);
    }
}

Logica di rendering

Il markup dichiarativo in un Razor file di componente (.razor) viene compilato in un metodo C# denominato BuildRenderTree. Quando viene eseguito il rendering di un componente, BuildRenderTree esegue e compila una struttura di dati che descrive gli elementi, il testo e i componenti figlio del componente sottoposto a rendering.

La logica di rendering può generare un'eccezione. Un esempio di questo scenario si verifica quando @someObject.PropertyName viene valutato ma @someObject è null. Per Blazor le app che operano su un circuito, un'eccezione non gestita generata dalla logica di rendering è irreversibile per il circuito dell'app.

Per evitare un oggetto NullReferenceException nella logica di rendering, verificare la presenza di un null oggetto prima di accedere ai relativi membri. Nell'esempio seguente le person.Address proprietà non sono accessibili se person.Address è null:

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

Il codice precedente presuppone che person non nullsia . Spesso, la struttura del codice garantisce che un oggetto esista al momento del rendering del componente. In questi casi, non è necessario verificare null la presenza nella logica di rendering. Nell'esempio precedente potrebbe person essere garantita l'esistenza perché person viene creata quando viene creata un'istanza del componente, come illustrato nell'esempio seguente:

@code {
    private Person person = new();

    ...
}

Gestori eventi

Il codice lato client attiva le chiamate del codice C# quando vengono creati gestori eventi tramite:

  • @onclick
  • @onchange
  • Altri @on... attributi
  • @bind

Il codice del gestore eventi potrebbe generare un'eccezione non gestita in questi scenari.

Se l'app chiama codice che potrebbe non riuscire per motivi esterni, intercettare le eccezioni usando un'istruzione try-catch con la gestione e la registrazione degli errori.

Se un gestore eventi genera un'eccezione non gestita (ad esempio, una query di database non riesce) non intrappolata e gestita dal codice dello sviluppatore:

  • Il framework registra l'eccezione.
  • In un'app Blazor che opera su un circuito, l'eccezione è irreversibile per il circuito dell'app.

Eliminazione dei componenti

Un componente può essere rimosso dall'interfaccia utente, ad esempio, perché l'utente ha spostato in un'altra pagina. Quando un componente che implementa System.IDisposable viene rimosso dall'interfaccia utente, il framework chiama il metodo del Dispose componente.

Se il metodo del Dispose componente genera un'eccezione non gestita in un'app Blazor che opera su un circuito, l'eccezione è irreversibile per il circuito dell'app.

Se la logica di eliminazione può generare eccezioni, l'app deve intercettare le eccezioni usando un'istruzione try-catch con la gestione e la registrazione degli errori.

Per altre informazioni sull'eliminazione dei componenti, vedere ASP.NET ciclo di vita dei componenti principaliRazor.

Interoperabilità JavaScript

IJSRuntime viene registrato dal Blazor framework. IJSRuntime.InvokeAsync consente al codice .NET di effettuare chiamate asincrone al runtime JavaScript (JS) nel browser dell'utente.

Le condizioni seguenti si applicano alla gestione degli errori con InvokeAsync:

  • Se una chiamata a InvokeAsync non riesce in modo sincrono, si verifica un'eccezione .NET. Una chiamata a InvokeAsync potrebbe non riuscire, ad esempio, perché gli argomenti forniti non possono essere serializzati. Il codice dello sviluppatore deve intercettare l'eccezione. Se il codice dell'app in un metodo del ciclo di vita di un gestore eventi o di un componente non gestisce un'eccezione in un'app Blazor che opera su un circuito, l'eccezione risultante è irreversibile per il circuito dell'app.
  • Se una chiamata a InvokeAsync non riesce in modo asincrono, .NET Task ha esito negativo. Una chiamata a InvokeAsync potrebbe non riuscire, ad esempio, perché il JScodice sul lato genera un'eccezione o restituisce un Promise oggetto completato come rejected. Il codice dello sviluppatore deve intercettare l'eccezione. Se si usa l'operatore await , è consigliabile eseguire il wrapping della chiamata al metodo in un'istruzione try-catch con la gestione degli errori e la registrazione. In caso contrario, in un'app Blazor che opera su un circuito, il codice non riuscito genera un'eccezione non gestita irreversibile per il circuito dell'app.
  • Per impostazione predefinita, le chiamate a InvokeAsync devono essere completate entro un determinato periodo o il timeout della chiamata. Il periodo di timeout predefinito è di un minuto. Il timeout protegge il codice da una perdita di connettività di rete o JS codice che non invia mai un messaggio di completamento. Se si verifica il timeout della chiamata, l'oggetto risultante System.Threading.Tasks ha esito negativo con un oggetto OperationCanceledException. Intercettare ed elaborare l'eccezione con la registrazione.

Analogamente, JS il codice può avviare chiamate ai metodi .NET indicati dall'attributo[JSInvokable] . Se questi metodi .NET generano un'eccezione non gestita:

  • In un'app Blazor che opera su un circuito, l'eccezione non viene considerata irreversibile per il circuito dell'app.
  • Il JSlato Promise -viene rifiutato.

È possibile usare il codice di gestione degli errori sul lato .NET o sul JS lato della chiamata al metodo.

Per altre informazioni, vedere gli articoli seguenti:

Prerendering

Razor Per impostazione predefinita, i componenti vengono prerenderati in modo che il markup HTML sottoposto a rendering venga restituito come parte della richiesta HTTP iniziale dell'utente.

In un'app Blazor che opera su un circuito, il prerendering funziona in base a:

  • Creazione di un nuovo circuito per tutti i componenti prerisorsi che fanno parte della stessa pagina.
  • Generazione del codice HTML iniziale.
  • Considerando il circuito come disconnected finché il browser dell'utente non stabilisce una SignalR connessione allo stesso server. Quando viene stabilita la connessione, l'interattività sul circuito viene ripresa e il markup HTML dei componenti viene aggiornato.

Per i componenti lato client prerenderati, il prerendering funziona in base a:

  • Generazione di codice HTML iniziale nel server per tutti i componenti prerenderati che fanno parte della stessa pagina.
  • Rendere interattivo il componente sul client dopo che il browser ha caricato il codice compilato dell'app e il runtime .NET (se non è già stato caricato) in background.

Se un componente genera un'eccezione non gestita durante la pre-esecuzione, ad esempio, durante un metodo del ciclo di vita o nella logica di rendering:

  • In un'app Blazor che opera su un circuito, l'eccezione è irreversibile per il circuito. Per i componenti lato client prerenderati, l'eccezione impedisce il rendering del componente.
  • L'eccezione viene generata dallo stack di chiamate da ComponentTagHelper.

In circostanze normali in cui il prerendering non riesce, continuare a compilare ed eseguire il rendering del componente non ha senso perché non è possibile eseguire il rendering di un componente funzionante.

Per tollerare errori che possono verificarsi durante la pre-esecuzione, è necessario inserire la logica di gestione degli errori all'interno di un componente che potrebbe generare eccezioni. Usare try-catch istruzioni con la gestione e la registrazione degli errori. Anziché eseguire il wrapping di in un'istruzione try-catch , posizionare la ComponentTagHelper logica di gestione degli errori nel componente sottoposto a rendering da ComponentTagHelper.

Scenari avanzati

Rendering ricorsivo

I componenti possono essere annidati in modo ricorsivo. Ciò è utile per rappresentare strutture di dati ricorsive. Ad esempio, un TreeNode componente può eseguire il rendering di più TreeNode componenti per ognuno degli elementi figlio del nodo.

Quando si esegue il rendering in modo ricorsivo, evitare modelli di codifica che comportano una ricorsione infinita:

  • Non eseguire il rendering ricorsivo di una struttura di dati che contiene un ciclo. Ad esempio, non eseguire il rendering di un nodo della struttura ad albero i cui elementi figlio si includono.
  • Non creare una catena di layout che contengono un ciclo. Ad esempio, non creare un layout il cui layout è stesso.
  • Non consentire a un utente finale di violare le ricorsioni invarianti (regole) tramite chiamate di interoperabilità di dati dannose o JavaScript.

Cicli infiniti durante il rendering:

  • Fa sì che il processo di rendering continui per sempre.
  • Equivale alla creazione di un ciclo senza terminazione.

In questi scenari, l'errore Blazor ha esito negativo e in genere tenta di:

  • Utilizzare il tempo di CPU consentito dal sistema operativo per un periodo illimitato.
  • Utilizzare una quantità illimitata di memoria. L'utilizzo di memoria illimitata equivale allo scenario in cui un ciclo senza terminazione aggiunge voci a una raccolta in ogni iterazione.

Per evitare modelli di ricorsione infiniti, assicurarsi che il codice di rendering ricorsivo contenga condizioni di arresto appropriate.

Logica dell'albero di rendering personalizzata

La maggior parte dei Razor componenti viene implementata come Razor file di componente (.razor) e viene compilata dal framework per produrre logica che opera su un RenderTreeBuilder per eseguire il rendering dell'output. Tuttavia, uno sviluppatore può implementare RenderTreeBuilder manualmente la logica usando codice C# procedurale. Per altre informazioni, vedere scenari avanzati di ASP.NET Core Blazor (costruzione dell'albero di rendering).

Avviso

L'uso della logica manuale del generatore di alberi di rendering è considerato uno scenario avanzato e non sicuro, non consigliato per lo sviluppo generale dei componenti.

Se RenderTreeBuilder il codice viene scritto, lo sviluppatore deve garantire la correttezza del codice. Ad esempio, lo sviluppatore deve assicurarsi che:

  • Le chiamate a OpenElement e CloseElement sono bilanciate correttamente.
  • Gli attributi vengono aggiunti solo nelle posizioni corrette.

La logica del generatore di alberi di rendering manuale non corretta può causare comportamenti arbitrari, inclusi arresti anomali, blocchi di app o server e vulnerabilità di sicurezza.

Prendere in considerazione la logica manuale del generatore di alberi di rendering sullo stesso livello di complessità e con lo stesso livello di pericolo della scrittura manuale di codice assembly o istruzioni MSIL (Microsoft Intermediate Language) manualmente.

Risorse aggiuntive

†Applies alle app api Web di base ASP.NET back-end usate da app sul lato Blazor client per la registrazione.