Bagikan melalui


Menangani kesalahan di aplikasi ASP.NET Core Blazor

Catatan

Ini bukan versi terbaru dari artikel ini. Untuk rilis saat ini, lihat versi .NET 8 dari artikel ini.

Penting

Informasi ini berkaitan dengan produk pra-rilis yang mungkin dimodifikasi secara substansial sebelum dirilis secara komersial. Microsoft tidak memberikan jaminan, tersirat maupun tersurat, sehubungan dengan informasi yang diberikan di sini.

Untuk rilis saat ini, lihat versi .NET 8 dari artikel ini.

Artikel ini menjelaskan cara Blazor mengelola pengecualian yang tidak tertangani dan cara mengembangkan aplikasi yang mendeteksi dan menangani kesalahan.

Kesalahan terperinci selama pengembangan

Blazor Saat aplikasi tidak berfungsi dengan baik selama pengembangan, menerima informasi kesalahan terperinci dari aplikasi membantu pemecahan masalah dan memperbaiki masalah. Saat terjadi kesalahan, Blazor aplikasi menampilkan bilah kuning muda di bagian bawah layar:

  • Selama pengembangan, bilah mengarahkan Anda ke konsol browser, tempat Anda dapat melihat pengecualian.
  • Dalam produksi, bilah memberi tahu pengguna bahwa kesalahan telah terjadi dan merekomendasikan penyegaran browser.

Antarmuka pengguna untuk pengalaman penanganan kesalahan ini adalah bagian Blazor dari templat proyek. Tidak semua versi Blazor templat proyek menggunakan data-nosnippet atribut untuk memberi sinyal ke browser untuk tidak menyimpan konten antarmuka pengguna kesalahan, tetapi semua versi Blazor dokumentasi menerapkan atribut .

Blazor Di Aplikasi Web, kustomisasi pengalaman dalam MainLayout komponen. Karena Pembantu Tag Lingkungan (misalnya, <environment include="Production">...</environment>) tidak didukung dalam Razor komponen, contoh berikut menyuntikkan IHostEnvironment untuk mengonfigurasi pesan kesalahan untuk lingkungan yang berbeda.

Di bagian MainLayout.razoratas :

@inject IHostEnvironment HostEnvironment

Buat atau ubah Blazor markup antarmuka pengguna kesalahan:

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

Di aplikasi Blazor Server , sesuaikan pengalaman dalam Pages/_Host.cshtml file. Contoh berikut menggunakan Pembantu Tag Lingkungan untuk mengonfigurasi pesan kesalahan untuk lingkungan yang berbeda.

Di aplikasi Blazor Server , sesuaikan pengalaman dalam Pages/_Layout.cshtml file. Contoh berikut menggunakan Pembantu Tag Lingkungan untuk mengonfigurasi pesan kesalahan untuk lingkungan yang berbeda.

Di aplikasi Blazor Server , sesuaikan pengalaman dalam Pages/_Host.cshtml file. Contoh berikut menggunakan Pembantu Tag Lingkungan untuk mengonfigurasi pesan kesalahan untuk lingkungan yang berbeda.

Buat atau ubah Blazor markup antarmuka pengguna kesalahan:

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

Di aplikasi Blazor WebAssembly , sesuaikan pengalaman dalam 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>

Elemen blazor-error-ui ini biasanya disembunyikan karena adanya display: none gaya blazor-error-ui kelas CSS di stylesheet yang dihasilkan secara otomatis dari aplikasi. Ketika kesalahan terjadi, kerangka kerja berlaku display: block untuk elemen .

Elemen blazor-error-ui biasanya tersembunyi karena adanya display: none gaya blazor-error-ui kelas CSS di lembar gaya situs di wwwroot/css folder. Ketika kesalahan terjadi, kerangka kerja berlaku display: block untuk elemen .

Kesalahan sirkuit terperinci

Bagian ini berlaku untuk Blazor Web Apps yang beroperasi melalui sirkuit.

Bagian ini berlaku untuk Blazor Server aplikasi.

Kesalahan sisi klien tidak menyertakan tumpukan panggilan dan tidak memberikan detail tentang penyebab kesalahan, tetapi log server berisi informasi tersebut. Untuk tujuan pengembangan, informasi kesalahan sirkuit sensitif dapat disediakan untuk klien dengan mengaktifkan kesalahan terperinci.

Atur CircuitOptions.DetailedErrors ke true. Untuk informasi selengkapnya dan contohnya, lihat panduan ASP.NET CoreBlazorSignalR.

Alternatif untuk pengaturan CircuitOptions.DetailedErrors adalah mengatur DetailedErrors kunci konfigurasi ke true dalam file pengaturan lingkungan aplikasi Development (appsettings.Development.json). Selain itu, atur SignalR pengelogan sisi server (Microsoft.AspNetCore.SignalR) ke Debug atau Lacak untuk pengelogan terperinci SignalR .

appsettings.Development.json:

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

Kunci DetailedErrors konfigurasi juga dapat diatur ke true menggunakan ASPNETCORE_DETAILEDERRORS variabel lingkungan dengan nilai true pada Development/Staging server lingkungan atau pada sistem lokal Anda.

Peringatan

Selalu hindari mengekspos informasi kesalahan kepada klien di Internet, yang merupakan risiko keamanan.

Kesalahan terperinci untuk Razor penyajian sisi server komponen

Bagian ini berlaku untuk Blazor Web Apps.

RazorComponentsServiceOptions.DetailedErrors Gunakan opsi untuk mengontrol menghasilkan informasi terperinci tentang kesalahan untuk Razor penyajian sisi server komponen. Nilai defaultnya adalah false.

Contoh berikut memungkinkan kesalahan terperinci:

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

Peringatan

Hanya aktifkan kesalahan terperinci di Development lingkungan. Kesalahan terperinci mungkin berisi informasi sensitif tentang aplikasi yang dapat digunakan pengguna berbahaya dalam serangan.

Contoh sebelumnya memberikan tingkat keamanan dengan menetapkan nilai DetailedErrors berdasarkan nilai yang dikembalikan oleh IsDevelopment. Saat aplikasi berada di Development lingkungan, DetailedErrors diatur ke true. Pendekatan ini tidak mudah karena dimungkinkan untuk menghosting aplikasi produksi di server publik di Development lingkungan.

Mengelola pengecualian yang tidak tertangani dalam kode pengembang

Agar aplikasi berlanjut setelah kesalahan, aplikasi harus memiliki logika penanganan kesalahan. Bagian selanjutnya dari artikel ini menjelaskan sumber potensial pengecualian yang tidak tertangani.

Dalam produksi, jangan merender pesan pengecualian kerangka kerja atau jejak tumpukan di UI. Merender pesan pengecualian atau jejak tumpukan dapat:

  • Mengungkapkan informasi sensitif kepada pengguna akhir.
  • Bantu pengguna berbahaya menemukan kelemahan di aplikasi yang dapat membahayakan keamanan aplikasi, server, atau jaringan.

Pengecualian yang tidak tertangani untuk sirkuit

Bagian ini berlaku untuk aplikasi sisi server yang beroperasi melalui sirkuit.

Razor komponen dengan interaktivitas server diaktifkan bersifat stateful di server. Saat pengguna berinteraksi dengan komponen di server, mereka mempertahankan koneksi ke server yang dikenal sebagai sirkuit. Sirkuit ini menyimpan instans komponen aktif, ditambah banyak aspek status lainnya, seperti:

  • Output komponen yang dirender terbaru.
  • Kumpulan delegasi penanganan peristiwa saat ini yang dapat dipicu oleh peristiwa sisi klien.

Jika pengguna membuka aplikasi di beberapa tab browser, pengguna membuat beberapa sirkuit independen.

Blazor memperlakukan pengecualian yang paling tidak tertangani sebagai fatal bagi sirkuit tempat mereka terjadi. Jika sirkuit dihentikan karena pengecualian yang tidak tertangani, pengguna hanya dapat terus berinteraksi dengan aplikasi dengan memuat ulang halaman untuk membuat sirkuit baru. Sirkuit di luar sirkuit yang dihentikan, yang merupakan sirkuit untuk pengguna lain atau tab browser lainnya, tidak terpengaruh. Skenario ini mirip dengan aplikasi desktop yang mengalami crash. Aplikasi yang mengalami crash harus dimulai ulang, tetapi aplikasi lain tidak terpengaruh.

Kerangka kerja mengakhiri sirkuit ketika pengecualian yang tidak tertangani terjadi karena alasan berikut:

  • Pengecualian yang tidak tertangani sering meninggalkan sirkuit dalam keadaan tidak terdefinisi.
  • Operasi normal aplikasi tidak dapat dijamin setelah pengecualian yang tidak tertangani.
  • Kerentanan keamanan dapat muncul di aplikasi jika sirkuit berlanjut dalam status tidak terdefinisi.

Penanganan pengecualian global

Untuk penanganan pengecualian global, lihat bagian berikut ini:

Batas kesalahan

Batas kesalahan memberikan pendekatan yang nyaman untuk menangani pengecualian. Komponen ErrorBoundary :

  • Merender konten turunannya ketika kesalahan belum terjadi.
  • Merender UI kesalahan ketika pengecualian yang tidak tertangani dilemparkan.

Untuk menentukan batas kesalahan, gunakan ErrorBoundary komponen untuk membungkus konten yang ada. Aplikasi terus berfungsi secara normal, tetapi batas kesalahan menangani pengecualian yang tidak tertangani.

<ErrorBoundary>
    ...
</ErrorBoundary>

Untuk menerapkan batas kesalahan dengan cara global, tambahkan batas di sekitar konten isi tata letak utama aplikasi.

Di MainLayout.razor:

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

Di Blazor Web Apps dengan batas kesalahan hanya diterapkan ke komponen statis MainLayout , batas hanya aktif selama fase penyajian sisi server statis (SSR statis). Batas tidak diaktifkan hanya karena komponen lebih jauh ke hierarki komponen bersifat interaktif. Untuk mengaktifkan interaktivitas secara luas untuk MainLayout komponen dan komponen lainnya lebih jauh ke hierarki komponen, aktifkan penyajian interaktif untuk HeadOutlet instans komponen dan Routes dalam App komponen (Components/App.razor). Contoh berikut mengadopsi mode render Server Interaktif (InteractiveServer):

<HeadOutlet @rendermode="InteractiveServer" />

...

<Routes @rendermode="InteractiveServer" />

Jika Anda lebih suka tidak mengaktifkan interaktivitas server di seluruh aplikasi dari Routes komponen, letakkan batas kesalahan lebih jauh ke hierarki komponen. Misalnya, tempatkan batas kesalahan di sekitar markup di komponen individual yang memungkinkan interaktivitas, bukan di tata letak utama aplikasi. Konsep penting yang perlu diingat adalah bahwa di mana pun batas kesalahan ditempatkan:

  • Jika batas kesalahan tidak interaktif, itu hanya mampu mengaktifkan di server selama penyajian statis. Misalnya, batas dapat diaktifkan ketika kesalahan dilemparkan dalam metode siklus hidup komponen.
  • Jika batas kesalahan interaktif, ia mampu mengaktifkan komponen yang dirender Server Interaktif yang dibungkusnya.

Pertimbangkan contoh berikut, di mana Counter komponen melemparkan pengecualian jika kenaikan jumlah melewati lima.

Di Counter.razor:

private void IncrementCount()
{
    currentCount++;

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

Jika pengecualian yang tidak tertangani dilemparkan selama currentCount lebih dari lima:

  • Kesalahan dicatat secara normal (System.InvalidOperationException: Current count is too big!).
  • Pengecualian ditangani oleh batas kesalahan.
  • UI kesalahan dirender oleh batas kesalahan dengan pesan kesalahan default berikut: An error has occurred.

Secara default, ErrorBoundary komponen merender elemen kosong <div> dengan blazor-error-boundary kelas CSS untuk konten kesalahannya. Warna, teks, dan ikon untuk UI default didefinisikan menggunakan CSS di lembar gaya aplikasi di wwwroot folder, sehingga Anda bebas untuk menyesuaikan antarmuka pengguna kesalahan.

Ubah konten kesalahan default dengan mengatur ErrorContent properti :

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

Karena batas kesalahan ditentukan dalam tata letak dalam contoh sebelumnya, antarmuka pengguna kesalahan terlihat terlepas dari halaman mana pengguna menavigasi setelah kesalahan terjadi. Sebaiknya cakupan batas kesalahan secara sempit dalam sebagian besar skenario. Jika Anda secara luas mencakup batas kesalahan, Anda dapat mengatur ulang ke status non-kesalahan pada peristiwa navigasi halaman berikutnya dengan memanggil metode batas Recover kesalahan.

Di MainLayout.razor:

...

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

...

@code {
    private ErrorBoundary? errorBoundary;

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

Untuk menghindari perulangan tak terbatas di mana pemulihan hanya merender komponen yang melemparkan kesalahan lagi, jangan panggil Recover dari logika penyajian. Hanya panggil Recover saat:

  • Pengguna melakukan gerakan UI, seperti memilih tombol untuk menunjukkan bahwa mereka ingin mencoba kembali prosedur atau ketika pengguna menavigasi ke komponen baru.
  • Logika tambahan juga menghapus pengecualian. Ketika komponen dirender ulang, kesalahan tidak terulang kembali.

Penanganan pengecualian global alternatif

Alternatif untuk menggunakan Batas kesalahan (ErrorBoundary) adalah meneruskan komponen kesalahan kustom sebagai CascadingValue komponen ke anak. Keuntungan menggunakan komponen daripada menggunakan layanan yang disuntikkan atau implementasi pencatat kustom adalah bahwa komponen bertingkat dapat merender konten dan menerapkan gaya CSS saat kesalahan terjadi.

Contoh komponen berikut Error hanya mencatat kesalahan, tetapi metode komponen dapat memproses kesalahan dengan cara apa pun yang diperlukan oleh aplikasi, termasuk melalui penggunaan beberapa metode pemrosesan kesalahan.

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

Catatan

Untuk informasi selengkapnya tentang RenderFragment, lihat komponen ASP.NET CoreRazor.

Routes Dalam komponen, bungkus Router komponen (<Router>...</Router>) dengan Error komponen . Hal ini memungkinkan Error komponen untuk melakukan cascade ke komponen aplikasi mana pun di mana Error komponen diterima sebagai CascadingParameter.

Di Routes.razor:

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

App Dalam komponen, bungkus Router komponen (<Router>...</Router>) dengan Error komponen . Hal ini memungkinkan Error komponen untuk melakukan cascade ke komponen aplikasi mana pun di mana Error komponen diterima sebagai CascadingParameter.

Di App.razor:

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

Untuk memproses kesalahan dalam komponen:

  • Menunjuk Error komponen sebagai CascadingParameter di @code blok. Dalam contoh Counter komponen dalam aplikasi berdasarkan Blazor templat proyek, tambahkan properti berikut Error :

    [CascadingParameter]
    public Error? Error { get; set; }
    
  • Panggil metode pemrosesan kesalahan di blok mana pun catch dengan jenis pengecualian yang sesuai. Komponen contoh Error hanya menawarkan satu ProcessError metode, tetapi komponen pemrosesan kesalahan dapat menyediakan sejumlah metode pemrosesan kesalahan untuk mengatasi persyaratan pemrosesan kesalahan alternatif di seluruh aplikasi. Dalam contoh komponen berikut Counter , pengecualian dilemparkan dan terperangkap ketika jumlahnya lebih besar dari lima:

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

Menggunakan komponen sebelumnya Error dengan perubahan sebelumnya yang Counter dilakukan pada komponen, konsol alat pengembang browser menunjukkan kesalahan yang terperangkap dan dicatat:

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

Jika metode secara ProcessError langsung berpartisipasi dalam penyajian, seperti memperlihatkan bilah pesan kesalahan kustom atau mengubah gaya CSS elemen yang dirender, panggil StateHasChanged di akhir ProcessErrors metode untuk merender UI.

Karena pendekatan di bagian ini menangani kesalahan dengan try-catch pernyataan, koneksi aplikasi SignalR antara klien dan server tidak rusak ketika kesalahan terjadi dan sirkuit tetap hidup. Pengecualian lain yang tidak tertangani tetap fatal bagi sirkuit. Untuk informasi selengkapnya, lihat bagian tentang bagaimana sirkuit bereaksi terhadap pengecualian yang tidak tertangani.

Aplikasi dapat menggunakan komponen pemrosesan kesalahan sebagai nilai berkala untuk memproses kesalahan dengan cara terpusat.

Komponen berikut Error meneruskan dirinya sebagai CascadingValue komponen ke anak. Contoh berikut hanya mencatat kesalahan, tetapi metode komponen dapat memproses kesalahan dengan cara apa pun yang diperlukan oleh aplikasi, termasuk melalui penggunaan beberapa metode pemrosesan kesalahan. Keuntungan menggunakan komponen daripada menggunakan layanan yang disuntikkan atau implementasi pencatat kustom adalah bahwa komponen bertingkat dapat merender konten dan menerapkan gaya CSS saat kesalahan terjadi.

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

Catatan

Untuk informasi selengkapnya tentang RenderFragment, lihat komponen ASP.NET CoreRazor.

App Dalam komponen, bungkus Router komponen dengan Error komponen . Hal ini memungkinkan Error komponen untuk melakukan cascade ke komponen aplikasi mana pun di mana Error komponen diterima sebagai CascadingParameter.

App.razor:

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

Untuk memproses kesalahan dalam komponen:

  • Tetapkan Error komponen sebagai CascadingParameter dalam @code blok:

    [CascadingParameter]
    public Error Error { get; set; }
    
  • Panggil metode pemrosesan kesalahan di blok mana pun catch dengan jenis pengecualian yang sesuai. Komponen contoh Error hanya menawarkan satu ProcessError metode, tetapi komponen pemrosesan kesalahan dapat menyediakan sejumlah metode pemrosesan kesalahan untuk mengatasi persyaratan pemrosesan kesalahan alternatif di seluruh aplikasi.

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

Menggunakan komponen dan ProcessError metode contoh Error sebelumnya, konsol alat pengembang browser menunjukkan kesalahan yang terperangkap dan dicatat:

fail: BlazorSample.Shared.Error[0] Error:ProcessError - Type: System.NullReferenceException Message: Referensi objek tidak diatur ke instans objek.

Jika metode secara ProcessError langsung berpartisipasi dalam penyajian, seperti memperlihatkan bilah pesan kesalahan kustom atau mengubah gaya CSS elemen yang dirender, panggil StateHasChanged di akhir ProcessErrors metode untuk merender UI.

Karena pendekatan di bagian ini menangani kesalahan dengan try-catch pernyataan, Blazor koneksi aplikasi SignalR antara klien dan server tidak rusak ketika kesalahan terjadi dan sirkuit tetap hidup. Pengecualian yang tidak tertangani berakibat fatal bagi sirkuit. Untuk informasi selengkapnya, lihat bagian tentang bagaimana sirkuit bereaksi terhadap pengecualian yang tidak tertangani.

Kesalahan log dengan penyedia persisten

Jika terjadi pengecualian yang tidak tertangani, pengecualian dicatat ke ILogger instans yang dikonfigurasi dalam kontainer layanan. Secara default, Blazor aplikasi mencatat ke output konsol dengan Penyedia Pengelogan Konsol. Pertimbangkan untuk masuk ke lokasi di server (atau API web backend untuk aplikasi sisi klien) dengan penyedia yang mengelola ukuran log dan rotasi log. Atau, aplikasi dapat menggunakan layanan Manajemen Performa Aplikasi (APM), seperti Azure Application Insights (Azure Monitor).

Catatan

Fitur Application Insights asli untuk mendukung aplikasi sisi klien dan dukungan kerangka kerja asli Blazor untuk Google Analytics mungkin tersedia dalam rilis teknologi ini di masa mendatang. Untuk informasi selengkapnya, lihat Mendukung App Insights di Blazor Sisi Klien WASM (microsoft/ApplicationInsights-dotnet #2143) dan Analitik dan diagnostik Web (termasuk tautan ke implementasi komunitas) (dotnet/aspnetcore #5461). Sementara itu, aplikasi sisi klien dapat menggunakan Application Insights JavaScript SDK dengan JS interop untuk mencatat kesalahan langsung ke Application Insights dari aplikasi sisi klien.

Selama pengembangan dalam aplikasi yang Blazor beroperasi melalui sirkuit, aplikasi biasanya mengirim detail lengkap pengecualian ke konsol browser untuk membantu penelusuran kesalahan. Dalam produksi, kesalahan terperinci tidak dikirim ke klien, tetapi detail lengkap pengecualian dicatat di server.

Anda harus memutuskan insiden mana yang akan dicatat dan tingkat keparahan insiden yang dicatat. Pengguna yang bermusuhan mungkin dapat memicu kesalahan dengan sengaja. Misalnya, jangan mencatat insiden dari kesalahan di mana yang tidak diketahui ProductId disediakan dalam URL komponen yang menampilkan detail produk. Tidak semua kesalahan harus diperlakukan sebagai insiden untuk pengelogan.

Untuk informasi lebih lanjut, baca artikel berikut:

‡Berlaku untuk aplikasi sisi Blazor server dan aplikasi ASP.NET Core sisi server lainnya yang merupakan aplikasi backend API web untuk Blazor. Aplikasi sisi klien dapat menjebak dan mengirim informasi kesalahan pada klien ke API web, yang mencatat informasi kesalahan ke penyedia pengelogan persisten.

Jika terjadi pengecualian yang tidak tertangani, pengecualian dicatat ke ILogger instans yang dikonfigurasi dalam kontainer layanan. Secara default, Blazor aplikasi mencatat ke output konsol dengan Penyedia Pengelogan Konsol. Pertimbangkan untuk masuk ke lokasi yang lebih permanen di server dengan mengirim informasi kesalahan ke API web backend yang menggunakan penyedia pengelogan dengan manajemen ukuran log dan rotasi log. Atau, aplikasi API web backend dapat menggunakan layanan Manajemen Performa Aplikasi (APM), seperti Azure Application Insights (Azure Monitor)†, untuk merekam informasi kesalahan yang diterimanya dari klien.

Anda harus memutuskan insiden mana yang akan dicatat dan tingkat keparahan insiden yang dicatat. Pengguna yang bermusuhan mungkin dapat memicu kesalahan dengan sengaja. Misalnya, jangan mencatat insiden dari kesalahan di mana yang tidak diketahui ProductId disediakan dalam URL komponen yang menampilkan detail produk. Tidak semua kesalahan harus diperlakukan sebagai insiden untuk pengelogan.

Untuk informasi lebih lanjut, baca artikel berikut:

†Native Application Insights untuk mendukung aplikasi sisi klien dan dukungan kerangka kerja asli Blazor untuk Google Analytics mungkin tersedia dalam rilis teknologi ini di masa mendatang. Untuk informasi selengkapnya, lihat Mendukung App Insights di Blazor Sisi Klien WASM (microsoft/ApplicationInsights-dotnet #2143) dan Analitik dan diagnostik Web (termasuk tautan ke implementasi komunitas) (dotnet/aspnetcore #5461). Sementara itu, aplikasi sisi klien dapat menggunakan Application Insights JavaScript SDK dengan JS interop untuk mencatat kesalahan langsung ke Application Insights dari aplikasi sisi klien.

‡Berlaku untuk aplikasi ASP.NET Core sisi server yang merupakan aplikasi backend API web untuk Blazor aplikasi. Aplikasi sisi klien menjebak dan mengirim informasi kesalahan ke API web, yang mencatat informasi kesalahan ke penyedia pengelogan persisten.

Tempat di mana kesalahan dapat terjadi

Kerangka kerja dan kode aplikasi dapat memicu pengecualian yang tidak tertangani di salah satu lokasi berikut, yang dijelaskan lebih lanjut di bagian berikut dari artikel ini:

Instansiasi komponen

Saat Blazor membuat instans komponen:

  • Konstruktor komponen dipanggil.
  • Konstruktor layanan DI yang disediakan ke konstruktor komponen melalui @inject direktif atau [Inject] atribut dipanggil.

Kesalahan dalam konstruktor yang dijalankan atau setter untuk properti apa pun [Inject] menghasilkan pengecualian yang tidak tertangani dan menghentikan kerangka kerja membuat instans komponen. Jika aplikasi beroperasi melalui sirkuit, sirkuit gagal. Jika logika konstruktor dapat melemparkan pengecualian, aplikasi harus menjebak pengecualian menggunakan try-catch pernyataan dengan penanganan kesalahan dan pengelogan.

Metode siklus hidup

Selama masa pakai komponen, Blazor memanggil metode siklus hidup. Jika ada metode siklus hidup yang melemparkan pengecualian, secara sinkron atau asinkron, pengecualiannya fatal bagi sirkuit. Agar komponen menangani kesalahan dalam metode siklus hidup, tambahkan logika penanganan kesalahan.

Dalam contoh berikut di mana OnParametersSetAsync memanggil metode untuk mendapatkan produk:

  • Pengecualian yang dilemparkan dalam metode ditangani ProductRepository.GetProductByIdAsync oleh try-catch pernyataan.
  • catch Ketika blok dijalankan:
    • loadFailed diatur ke true, yang digunakan untuk menampilkan pesan kesalahan kepada pengguna.
    • Kesalahan dicatat.
@page "/product-details/{ProductId:int?}"
@inject ILogger<ProductDetails> Logger
@inject IProductRepository Product

<PageTitle>Product Details</PageTitle>

<h1>Product Details Example</h1>

@if (details != null)
{
    <h2>@details.ProductName</h2>
    <p>
        @details.Description
        <a href="@details.Url">Company Link</a>
    </p>
    
}
else if (loadFailed)
{
    <h1>Sorry, we could not load this product due to an error.</h1>
}
else
{
    <h1>Loading...</h1>
}

@code {
    private ProductDetail? details;
    private bool loadFailed;

    [Parameter]
    public int ProductId { get; set; }

    protected override async Task OnParametersSetAsync()
    {
        try
        {
            loadFailed = false;

            // Reset details to null to display the loading indicator
            details = null;

            details = await Product.GetProductByIdAsync(ProductId);
        }
        catch (Exception ex)
        {
            loadFailed = true;
            Logger.LogWarning(ex, "Failed to load product {ProductId}", ProductId);
        }
    }

    public class ProductDetail
    {
        public string? ProductName { get; set; }
        public string? Description { get; set; }
        public string? Url { get; set; }
    }

    /*
    * Register the service in Program.cs:
    * using static BlazorSample.Components.Pages.ProductDetails;
    * builder.Services.AddScoped<IProductRepository, ProductRepository>();
    */

    public interface IProductRepository
    {
        public Task<ProductDetail> GetProductByIdAsync(int id);
    }

    public class ProductRepository : IProductRepository
    {
        public Task<ProductDetail> GetProductByIdAsync(int id)
        {
            return Task.FromResult(
                new ProductDetail()
                {
                    ProductName = "Flowbee ",
                    Description = "The Revolutionary Haircutting System You've Come to Love!",
                    Url = "https://flowbee.com/"
                });
        }
    }
}
@page "/product-details/{ProductId:int}"
@using Microsoft.Extensions.Logging
@inject ILogger<ProductDetails> Logger
@inject IProductRepository ProductRepository

@if (details != null)
{
    <h1>@details.ProductName</h1>
    <p>@details.Description</p>
}
else if (loadFailed)
{
    <h1>Sorry, we could not load this product due to an error.</h1>
}
else
{
    <h1>Loading...</h1>
}

@code {
    private ProductDetail? details;
    private bool loadFailed;

    [Parameter]
    public int ProductId { get; set; }

    protected override async Task OnParametersSetAsync()
    {
        try
        {
            loadFailed = false;

            // Reset details to null to display the loading indicator
            details = null;

            details = await ProductRepository.GetProductByIdAsync(ProductId);
        }
        catch (Exception ex)
        {
            loadFailed = true;
            Logger.LogWarning(ex, "Failed to load product {ProductId}", ProductId);
        }
    }

    public class ProductDetail
    {
        public string? ProductName { get; set; }
        public string? Description { get; set; }
    }

    public interface IProductRepository
    {
        public Task<ProductDetail> GetProductByIdAsync(int id);
    }
}
@page "/product-details/{ProductId:int}"
@using Microsoft.Extensions.Logging
@inject ILogger<ProductDetails> Logger
@inject IProductRepository ProductRepository

@if (details != null)
{
    <h1>@details.ProductName</h1>
    <p>@details.Description</p>
}
else if (loadFailed)
{
    <h1>Sorry, we could not load this product due to an error.</h1>
}
else
{
    <h1>Loading...</h1>
}

@code {
    private ProductDetail? details;
    private bool loadFailed;

    [Parameter]
    public int ProductId { get; set; }

    protected override async Task OnParametersSetAsync()
    {
        try
        {
            loadFailed = false;

            // Reset details to null to display the loading indicator
            details = null;

            details = await ProductRepository.GetProductByIdAsync(ProductId);
        }
        catch (Exception ex)
        {
            loadFailed = true;
            Logger.LogWarning(ex, "Failed to load product {ProductId}", ProductId);
        }
    }

    public class ProductDetail
    {
        public string? ProductName { get; set; }
        public string? Description { get; set; }
    }

    public interface IProductRepository
    {
        public Task<ProductDetail> GetProductByIdAsync(int id);
    }
}
@page "/product-details/{ProductId:int}"
@using Microsoft.Extensions.Logging
@inject ILogger<ProductDetails> Logger
@inject IProductRepository ProductRepository

@if (details != null)
{
    <h1>@details.ProductName</h1>
    <p>@details.Description</p>
}
else if (loadFailed)
{
    <h1>Sorry, we could not load this product due to an error.</h1>
}
else
{
    <h1>Loading...</h1>
}

@code {
    private ProductDetail details;
    private bool loadFailed;

    [Parameter]
    public int ProductId { get; set; }

    protected override async Task OnParametersSetAsync()
    {
        try
        {
            loadFailed = false;

            // Reset details to null to display the loading indicator
            details = null;

            details = await ProductRepository.GetProductByIdAsync(ProductId);
        }
        catch (Exception ex)
        {
            loadFailed = true;
            Logger.LogWarning(ex, "Failed to load product {ProductId}", ProductId);
        }
    }

    public class ProductDetail
    {
        public string ProductName { get; set; }
        public string Description { get; set; }
    }

    public interface IProductRepository
    {
        public Task<ProductDetail> GetProductByIdAsync(int id);
    }
}
@page "/product-details/{ProductId:int}"
@using Microsoft.Extensions.Logging
@inject ILogger<ProductDetails> Logger
@inject IProductRepository ProductRepository

@if (details != null)
{
    <h1>@details.ProductName</h1>
    <p>@details.Description</p>
}
else if (loadFailed)
{
    <h1>Sorry, we could not load this product due to an error.</h1>
}
else
{
    <h1>Loading...</h1>
}

@code {
    private ProductDetail details;
    private bool loadFailed;

    [Parameter]
    public int ProductId { get; set; }

    protected override async Task OnParametersSetAsync()
    {
        try
        {
            loadFailed = false;

            // Reset details to null to display the loading indicator
            details = null;

            details = await ProductRepository.GetProductByIdAsync(ProductId);
        }
        catch (Exception ex)
        {
            loadFailed = true;
            Logger.LogWarning(ex, "Failed to load product {ProductId}", ProductId);
        }
    }

    public class ProductDetail
    {
        public string ProductName { get; set; }
        public string Description { get; set; }
    }

    public interface IProductRepository
    {
        public Task<ProductDetail> GetProductByIdAsync(int id);
    }
}

Logika penyajian

Markup deklaratif dalam file komponen () dikompilasi Razor ke dalam metode C# yang disebut BuildRenderTree..razor Saat komponen merender, BuildRenderTree menjalankan dan membangun struktur data yang menjelaskan elemen, teks, dan komponen turunan dari komponen yang dirender.

Logika penyajian dapat melemparkan pengecualian. Contoh skenario ini terjadi ketika @someObject.PropertyName dievaluasi tetapi @someObject adalah null. Untuk Blazor aplikasi yang beroperasi melalui sirkuit, pengecualian yang tidak tertangani yang dilemparkan oleh logika penyajian berakibat fatal bagi sirkuit aplikasi.

Untuk mencegah NullReferenceException dalam logika penyajian, periksa null objek sebelum mengakses anggotanya. Dalam contoh berikut, person.Address properti tidak diakses jika person.Address adalah 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>
}

Kode sebelumnya mengasumsikan bahwa person bukan null. Seringkali, struktur kode menjamin bahwa objek ada pada saat komponen dirender. Dalam kasus tersebut, tidak perlu memeriksa null logika penyajian. Dalam contoh sebelumnya, person mungkin dijamin ada karena person dibuat saat komponen dibuat, seperti yang ditunjukkan contoh berikut:

@code {
    private Person person = new();

    ...
}

Penangan kejadian

Kode sisi klien memicu pemanggilan kode C# saat penanganan aktivitas dibuat menggunakan:

  • @onclick
  • @onchange
  • Atribut lain @on...
  • @bind

Kode penanganan aktivitas mungkin melemparkan pengecualian yang tidak tertangani dalam skenario ini.

Jika aplikasi memanggil kode yang dapat gagal karena alasan eksternal, pengecualian perangkap menggunakan try-catch pernyataan dengan penanganan kesalahan dan pengelogan.

Jika penanganan aktivitas melemparkan pengecualian yang tidak tertangani (misalnya, kueri database gagal) yang tidak terperangkap dan ditangani oleh kode pengembang:

  • Kerangka kerja mencatat pengecualian.
  • Dalam aplikasi yang Blazor beroperasi melalui sirkuit, pengecualiannya berakibat fatal bagi sirkuit aplikasi.

Pembuangan komponen

Komponen dapat dihapus dari UI, misalnya, karena pengguna telah menavigasi ke halaman lain. Ketika komponen yang menerapkan System.IDisposable dihapus dari UI, kerangka kerja memanggil metode komponen Dispose .

Jika metode komponen Dispose melemparkan pengecualian yang tidak tertangani dalam aplikasi yang Blazor beroperasi melalui sirkuit, pengecualiannya berakibat fatal bagi sirkuit aplikasi.

Jika logika pembuangan dapat melemparkan pengecualian, aplikasi harus menjebak pengecualian menggunakan try-catch pernyataan dengan penanganan kesalahan dan pengelogan.

Untuk informasi selengkapnya tentang pembuangan komponen, lihat siklus hidup komponen ASP.NET CoreRazor.

Interop JavaScript

IJSRuntime terdaftar oleh Blazor kerangka kerja. IJSRuntime.InvokeAsync memungkinkan kode .NET untuk melakukan panggilan asinkron ke runtime JavaScript (JS) di browser pengguna.

Kondisi berikut berlaku untuk penanganan kesalahan dengan InvokeAsync:

  • Jika panggilan gagal InvokeAsync secara sinkron, pengecualian .NET terjadi. Panggilan ke InvokeAsync mungkin gagal, misalnya, karena argumen yang disediakan tidak dapat diserialisasikan. Kode pengembang harus menangkap pengecualian. Jika kode aplikasi dalam metode penanganan aktivitas atau siklus hidup komponen tidak menangani pengecualian dalam aplikasi yang Blazor beroperasi melalui sirkuit, pengecualian yang dihasilkan berakibat fatal bagi sirkuit aplikasi.
  • Jika panggilan gagal InvokeAsync secara asinkron, .NET Task gagal. Panggilan ke InvokeAsync mungkin gagal, misalnya, karena JSkode -side melempar pengecualian atau mengembalikan Promise yang diselesaikan sebagai rejected. Kode pengembang harus menangkap pengecualian. Jika menggunakan await operator, pertimbangkan untuk membungkus panggilan metode dalam try-catch pernyataan dengan penanganan kesalahan dan pengelogan. Jika tidak, dalam aplikasi yang Blazor beroperasi melalui sirkuit, kode yang gagal menghasilkan pengecualian yang tidak tertangani yang fatal bagi sirkuit aplikasi.
  • Secara default, panggilan ke InvokeAsync harus diselesaikan dalam periode tertentu atau waktu panggilan habis. Periode batas waktu default adalah satu menit. Batas waktu melindungi kode dari kehilangan konektivitas jaringan atau JS kode yang tidak pernah mengirim kembali pesan penyelesaian. Jika waktu panggilan habis, hasilnya System.Threading.Tasks gagal dengan OperationCanceledException. Perangkap dan proses pengecualian dengan pengelogan.

Demikian pula, JS kode dapat memulai panggilan ke metode .NET yang ditunjukkan oleh [JSInvokable] atribut . Jika metode .NET ini melemparkan pengecualian yang tidak tertangani:

  • Dalam aplikasi yang Blazor beroperasi melalui sirkuit, pengecualian tidak diperlakukan sebagai fatal bagi sirkuit aplikasi.
  • - JSsisi Promise ditolak.

Anda memiliki opsi untuk menggunakan kode penanganan kesalahan di sisi .NET atau sisi JS panggilan metode.

Untuk informasi lebih lanjut, baca artikel berikut:

Pra-penyajian

Razor komponen telah dirender secara default sehingga markup HTML yang dirender dikembalikan sebagai bagian dari permintaan HTTP awal pengguna.

Dalam aplikasi yang Blazor beroperasi melalui sirkuit, pra-penyajian berfungsi dengan:

  • Membuat sirkuit baru untuk semua komponen yang telah dirender sebelumnya yang merupakan bagian dari halaman yang sama.
  • Membuat HTML awal.
  • Memperlakukan sirkuit sampai disconnected browser pengguna membuat SignalR koneksi kembali ke server yang sama. Ketika koneksi dibuat, interaktivitas pada sirkuit dilanjutkan dan markup HTML komponen diperbarui.

Untuk komponen sisi klien yang telah dirender sebelumnya, prarender bekerja dengan:

  • Menghasilkan HTML awal di server untuk semua komponen yang telah dirender sebelumnya yang merupakan bagian dari halaman yang sama.
  • Membuat komponen interaktif pada klien setelah browser memuat kode yang dikompilasi aplikasi dan runtime .NET (jika belum dimuat) di latar belakang.

Jika komponen melemparkan pengecualian yang tidak tertangani selama pra-penyajian, misalnya, selama metode siklus hidup atau dalam logika penyajian:

  • Dalam aplikasi yang Blazor beroperasi melalui sirkuit, pengecualiannya berakibat fatal bagi sirkuit. Untuk komponen sisi klien yang telah dirender sebelumnya, pengecualian mencegah penyajian komponen.
  • Pengecualian dilemparkan ke tumpukan panggilan dari ComponentTagHelper.

Dalam keadaan normal saat pra-penyajian gagal, terus membangun dan merender komponen tidak masuk akal karena komponen yang berfungsi tidak dapat dirender.

Untuk mentolerir kesalahan yang mungkin terjadi selama pra-penyajian, logika penanganan kesalahan harus ditempatkan di dalam komponen yang dapat melemparkan pengecualian. Gunakan try-catch pernyataan dengan penanganan kesalahan dan pengelogan. Alih-alih membungkus ComponentTagHelper dalam try-catch pernyataan, tempatkan logika penanganan kesalahan dalam komponen yang dirender oleh ComponentTagHelper.

Skenario tingkat lanjut

Penyajian rekursif

Komponen dapat disarangkan secara rekursif. Ini berguna untuk mewakili struktur data rekursif. Misalnya, TreeNode komponen dapat merender lebih TreeNode banyak komponen untuk setiap anak simpul.

Saat merender secara rekursif, hindari pola pengkodian yang mengakibatkan rekursi tak terbatas:

  • Jangan merender struktur data secara rekursif yang berisi siklus. Misalnya, jangan merender simpul pohon yang anak-anaknya menyertakan dirinya sendiri.
  • Jangan membuat rantai tata letak yang berisi siklus. Misalnya, jangan membuat tata letak yang tata letaknya sendiri.
  • Jangan izinkan pengguna akhir melanggar invarian rekursi (aturan) melalui entri data berbahaya atau panggilan interop JavaScript.

Perulangan tak terbatas selama penyajian:

  • Menyebabkan proses penyajian berlanjut selamanya.
  • Setara dengan membuat perulangan yang tidak ditentukan.

Dalam skenario ini, Blazor gagal dan biasanya mencoba untuk:

  • Konsumsi waktu CPU sebanyak yang diizinkan oleh sistem operasi, tanpa batas waktu.
  • Mengonsumsi memori dalam jumlah tak terbatas. Mengonsumsi memori tak terbatas setara dengan skenario di mana perulangan yang tidak ditentukan menambahkan entri ke koleksi pada setiap perulangan.

Untuk menghindari pola rekursi tak terbatas, pastikan bahwa kode rendering rekursif berisi kondisi penghentian yang sesuai.

Logika pohon render kustom

Sebagian besar Razor komponen diimplementasikan sebagai Razor file komponen (.razor) dan dikompilasi oleh kerangka kerja untuk menghasilkan logika yang beroperasi pada RenderTreeBuilder untuk merender output mereka. Namun, pengembang dapat menerapkan logika secara RenderTreeBuilder manual menggunakan kode C# prosedural. Untuk informasi selengkapnya, lihat skenario lanjutan ASP.NET Core Blazor (konstruksi pohon render).

Peringatan

Penggunaan logika pembuat pohon render manual dianggap sebagai skenario tingkat lanjut dan tidak aman, tidak disarankan untuk pengembangan komponen umum.

Jika RenderTreeBuilder kode ditulis, pengembang harus menjamin kebenaran kode. Misalnya, pengembang harus memastikan bahwa:

  • Panggilan ke OpenElement dan CloseElement diseimbangkan dengan benar.
  • Atribut hanya ditambahkan di tempat yang benar.

Logika pembangun pohon render manual yang salah dapat menyebabkan perilaku yang tidak terdefinisi secara arbitrer, termasuk crash, aplikasi atau server macet, dan kerentanan keamanan.

Pertimbangkan logika pembuat pohon render manual pada tingkat kompleksitas yang sama dan dengan tingkat bahaya yang sama seperti menulis kode perakitan atau instruksi Microsoft Intermediate Language (MSIL) secara manual.

Sumber Daya Tambahan:

†Aplikasi ke backend ASP.NET aplikasi API web Core yang digunakan aplikasi sisi Blazor klien untuk pengelogan.