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.

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'attributodata-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 la gestione delle eccezioni globali, vedere le sezioni seguenti:

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 viene generata un'eccezione non gestita.

Per definire un limite di errore, usare il componente per eseguire il wrapping del ErrorBoundary contenuto esistente. L'app continua a funzionare normalmente, ma il limite di errore gestisce le eccezioni non gestite.

<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 la fase di rendering statico lato server (SSR statico). Il limite non viene attivato solo perché un componente più in basso nella gerarchia dei componenti è interattivo. Per abilitare l'interattività su larga scala per il MainLayout componente e il resto dei componenti più in basso nella gerarchia dei componenti, abilitare il rendering interattivo per le istanze dei HeadOutlet componenti e Routes nel App componente (Components/App.razor). L'esempio seguente adotta la modalità di rendering Interactive Server (InteractiveServer):

<HeadOutlet @rendermode="InteractiveServer" />

...

<Routes @rendermode="InteractiveServer" />

Se si preferisce non abilitare l'interattività del server nell'intera app dal Routes componente, posizionare il limite di errore più in basso nella gerarchia dei componenti. Ad esempio, posizionare il limite di errore intorno al markup nei singoli componenti che abilitano l'interattività, non nel layout principale dell'app. I concetti importanti da tenere presente sono che ovunque si trovi il limite di errore:

  • Se il limite di errore non è interattivo, è in grado di attivare sul server solo durante il rendering statico. Ad esempio, il limite può essere attivato quando viene generato un errore in un metodo del ciclo di vita del componente.
  • Se il limite di errore è interattivo, è in grado di attivare per i componenti di cui è stato eseguito il rendering di Interactive Server.

Si consideri l'esempio seguente, in cui il Counter componente genera un'eccezione se il conteggio aumenta oltre cinque.

In Counter.razor:

private void IncrementCount()
{
    currentCount++;

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

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 viene eseguito dal limite di errore con il messaggio di errore predefinito seguente: An error has occurred.

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

Modificare il contenuto di errore predefinito impostando la ErrorContent proprietà :

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

Poiché il limite di errore è definito nel layout negli esempi precedenti, l'interfaccia utente dell'errore viene visualizzata indipendentemente dalla pagina a cui l'utente passa dopo 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.
  • La logica aggiuntiva cancella anche l'eccezione. Quando il componente viene rerendered, l'errore non viene eseguito di nuovo.

Gestione alternativa delle eccezioni globali

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

Error.razor:

@inject ILogger<Error> Logger

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

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

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

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

Nota

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

Routes Nel componente eseguire il wrapping del Router componente (<Router>...</Router>) con il Error componente . Ciò consente al Error componente di propagarsi verso il basso a qualsiasi componente dell'app in cui il Error componente viene ricevuto come .CascadingParameter

In Routes.razor:

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

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

In App.razor:

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

Per elaborare gli errori in un componente:

  • Designare il Error 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 Error :

    [CascadingParameter]
    public Error? Error { get; set; }
    
  • Chiamare un metodo di elaborazione degli errori in qualsiasi catch blocco con un tipo di eccezione appropriato. Il componente di esempio Error offre un solo ProcessError 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. Nell'esempio di componente seguente Counter viene generata un'eccezione e intrappolata quando il conteggio è maggiore di cinque:

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

Usando il componente precedente con le modifiche precedenti Error apportate a un Counter componente, la console degli strumenti di sviluppo del browser indica l'errore bloccato e registrato:

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

Se il ProcessError 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 ProcessErrors 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 Error 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.

Error.razor:

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

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

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

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

Nota

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

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

App.razor:

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

Per elaborare gli errori in un componente:

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

    [CascadingParameter]
    public Error Error { get; set; }
    
  • Chiamare un metodo di elaborazione degli errori in qualsiasi catch blocco con un tipo di eccezione appropriato. Il componente di esempio Error offre un solo ProcessError 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)
    {
        Error.ProcessError(ex);
    }
    

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

fail: BlazorSample.Shared.Error[0] Error:ProcessError - Type: System.NullReferenceException Message: Object reference not set to an instance of an object.

Se il ProcessError 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 ProcessErrors 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.