Anteckning
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
Not
Det här är inte den senaste versionen av den här artikeln. För den aktuella versionen, se .NET 9-versionen av den här artikeln.
Varning
Den här versionen av ASP.NET Core stöds inte längre. Mer information finns i .NET och .NET Core Support Policy. Den aktuella versionen finns i .NET 9-versionen av den här artikeln.
Viktig
Den här informationen gäller en förhandsversionsprodukt som kan ändras avsevärt innan den släpps kommersiellt. Microsoft lämnar inga garantier, uttryckliga eller underförstådda, med avseende på den information som tillhandahålls här.
För den aktuella versionen, se .NET 9-versionen av den här artikeln.
Den här artikeln beskriver hur Blazor hanterar ohanterade undantag och hur du utvecklar appar som identifierar och hanterar fel.
Detaljerade fel under utveckling
När en Blazor app inte fungerar korrekt under utvecklingen hjälper det att få detaljerad felinformation från appen för att felsöka och åtgärda problemet. När ett fel inträffar visar Blazor appar ett ljusgult fält längst ned på skärmen:
- Under utvecklingen dirigerar fältet dig till webbläsarkonsolen, där du kan se undantaget.
- I produktion meddelar fältet användaren att ett fel har inträffat och rekommenderar att du uppdaterar webbläsaren.
Användargränssnittet för den här felhanteringsupplevelsen är en del av Blazor projektmallar. Alla versioner av Blazor projektmallar använder inte attributet data-nosnippet
för att signalera till webbläsare att inte cachelagrar innehållet i felgränssnittet, men alla versioner av Blazor dokumentation tillämpar attributet.
I en Blazor Web Appanpassar du upplevelsen i komponenten MainLayout
. Eftersom (till exempel <environment include="Production">...</environment>
) inte stöds i Razor-komponenter, injicerar följande exempel IHostEnvironment för att konfigurera felmeddelanden för olika miljöer.
Överst i MainLayout.razor
:
@inject IHostEnvironment HostEnvironment
Skapa eller ändra UI-markering för Blazor-fel:
<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>
I en Blazor Server app anpassar du upplevelsen i Pages/_Host.cshtml
-filen. I följande exempel används miljötagghjälp för att konfigurera felmeddelanden för olika miljöer.
I en Blazor Server app anpassar du upplevelsen i Pages/_Layout.cshtml
-filen. I följande exempel används miljötagghjälp för att konfigurera felmeddelanden för olika miljöer.
I en Blazor Server app anpassar du upplevelsen i Pages/_Host.cshtml
-filen. I följande exempel används miljötagghjälp för att konfigurera felmeddelanden för olika miljöer.
Skapa eller ändra UI-markering för Blazor-fel:
<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>
I en Blazor WebAssembly app anpassar du upplevelsen i wwwroot/index.html
-filen:
<div id="blazor-error-ui" data-nosnippet>
An unhandled error has occurred.
<a href="" class="reload">Reload</a>
<a class="dismiss">🗙</a>
</div>
Elementet blazor-error-ui
döljs normalt på grund av förekomsten av display: none
formatmallen för blazor-error-ui
CSS-klassen i appens automatiskt genererade formatmall. När ett fel inträffar gäller ramverket display: block
för elementet.
Elementet blazor-error-ui
döljs normalt på grund av förekomsten av display: none
formatmallen för blazor-error-ui
CSS-klassen i webbplatsens formatmall i mappen wwwroot/css
. När ett fel inträffar gäller ramverket display: block
för elementet.
Detaljerade kretsfel
Det här avsnittet gäller för Blazor Web Appsom fungerar över en krets.
Det här avsnittet gäller för Blazor Server appar.
Fel på klientsidan inkluderar inte anropsstacken och ger ingen information om orsaken till felet, men serverloggarna innehåller sådan information. I utvecklingssyfte kan känslig kretsfelinformation göras tillgänglig för klienten genom att aktivera detaljerade fel.
Ange CircuitOptions.DetailedErrors till true
. Mer information och ett exempel finns i ASP.NET Core BlazorSignalR vägledning.
Ett alternativ till att ange CircuitOptions.DetailedErrors är att ange DetailedErrors
konfigurationsnyckeln till true
i appens Development
miljöinställningsfil (appsettings.Development.json
). Ange dessutom SignalR loggning på serversidan (Microsoft.AspNetCore.SignalR
) till Felsökning eller Spårning för detaljerad SignalR loggning.
appsettings.Development.json
:
{
"DetailedErrors": true,
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information",
"Microsoft.AspNetCore.SignalR": "Debug"
}
}
}
Konfigurationsnyckeln för DetailedErrors kan också anges till true
med hjälp av miljövariabeln ASPNETCORE_DETAILEDERRORS
med värdet true
på Development
/Staging
miljöservrar eller i det lokala systemet.
Varning
Undvik alltid att exponera felinformation för klienter på Internet, vilket är en säkerhetsrisk.
Detaljerade fel för Razor komponentåtergivning på serversidan
Det här avsnittet gäller för Blazor Web Apps.
Använd alternativet RazorComponentsServiceOptions.DetailedErrors för att styra skapande av detaljerad information om fel för Razor komponentåtergivning på serversidan. Standardvärdet är false
.
I följande exempel aktiveras detaljerade fel:
builder.Services.AddRazorComponents(options =>
options.DetailedErrors = builder.Environment.IsDevelopment());
Varning
Aktivera endast detaljerade fel i Development
-miljön. Detaljerade fel kan innehålla känslig information om appen som skadliga användare kan använda i en attack.
Föregående exempel ger en viss säkerhetsnivå genom att ange värdet för DetailedErrors baserat på värdet som returneras av IsDevelopment. När appen finns i den Development
miljön är DetailedErrors inställd på true
. Det här tillvägagångssättet är inte idiotsäkert eftersom det är möjligt att köra en produktionsapp på en offentlig server i miljön Development
.
Hantera ohanterade undantag i utvecklarkod
För att en app ska kunna fortsätta efter ett fel måste appen ha felhanteringslogik. Senare avsnitt i den här artikeln beskriver potentiella källor till ohanterade undantag.
Rendera inte ramverksfelmeddelanden eller stackspårningar i användargränssnittet i produktion. Återgivning av undantagsmeddelanden eller stackspårningar kan:
- Lämna ut känslig information till slutanvändarna.
- Hjälp en illasinnad användare att upptäcka svagheter i en app som kan äventyra säkerheten för appen, servern eller nätverket.
Ohanterade undantag för kretsar
Det här avsnittet gäller för appar på serversidan som körs via en krets.
Razor-komponenter med aktiverad serverinteraktivitet är tillståndsfulla på servern. Medan användarna interagerar med komponenten på servern upprätthåller de en anslutning till servern som kallas för en krets. Kretsen innehåller aktiva komponentinstanser, plus många andra aspekter av tillståndet, till exempel:
- De senaste renderade utdata från komponenter.
- Den aktuella uppsättningen ombud för händelsehantering som kan utlösas av händelser på klientsidan.
Om en användare öppnar appen på flera webbläsarflikar skapar användaren flera oberoende kretsar.
Blazor behandlar de flesta ohanterade undantag som dödliga för kretsen där de inträffar. Om en krets avslutas på grund av ett ohanterat undantag kan användaren bara fortsätta att interagera med appen genom att läsa in sidan igen för att skapa en ny krets. Kretsar utanför den som avslutas, som är kretsar för andra användare eller andra webbläsarflikar, påverkas inte. Det här scenariot liknar en skrivbordsapp som kraschar. Den kraschade appen måste startas om, men andra appar påverkas inte.
Ramverket avslutar en krets när ett ohanterat undantag inträffar av följande skäl:
- Ett ohanterat undantag lämnar ofta kretsen i ett odefinierat tillstånd.
- Appens normala funktion kan inte garanteras efter ett ohanterat undantag.
- Säkerhetsrisker kan visas i appen om kretsen fortsätter i ett odefinierat tillstånd.
Global undantagshantering
Metoder för att hantera undantag globalt finns i följande avsnitt:
- Felgränser: Gäller för alla Blazor appar.
- Alternativ global undantagshantering: Gäller för Blazor Server, Blazor WebAssemblyoch Blazor Web App(8.0 eller senare) som använder ett globalt interaktivt återgivningsläge.
Felgränser
Felgränser ger en praktisk metod för att hantera undantag. Komponenten ErrorBoundary:
- Renderar dess underordnade innehåll när ett fel inte har inträffat.
- Renderar felgränssnittet när ett ohanterat undantag genereras av någon komponent inom felgränsen.
Om du vill definiera en felgräns använder du komponenten ErrorBoundary för att omsluta en eller flera andra komponenter. Felgränsen hanterar ohanterade undantag som genereras av de komponenter som den omsluter.
<ErrorBoundary>
...
</ErrorBoundary>
Om du vill implementera en felgräns på ett globalt sätt lägger du till gränsen runt brödtextinnehållet i appens huvudlayout.
I MainLayout.razor
:
<article class="content px-4">
<ErrorBoundary>
@Body
</ErrorBoundary>
</article>
I Blazor Web Appdär felgränsen endast tillämpas på en statisk MainLayout
-komponent är gränsen endast aktiv vid statisk rendering från servern (statisk SSR). Gränsen aktiveras inte bara för att en komponent längre ned i komponenthierarkin är interaktiv.
Det går inte att tillämpa ett interaktivt återgivningsläge på komponenten MainLayout
eftersom komponentens Body
parameter är ett RenderFragment ombud, vilket är godtycklig kod och inte kan serialiseras. Om du vill aktivera interaktivitet brett för komponenten MainLayout
och resten av komponenterna längre ned i komponenthierarkin måste appen använda ett globalt interaktivt återgivningsläge genom att tillämpa det interaktiva återgivningsläget på HeadOutlet
- och Routes
komponentinstanserna i appens rotkomponent, vilket vanligtvis är den App
komponenten. I följande exempel används återgivningsläget Interaktiv server (InteractiveServer
) globalt.
I Components/App.razor
:
<HeadOutlet @rendermode="InteractiveServer" />
...
<Routes @rendermode="InteractiveServer" />
Om du föredrar att inte aktivera global interaktivitet placerar du felgränsen längre ned i komponenthierarkin. De viktiga begreppen att tänka på är att varhelst felgränsen placeras:
- Om komponenten där felgränsen placeras inte är interaktiv kan felgränsen bara aktiveras på servern under statisk SSR. Gränsen kan till exempel aktiveras när ett fel utlöses i en komponents livscykelmetod, men inte för en händelse som utlöses av användarinteraktivitet i komponenten, till exempel ett fel som utlöses av en knappklickshanterare.
- Om komponenten där felgränsen placeras är interaktiv kan felgränsen aktiveras för interaktiva komponenter som den omsluter.
Not
Ovanstående överväganden är inte relevanta för fristående Blazor WebAssembly appar eftersom rendering på klientsidan (CSR) för en Blazor WebAssembly app är helt interaktiv.
Tänk på följande exempel, där ett undantag som kastas av en inbäddad räknarkomponent fångas av en felgräns i komponenten Home
, som tillämpar ett interaktivt återgivningsläge.
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>
Tänk på följande exempel, där ett undantag som genereras av en inbäddad räknarkomponent fångas av en felgräns i komponenten Home
.
EmbeddedCounter.razor
:
<h1>Embedded Counter</h1>
<p role="status">Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
private int currentCount = 0;
private void IncrementCount()
{
currentCount++;
if (currentCount > 5)
{
throw new InvalidOperationException("Current count is too big!");
}
}
}
Home.razor
:
@page "/"
<PageTitle>Home</PageTitle>
<h1>Home</h1>
<ErrorBoundary>
<EmbeddedCounter />
</ErrorBoundary>
Om det ohanterade undantaget genereras för en currentCount
över fem:
- Felet loggas normalt (
System.InvalidOperationException: Current count is too big!
). - Undantaget hanteras av felgränsen.
- Standardfelgränssnittet återges av felgränsen.
Komponenten ErrorBoundary renderar ett tomt <div>
-element med hjälp av blazor-error-boundary
CSS-klassen för dess felinnehåll. Färgerna, texten och ikonen för standardgränssnittet definieras i appens formatmall i mappen wwwroot
, så du kan anpassa felgränssnittet.
Så här ändrar du standardfelinnehållet:
- Omsluta komponenterna i felgränsen i egenskapen ChildContent.
- Ange egenskapen ErrorContent för felinnehållet.
I följande exempel omsluts EmbeddedCounter
komponenten och innehåller anpassat felinnehåll:
<ErrorBoundary>
<ChildContent>
<EmbeddedCounter />
</ChildContent>
<ErrorContent>
<p class="errorUI">😈 A rotten gremlin got us. Sorry!</p>
</ErrorContent>
</ErrorBoundary>
I föregående exempel innehåller appens formatmall förmodligen en errorUI
CSS-klass för att formatera innehållet. Felinnehållet återges från egenskapen ErrorContent utan element på blocknivå. Ett element på blocknivå, till exempel en division (<div>
) eller ett stycke (<p>
) element, kan omsluta felinnehållsmarkeringen, men det krävs inte.
Du kan också använda kontexten (@context
) för ErrorContent för att hämta feldata:
<ErrorContent>
@context.HelpLink
</ErrorContent>
ErrorContent kan också namnge kontexten. I följande exempel heter kontexten exception
:
<ErrorContent Context="exception">
@exception.HelpLink
</ErrorContent>
Varning
Undvik alltid att exponera felinformation för klienter på Internet, vilket är en säkerhetsrisk.
Om felgränsen definieras i appens layout visas felgränssnittet oavsett vilken sida användaren navigerar till när felet inträffar. Vi rekommenderar att du begränsar felgränserna i de flesta scenarier. Om du definierar en bred felgräns kan du återställa den till ett ofelskt tillstånd för senare sidhändelser genom att anropa felgränsens Recover-metod.
I MainLayout.razor
:
- Lägg till ett fält från ErrorBoundary till för att fånga en referens till det med attributdirektivet
@ref
. - I
OnParameterSet
livscykelmetodenkan du utlösa en återställning på felgränsen med Recover för att rensa felet när användaren navigerar till en annan komponent.
...
<ErrorBoundary @ref="errorBoundary">
@Body
</ErrorBoundary>
...
@code {
private ErrorBoundary? errorBoundary;
protected override void OnParametersSet()
{
errorBoundary?.Recover();
}
}
Om du vill undvika den oändliga loopen där återställning bara skickar om en komponent som genererar felet igen ska du inte anropa Recover från renderingslogik. Anropa endast Recover när:
- Användaren utför en UI-gest, till exempel genom att välja en knapp för att ange att de vill försöka utföra en procedur igen eller när användaren navigerar till en ny komponent.
- Ytterligare logik som körs rensar också undantaget. När komponenten har återskapats upprepas inte felet.
I följande exempel kan användaren återställa från undantaget med en knapp:
<ErrorBoundary @ref="errorBoundary">
<ChildContent>
<EmbeddedCounter />
</ChildContent>
<ErrorContent>
<div class="alert alert-danger" role="alert">
<p class="fs-3 fw-bold">😈 A rotten gremlin got us. Sorry!</p>
<p>@context.HelpLink</p>
<button class="btn btn-info" @onclick="_ => errorBoundary?.Recover()">
Clear
</button>
</div>
</ErrorContent>
</ErrorBoundary>
@code {
private ErrorBoundary? errorBoundary;
}
Du kan också subklassa ErrorBoundary för anpassad bearbetning genom att åsidosätta OnErrorAsync. I följande exempel loggas bara felet, men du kan implementera valfri felhanteringskod som du vill. Du kan ta bort raden som returnerar en CompletedTask om koden väntar på en asynkron uppgift.
CustomErrorBoundary.razor
:
@inherits ErrorBoundary
@inject ILogger<CustomErrorBoundary> Logger
@if (CurrentException is null)
{
@ChildContent
}
else if (ErrorContent is not null)
{
@ErrorContent(CurrentException)
}
@code {
protected override Task OnErrorAsync(Exception ex)
{
Logger.LogError(ex, "😈 A rotten gremlin got us. Sorry!");
return Task.CompletedTask;
}
}
Föregående exempel kan också implementeras som en klass.
CustomErrorBoundary.cs
:
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
namespace BlazorSample;
public class CustomErrorBoundary : ErrorBoundary
{
[Inject]
ILogger<CustomErrorBoundary> Logger { get; set; } = default!;
protected override Task OnErrorAsync(Exception ex)
{
Logger.LogError(ex, "😈 A rotten gremlin got us. Sorry!");
return Task.CompletedTask;
}
}
Någon av de föregående implementeringarna som används i en komponent:
<CustomErrorBoundary>
...
</CustomErrorBoundary>
Alternativ global undantagshantering
Den metod som beskrivs i det här avsnittet gäller för Blazor Server, Blazor WebAssemblyoch Blazor Web Appsom använder ett globalt interaktivt återgivningsläge (InteractiveServer
, InteractiveWebAssembly
eller InteractiveAuto
). Metoden fungerar inte med Blazor Web Appsom använder återgivningslägen per sida/komponent eller statisk återgivning på serversidan (statisk SSR) eftersom metoden förlitar sig på en CascadingValue
/CascadingParameter
, som inte fungerar över gränserna för återgivningsläge eller med komponenter som använder statisk SSR.
Ett alternativ till att använda Felgränser (ErrorBoundary) är att skicka en anpassad felkomponent som en CascadingValue
till underordnade komponenter. En fördel med att använda en komponent jämfört med att använda en inmatad tjänst eller en anpassad loggningsimplementering är att en överlappande komponent kan återge innehåll och tillämpa CSS-format när ett fel inträffar.
Följande ProcessError
komponentexempel loggar bara fel, men komponentens metoder kan bearbeta fel på alla sätt som krävs av appen, inklusive genom användning av flera felbearbetningsmetoder.
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();
}
}
Not
Mer information om RenderFragmentfinns i ASP.NET Core Razor-komponenter.
När du använder den här metoden i en Blazor Web Appöppnar du komponenten Routes
och omsluter komponenten Router (<Router>...</Router>
) med komponenten ProcessError
. Detta gör att ProcessError
-komponenten kan sprida sig ner till vilken komponent som helst i appen där ProcessError
-komponenten tas emot som en CascadingParameter
.
I Routes.razor
:
<ProcessError>
<Router ...>
...
</Router>
</ProcessError>
När du använder den här metoden i en Blazor Server- eller Blazor WebAssembly-app öppnar du komponenten App
, omsluter Router komponenten (<Router>...</Router>
) med komponenten ProcessError
. Detta gör att den ProcessError
-komponenten kan kaskadera ner till vilken komponent som helst i appen där den ProcessError
-komponenten tas emot som en CascadingParameter
.
I App.razor
:
<ProcessError>
<Router ...>
...
</Router>
</ProcessError>
Så här bearbetar du fel i en komponent:
Ange komponenten
ProcessError
som enCascadingParameter
i@code
-blocket. I ett exempelCounter
komponent i en app baserat på en Blazor projektmall lägger du till följandeProcessError
egenskap:[CascadingParameter] public ProcessError? ProcessError { get; set; }
Anropa en felbearbetningsmetod i alla
catch
block med en lämplig undantagstyp. ExempletProcessError
komponent erbjuder bara en endaLogError
-metod, men komponenten för felbearbetning kan ge valfritt antal felbearbetningsmetoder för att hantera alternativa felbearbetningskrav i hela appen. FöljandeCounter
-komponent@code
blockexempel innehåller den sammanhängande parameternProcessError
och fångar ett undantag för loggning av när antalet är större än fem:@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); } } }
Det loggade felet:
fail: {COMPONENT NAMESPACE}.ProcessError[0]
ProcessError.LogError: System.InvalidOperationException Message: Current count is over five!
Om LogError
-metoden direkt deltar i återgivningen, till exempel om du visar ett anpassat felmeddelandefält eller ändrar CSS-formatmallarna för de renderade elementen, anropar du StateHasChanged
i slutet av LogError
-metoden för att återskapa användargränssnittet.
Eftersom metoderna i det här avsnittet hanterar fel med en try-catch
-instruktion bryts inte appens SignalR anslutning mellan klienten och servern när ett fel inträffar och kretsen förblir aktiv. Andra ohanterade undantag är fortfarande dödliga för en krets. Mer information finns i avsnittet om hur en krets reagerar på ohanterade undantag.
En app kan använda en felbearbetningskomponent som ett sammanhängande värde för att bearbeta fel på ett centraliserat sätt.
Följande ProcessError
komponent skickar sig själv som en CascadingValue
till underordnade komponenter. I följande exempel loggas bara felet, men komponentens metoder kan bearbeta fel på alla sätt som krävs av appen, bland annat med hjälp av flera felbearbetningsmetoder. En fördel med att använda en komponent jämfört med att använda en inmatad tjänst eller en anpassad loggningsimplementering är att en överlappande komponent kan återge innehåll och tillämpa CSS-format när ett fel inträffar.
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);
}
}
Not
Mer information om RenderFragmentfinns i ASP.NET Core Razor-komponenter.
I komponenten App
omsluter du Router komponenten med komponenten ProcessError
. Detta gör att ProcessError
-komponenten kan sprida sig till vilken komponent som helst i appen där ProcessError
-komponenten tas emot som en CascadingParameter
.
App.razor
:
<ProcessError>
<Router ...>
...
</Router>
</ProcessError>
Så här bearbetar du fel i en komponent:
Ange komponenten
ProcessError
som enCascadingParameter
i@code
blocket:[CascadingParameter] public ProcessError ProcessError { get; set; }
Anropa en felbearbetningsmetod i alla
catch
block med en lämplig undantagstyp. ExempletProcessError
komponent erbjuder bara en endaLogError
-metod, men komponenten för felbearbetning kan ge valfritt antal felbearbetningsmetoder för att hantera alternativa felbearbetningskrav i hela appen.try { ... } catch (Exception ex) { ProcessError.LogError(ex); }
Med hjälp av den föregående exempelkomponenten ProcessError
och metoden LogError
visar webbläsarens konsol för utvecklingsverktyg det fångade och loggade felet:
fail: {COMPONENT NAMESPACE}.Shared.ProcessError[0]
ProcessError.LogError: System.NullReferenceException Message: Object reference not set to an instance of an object.
Om LogError
-metoden direkt deltar i återgivningen, till exempel om du visar ett anpassat felmeddelandefält eller ändrar CSS-formatmallarna för de renderade elementen, anropar du StateHasChanged
i slutet av LogError
-metoden för att återskapa användargränssnittet.
Eftersom metoderna i det här avsnittet hanterar fel med en try-catch
-instruktion bryts inte en Blazor app SignalR-anslutning mellan klienten och servern när ett fel inträffar och kretsen förblir aktiv. Alla ohanterade undantag är dödliga för ett kretslopp. Mer information finns i avsnittet om hur en krets reagerar på ohanterade undantag.
Logga fel med en ihållande leverantör
Om ett ohanterat undantag inträffar loggas undantaget till ILogger instanser som konfigurerats i tjänstcontainern. Blazor-appar loggar konsolens utdata med konsolens loggprovider. Överväg att logga till en plats på servern (eller ett backend-API för klientapplikationer) med en leverantör som hanterar loggstorlek och loggrotation. Alternativt kan appen använda en APM-tjänst (Application Performance Management), till exempel Azure Application Insights (Azure Monitor).
Obs
Inbyggda Application Insights funktioner för att stödja appar på klientsidan och inbyggt Blazor ramverksstöd för Google Analytics- kan bli tillgängliga i framtida versioner av dessa tekniker. Mer information finns i Support App Insights i Blazor WASM-klientsidan (microsoft/ApplicationInsights-dotnet #2143) och webbanalys och diagnostik (innehåller länkar till community-implementeringar) (dotnet/aspnetcore #5461). Under tiden kan en app på klientsidan använda Application Insights JavaScript SDK med JS interop för att logga fel direkt till Application Insights från en app på klientsidan.
Under utvecklingen i en Blazor app som körs över en krets skickar appen vanligtvis fullständig information om undantag till webbläsarens konsol för att underlätta felsökning. I produktion skickas inte detaljerade fel till klienter, men de fullständiga detaljerna av ett undantag loggas på servern.
Du måste bestämma vilka incidenter som ska loggas och allvarlighetsgraden för loggade incidenter. Fientliga användare kanske kan utlösa fel avsiktligt. Logga till exempel inte en incident från ett fel där en okänd ProductId
anges i URL:en för en komponent som visar produktinformation. Alla fel bör inte behandlas som incidenter för loggning.
Mer information finns i följande artiklar:
‡Gäller för Blazor appar på serversidan och andra server-side ASP.NET Core-appar som är webb-API-backendappar för Blazor. Appar på klientsidan kan fånga och skicka felinformation på klienten till ett webb-API, som loggar felinformationen i en ihållande loggningstjänst.
Om ett ohanterat undantag inträffar loggas undantaget till ILogger instanser som konfigurerats i tjänstcontainern. Blazor-appar loggar konsolutdata med leverantören för konsolloggning. Överväg att logga till en mer permanent plats på servern genom att skicka felinformation till ett bakgrundswebb-API som använder en loggleverantör med hantering av loggstorlek och loggrotation. Alternativt kan serverdelswebb-API-appen använda en APM-tjänst (Application Performance Management), till exempel Azure Application Insights (Azure Monitor)†för att registrera felinformation som den tar emot från klienter.
Du måste bestämma vilka incidenter som ska loggas och allvarlighetsgraden för loggade incidenter. Fientliga användare kanske kan utlösa fel avsiktligt. Logga till exempel inte en incident från ett fel där en okänd ProductId
anges i URL:en för en komponent som visar produktinformation. Alla fel bör inte behandlas som incidenter för loggning.
Mer information finns i följande artiklar:
†Native Application Insights funktioner för att stödja klientappar och inbyggt Blazor ramverksstöd för Google Analytics kan bli tillgängliga i framtida versioner av dessa tekniker. Mer information finns i Support App Insights i Blazor WASM-klientsidan (microsoft/ApplicationInsights-dotnet #2143) och webbanalys och diagnostik (innehåller länkar till community-implementeringar) (dotnet/aspnetcore #5461). Under tiden kan en app på klientsidan använda Application Insights JavaScript SDK med JS interop för att logga fel direkt till Application Insights från en app på klientsidan.
‡Gäller för ASP.NET Core-appar på serversidan som är webb-API-backend-appar för Blazor-appar. Appar på klientsidan fångar och skickar felinformation till ett webb-API, som loggar felinformationen till en ihållande loggningstjänst.
Platser där fel kan uppstå
Ramverks- och appkod kan utlösa ohanterade undantag på någon av följande platser, som beskrivs ytterligare i följande avsnitt i den här artikeln:
Instansiering av komponent
När Blazor skapar en instans av en komponent:
- Konstruktorn för komponenten anropas.
- Konstruktorerna för DI-tjänster som tillhandahålls till komponentens konstruktor via
@inject
-direktivet eller attributet[Inject]
anropas.
Ett fel i en utförd konstruktor eller en setter för någon [Inject]
egenskap resulterar i ett ohanterat undantag och hindrar ramverket från att instansiera komponenten. Om appen körs över en krets misslyckas kretsen. Om konstruktorlogik kan utlösa undantag bör appen fånga undantagen med hjälp av en try-catch
-instruktion med felhantering och loggning.
Livscykelmetoder
Under en komponents livslängd anropar Blazorlivscykelmetoder. Om någon livscykelmetod utlöser ett undantag, synkront eller asynkront, är undantaget dödligt för en krets. För att komponenter ska kunna hantera fel i livscykelmetoder lägger du till logik för felhantering.
I följande exempel där OnParametersSetAsync anropar en metod för att hämta en produkt:
- Ett undantag som genereras i metoden
ProductRepository.GetProductByIdAsync
hanteras av entry-catch
-instruktion. - När
catch
-blocket körs:-
loadFailed
är inställt påtrue
, som används för att visa ett felmeddelande för användaren. - Felet loggas.
-
@page "/product-details/{ProductId:int?}"
@inject ILogger<ProductDetails> Logger
@inject IProductRepository Product
<PageTitle>Product Details</PageTitle>
<h1>Product Details Example</h1>
@if (details != null)
{
<h2>@details.ProductName</h2>
<p>
@details.Description
<a href="@details.Url">Company Link</a>
</p>
}
else if (loadFailed)
{
<h1>Sorry, we could not load this product due to an error.</h1>
}
else
{
<h1>Loading...</h1>
}
@code {
private ProductDetail? details;
private bool loadFailed;
[Parameter]
public int ProductId { get; set; }
protected override async Task OnParametersSetAsync()
{
try
{
loadFailed = false;
// Reset details to null to display the loading indicator
details = null;
details = await Product.GetProductByIdAsync(ProductId);
}
catch (Exception ex)
{
loadFailed = true;
Logger.LogWarning(ex, "Failed to load product {ProductId}", ProductId);
}
}
public class ProductDetail
{
public string? ProductName { get; set; }
public string? Description { get; set; }
public string? Url { get; set; }
}
/*
* Register the service in Program.cs:
* using static BlazorSample.Components.Pages.ProductDetails;
* builder.Services.AddScoped<IProductRepository, ProductRepository>();
*/
public interface IProductRepository
{
public Task<ProductDetail> GetProductByIdAsync(int id);
}
public class ProductRepository : IProductRepository
{
public Task<ProductDetail> GetProductByIdAsync(int id)
{
return Task.FromResult(
new ProductDetail()
{
ProductName = "Flowbee ",
Description = "The Revolutionary Haircutting System You've Come to Love!",
Url = "https://flowbee.com/"
});
}
}
}
@page "/product-details/{ProductId:int?}"
@inject ILogger<ProductDetails> Logger
@inject IProductRepository Product
<PageTitle>Product Details</PageTitle>
<h1>Product Details Example</h1>
@if (details != null)
{
<h2>@details.ProductName</h2>
<p>
@details.Description
<a href="@details.Url">Company Link</a>
</p>
}
else if (loadFailed)
{
<h1>Sorry, we could not load this product due to an error.</h1>
}
else
{
<h1>Loading...</h1>
}
@code {
private ProductDetail? details;
private bool loadFailed;
[Parameter]
public int ProductId { get; set; }
protected override async Task OnParametersSetAsync()
{
try
{
loadFailed = false;
// Reset details to null to display the loading indicator
details = null;
details = await Product.GetProductByIdAsync(ProductId);
}
catch (Exception ex)
{
loadFailed = true;
Logger.LogWarning(ex, "Failed to load product {ProductId}", ProductId);
}
}
public class ProductDetail
{
public string? ProductName { get; set; }
public string? Description { get; set; }
public string? Url { get; set; }
}
/*
* Register the service in Program.cs:
* using static BlazorSample.Components.Pages.ProductDetails;
* builder.Services.AddScoped<IProductRepository, ProductRepository>();
*/
public interface IProductRepository
{
public Task<ProductDetail> GetProductByIdAsync(int id);
}
public class ProductRepository : IProductRepository
{
public Task<ProductDetail> GetProductByIdAsync(int id)
{
return Task.FromResult(
new ProductDetail()
{
ProductName = "Flowbee ",
Description = "The Revolutionary Haircutting System You've Come to Love!",
Url = "https://flowbee.com/"
});
}
}
}
@page "/product-details/{ProductId:int}"
@using Microsoft.Extensions.Logging
@inject ILogger<ProductDetails> Logger
@inject IProductRepository ProductRepository
@if (details != null)
{
<h1>@details.ProductName</h1>
<p>@details.Description</p>
}
else if (loadFailed)
{
<h1>Sorry, we could not load this product due to an error.</h1>
}
else
{
<h1>Loading...</h1>
}
@code {
private ProductDetail? details;
private bool loadFailed;
[Parameter]
public int ProductId { get; set; }
protected override async Task OnParametersSetAsync()
{
try
{
loadFailed = false;
// Reset details to null to display the loading indicator
details = null;
details = await ProductRepository.GetProductByIdAsync(ProductId);
}
catch (Exception ex)
{
loadFailed = true;
Logger.LogWarning(ex, "Failed to load product {ProductId}", ProductId);
}
}
public class ProductDetail
{
public string? ProductName { get; set; }
public string? Description { get; set; }
}
public interface IProductRepository
{
public Task<ProductDetail> GetProductByIdAsync(int id);
}
}
@page "/product-details/{ProductId:int}"
@using Microsoft.Extensions.Logging
@inject ILogger<ProductDetails> Logger
@inject IProductRepository ProductRepository
@if (details != null)
{
<h1>@details.ProductName</h1>
<p>@details.Description</p>
}
else if (loadFailed)
{
<h1>Sorry, we could not load this product due to an error.</h1>
}
else
{
<h1>Loading...</h1>
}
@code {
private ProductDetail? details;
private bool loadFailed;
[Parameter]
public int ProductId { get; set; }
protected override async Task OnParametersSetAsync()
{
try
{
loadFailed = false;
// Reset details to null to display the loading indicator
details = null;
details = await ProductRepository.GetProductByIdAsync(ProductId);
}
catch (Exception ex)
{
loadFailed = true;
Logger.LogWarning(ex, "Failed to load product {ProductId}", ProductId);
}
}
public class ProductDetail
{
public string? ProductName { get; set; }
public string? Description { get; set; }
}
public interface IProductRepository
{
public Task<ProductDetail> GetProductByIdAsync(int id);
}
}
@page "/product-details/{ProductId:int}"
@using Microsoft.Extensions.Logging
@inject ILogger<ProductDetails> Logger
@inject IProductRepository ProductRepository
@if (details != null)
{
<h1>@details.ProductName</h1>
<p>@details.Description</p>
}
else if (loadFailed)
{
<h1>Sorry, we could not load this product due to an error.</h1>
}
else
{
<h1>Loading...</h1>
}
@code {
private ProductDetail details;
private bool loadFailed;
[Parameter]
public int ProductId { get; set; }
protected override async Task OnParametersSetAsync()
{
try
{
loadFailed = false;
// Reset details to null to display the loading indicator
details = null;
details = await ProductRepository.GetProductByIdAsync(ProductId);
}
catch (Exception ex)
{
loadFailed = true;
Logger.LogWarning(ex, "Failed to load product {ProductId}", ProductId);
}
}
public class ProductDetail
{
public string ProductName { get; set; }
public string Description { get; set; }
}
public interface IProductRepository
{
public Task<ProductDetail> GetProductByIdAsync(int id);
}
}
@page "/product-details/{ProductId:int}"
@using Microsoft.Extensions.Logging
@inject ILogger<ProductDetails> Logger
@inject IProductRepository ProductRepository
@if (details != null)
{
<h1>@details.ProductName</h1>
<p>@details.Description</p>
}
else if (loadFailed)
{
<h1>Sorry, we could not load this product due to an error.</h1>
}
else
{
<h1>Loading...</h1>
}
@code {
private ProductDetail details;
private bool loadFailed;
[Parameter]
public int ProductId { get; set; }
protected override async Task OnParametersSetAsync()
{
try
{
loadFailed = false;
// Reset details to null to display the loading indicator
details = null;
details = await ProductRepository.GetProductByIdAsync(ProductId);
}
catch (Exception ex)
{
loadFailed = true;
Logger.LogWarning(ex, "Failed to load product {ProductId}", ProductId);
}
}
public class ProductDetail
{
public string ProductName { get; set; }
public string Description { get; set; }
}
public interface IProductRepository
{
public Task<ProductDetail> GetProductByIdAsync(int id);
}
}
Återgivningslogik
Deklarativ kod i en Razor komponentfil (.razor
) kompileras till en C#-metod med namnet BuildRenderTree. När en komponent renderas körs och bygger BuildRenderTree upp en datastruktur som beskriver elementen, texten och de underordnade komponenterna i den renderade komponenten.
Renderingslogik kan utlösa ett undantag. Ett exempel på det här scenariot inträffar när @someObject.PropertyName
utvärderas men @someObject
är null
. För Blazor appar som körs över en krets är ett ohanterat undantag som genereras av renderingslogik ödesdigert för appens krets.
Om du vill förhindra en NullReferenceException i renderingslogik kontrollerar du om det finns ett null
objekt innan du kommer åt dess medlemmar. I följande exempel används egenskaperna hos person.Address
inte om person.Address
är 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>
}
Koden ovan förutsätter att person
inte är null
. Kodens struktur garanterar ofta att ett objekt finns vid den tidpunkt då komponenten återges. I dessa fall är det inte nödvändigt att söka efter null
i renderingslogik. I föregående exempel kan person
garanteras finnas eftersom person
skapas när komponenten instansieras, vilket visas i följande exempel:
@code {
private Person person = new();
...
}
Händelsehanterare
Kod på klientsidan utlöser anrop av C#-kod när händelsehanterare skapas med hjälp av:
@onclick
@onchange
- Andra
@on...
attribut @bind
Händelsehanterarkod kan utlösa ett ohanterat undantag i dessa scenarier.
Om appen anropar kod som kan misslyckas av externa skäl, fånga undantag med hjälp av ett try-catch
-uttalande med felhantering och loggning.
Om en händelsehanterare utlöser ett ohanterat undantag (till exempel misslyckas en databasfråga) som inte är fångad och hanteras av utvecklarkod:
- Ramverket loggar undantaget.
- I en Blazor app som körs över en krets är undantaget allvarligt för appens krets.
Bortskaffande av komponent
En komponent kan tas bort från användargränssnittet, till exempel eftersom användaren har navigerat till en annan sida. När en komponent som implementerar System.IDisposable tas bort från användargränssnittet anropar ramverket komponentens Dispose-metod.
Om komponentens Dispose
-metod genererar ett ohanterat undantag i en Blazor app som körs över en krets är undantaget allvarligt för appens krets.
Om bortskaffandelogik kan utlösa undantag bör appen fånga undantagen med hjälp av en try-catch
-instruktion med felhantering och loggning.
Mer information om bortskaffande av komponenter finns i ASP.NET Core Razor.
JavaScript-interoperabilitet
IJSRuntime registreras av Blazor ramverket. IJSRuntime.InvokeAsync tillåter att .NET-kod gör asynkrona anrop till JavaScript -körningen (JS) i användarens webbläsare.
Följande villkor gäller för felhantering med InvokeAsync:
- Om ett anrop till InvokeAsync misslyckas synkront uppstår ett .NET-undantag. Ett anrop till InvokeAsync kan till exempel misslyckas eftersom de angivna argumenten inte kan serialiseras. Utvecklarkoden måste fånga undantaget. Om appkod i en händelsehanterare eller komponentlivscykelmetod inte hanterar ett undantag i en Blazor app som körs över en krets är det resulterande undantaget allvarligt för appens krets.
- Om ett anrop till InvokeAsync misslyckas asynkront misslyckas .NET-Task. Ett anrop till InvokeAsync kan till exempel misslyckas eftersom koden JS-side utlöser ett undantag eller returnerar en
Promise
som har slutförts somrejected
. Utvecklarkoden måste fånga undantaget. Om du använder operatornawait
kan du överväga att omsluta metodanropet i entry-catch
-instruktion med felhantering och loggning. Annars i en Blazor app som körs över en krets resulterar den misslyckade koden i ett ohanterat undantag som är dödligt för appens krets. - Anrop till InvokeAsync måste slutföras inom en viss period, annars överskrids tidsgränsen för samtalet. Standardtidsgränsen är en minut. Tidsgränsen skyddar koden mot en förlust i nätverksanslutningen eller JS kod som aldrig skickar tillbaka ett slutförandemeddelande. Om anropet överskrider tidsgränsen misslyckas den resulterande System.Threading.Tasks på grund av en OperationCanceledException. Fånga och hantera undantaget med loggning.
På samma sätt kan JS kod initiera anrop till .NET-metoder som anges av attributet [JSInvokable]
. Om dessa .NET-metoder utlöser ett ohanterat undantag:
- I en Blazor app som körs över en krets behandlas undantaget inte som allvarligt för appens krets.
-
JS-sida
Promise
avvisas.
Du kan använda felhanteringskod på .NET-sidan eller på JS sidan av metodanropet.
Mer information finns i följande artiklar:
- Anropa JavaScript-funktioner från .NET-metoder i ASP.NET Core Blazor
- Anropa .NET-metoder från JavaScript-funktioner i ASP.NET Core Blazor
Förberendering
Razor komponenter är förinstallerade som standard så att deras renderade HTML-kod returneras som en del av användarens första HTTP-begäran.
I en Blazor app som körs över en krets fungerar prerendering genom att:
- Skapa en ny krets för alla förrenderade komponenter som ingår på samma sida.
- Genererar den första HTML-koden.
- Behandla kretsen som
disconnected
tills användarens webbläsare upprättar en SignalR anslutning tillbaka till samma server. När anslutningen upprättas återupptas interaktiviteten på kretsen och komponenternas HTML-kod uppdateras.
För förrenderade komponenter på klientsidan fungerar prerendering genom att:
- Genererar inledande HTML på servern för alla förrenderade komponenter som ingår på samma sida.
- Gör komponenten interaktiv på klienten när webbläsaren har läst in appens kompilerade kod och .NET-körningen (om den inte redan har lästs in) i bakgrunden.
Om en komponent genererar ett ohanterat undantag under förinläsning, till exempel under en livscykelmetod eller i renderingslogik:
- I en Blazor app som körs över en krets är undantaget allvarligt för kretsen. För förrenderade komponenter på klientsidan förhindrar undantaget att komponenten återges.
- Undantaget kastas uppför anropsstacken från ComponentTagHelper.
Under normala omständigheter när prerendering misslyckas är det inte meningsfullt att fortsätta att skapa och återge komponenten eftersom en fungerande komponent inte kan renderas.
Om du vill tolerera fel som kan uppstå under prerendering måste felhanteringslogik placeras i en komponent som kan utlösa undantag. Använd try-catch
-instruktioner med felhantering och loggningsfunktioner. I stället för att omsluta ComponentTagHelper i en try-catch
-instruktion placerar du felhanteringslogik i komponenten som återges av ComponentTagHelper.
Avancerade scenarier
Rekursiv återgivning
Komponenter kan kapslas rekursivt. Detta är användbart för att representera rekursiva datastrukturer. En TreeNode
-komponent kan till exempel återge fler TreeNode
-komponenter för var och en av nodens underordnade.
När du återger rekursivt bör du undvika kodningsmönster som resulterar i oändlig rekursion:
- Återge inte rekursivt en datastruktur som innehåller en cykel. Rendera till exempel inte en trädnod vars underordnade objekt inkluderar den själv.
- Skapa inte en kedja med layouter som innehåller en cykel. Skapa till exempel inte en layout vars layout är sig själv.
- Tillåt inte att en slutanvändare bryter mot rekursionsvarianter (regler) genom inmatning av skadliga data eller JavaScript-interop-anrop.
Oändliga loopar under återgivning:
- Gör att renderingsprocessen fortsätter för alltid.
- Motsvarar att skapa en obestämd loop.
I dessa scenarier misslyckas Blazor och försöker vanligtvis:
- Förbruka så mycket CPU-tid som operativsystemet tillåter, på obestämd tid.
- Förbruka en obegränsad mängd minne. Användning av obegränsat minne motsvarar scenariot där en obestämd loop lägger till poster i en samling vid varje iteration.
Se till att rekursiv återgivningskod innehåller lämpliga stoppvillkor för att undvika oändliga rekursionsmönster.
Logik för anpassat återgivningsträd
De flesta Razor komponenter implementeras som Razor komponentfiler (.razor
) och kompileras av ramverket för att skapa logik som fungerar på en RenderTreeBuilder för att återge deras utdata. En utvecklare kan dock implementera RenderTreeBuilder logik manuellt med hjälp av C#-procedurkod. Mer information finns i ASP.NET Core Blazor avancerade scenarier (rendera trädkonstruktion).
Varning
Användning av logik för manuellt återgivningsträd anses vara ett avancerat och osäkert scenario, vilket inte rekommenderas för allmän komponentutveckling.
Om RenderTreeBuilder kod skrivs måste utvecklaren garantera att koden är korrekt. Utvecklaren måste till exempel se till att:
- Anrop till OpenElement och CloseElement är korrekt balanserade.
- Attribut läggs bara till på rätt platser.
Felaktig logik för manuell återgivning av trädbyggare kan orsaka godtyckligt odefinierat beteende, till exempel krascher, att appen eller servern slutar svara och säkerhetsproblem.
Överväg manuell återgivning av trädbyggarlogik på samma komplexitetsnivå och med samma nivå av fara som att skriva sammansättningskod eller Microsoft Intermediate Language (MSIL) instruktioner för hand.
Ytterligare resurser
†Gäller backend ASP.NET Core web API-appar som klient-sida Blazor-program använder för loggning.
ASP.NET Core