Bagikan melalui


siklus hidup komponen ASP.NET Core Razor

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 siklus hidup komponen ASP.NET Core Razor dan cara menggunakan peristiwa siklus hidup.

Peristiwa siklus hidup

Komponen Razor memproses Razor peristiwa siklus hidup komponen dalam satu set metode siklus hidup sinkron dan asinkron. Metode siklus hidup dapat ditimpa untuk melakukan operasi tambahan dalam komponen selama inisialisasi dan penyajian komponen.

Artikel ini menyederhanakan pemrosesan peristiwa siklus hidup komponen untuk mengklarifikasi logika kerangka kerja yang kompleks dan tidak mencakup setiap perubahan yang dilakukan selama bertahun-tahun. Anda mungkin perlu mengakses ComponentBase sumber referensi untuk mengintegrasikan pemrosesan peristiwa kustom dengan Blazorpemrosesan peristiwa siklus hidup. Komentar kode di sumber referensi menyertakan komentar tambahan tentang pemrosesan peristiwa siklus hidup yang tidak muncul di artikel ini atau dalam dokumentasi API.

Catatan

Tautan dokumentasi ke sumber referensi .NET biasanya memuat cabang default repositori, yang mewakili pengembangan saat ini untuk rilis .NET berikutnya. Untuk memilih tag rilis tertentu, gunakan daftar dropdown Beralih cabang atau tag. Untuk informasi lebih lanjut, lihat Cara memilih tag versi kode sumber ASP.NET Core (dotnet/AspNetCore.Docs #26205).

Diagram yang disederhanakan berikut mengilustrasikan Razor pemrosesan peristiwa siklus hidup komponen. Metode C# yang terkait dengan peristiwa siklus hidup didefinisikan dengan contoh di bagian berikut dari artikel ini.

Peristiwa siklus hidup komponen:

  1. Jika komponen dirender untuk pertama kalinya berdasarkan permintaan:
    • Buat instans komponen.
    • Lakukan injeksi properti.
    • Panggil OnInitialized{Async}. Jika tidak lengkap Task dikembalikan, Task ditunggu dan kemudian komponen dirender. Metode sinkron dipanggil sebelum metode asinkron.
  2. Panggil OnParametersSet{Async}. Jika tidak lengkap Task dikembalikan, Task ditunggu dan kemudian komponen dirender. Metode sinkron dipanggil sebelum metode asinkron.
  3. Render untuk semua pekerjaan sinkron dan selesai Task.

Catatan

Tindakan asinkron yang dilakukan dalam peristiwa siklus hidup mungkin belum selesai sebelum komponen dirender. Untuk informasi selengkapnya, lihat bagian Menangani tindakan asinkron yang tidak lengkap di render nanti di artikel ini.

Komponen induk dirender sebelum komponen turunannya karena penyajian adalah apa yang menentukan turunan mana yang ada. Jika inisialisasi komponen induk sinkron digunakan, inisialisasi induk dijamin selesai terlebih dahulu. Jika inisialisasi komponen induk asinkron digunakan, urutan penyelesaian inisialisasi komponen induk dan anak tidak dapat ditentukan karena bergantung pada kode inisialisasi yang berjalan.

Peristiwa Razor siklus hidup komponen komponen dalam Blazor

Pemrosesan peristiwa DOM:

  1. Penanganan aktivitas dijalankan.
  2. Jika tidak lengkap Task dikembalikan, Task ditunggu dan kemudian komponen dirender.
  3. Render untuk semua pekerjaan sinkron dan selesai Task.

Pemrosesan peristiwa DOM

Render Siklus hidup:

  1. Hindari operasi penyajian lebih lanjut pada komponen ketika kedua kondisi berikut terpenuhi:
    • Ini bukan render pertama.
    • ShouldRender menghasilkan false.
  2. Buat diff pohon render (perbedaan) dan render komponen.
  3. Tunggu DOM untuk diperbarui.
  4. Panggil OnAfterRender{Async}. Metode sinkron dipanggil sebelum metode asinkron.

Siklus hidup render

Panggilan pengembang untuk StateHasChanged menghasilkan render. Untuk informasi lebih lanjut, lihat perenderan komponen Razor ASP.NET Core.

Ketika parameter diatur (SetParametersAsync)

SetParametersAsync mengatur parameter yang disediakan oleh induk komponen di pohon render atau dari parameter rute.

Parameter metode ParameterView berisi sekumpulan nilai parameter komponen untuk komponen setiap kali SetParametersAsync dipanggil. Dengan mengesampingkan SetParametersAsync metode , kode pengembang dapat berinteraksi langsung dengan ParameterViewparameter .

Implementasi SetParametersAsync default menetapkan nilai setiap properti dengan [Parameter] atribut atau [CascadingParameter] yang memiliki nilai yang sesuai dalam ParameterView. Parameter yang tidak memiliki nilai yang sesuai dibiarkan ParameterView tidak berubah.

Umumnya, kode Anda harus memanggil metode kelas dasar (await base.SetParametersAsync(parameters);) saat mengambil alih SetParametersAsync. Dalam skenario lanjutan, kode pengembang dapat menginterpretasikan nilai parameter masuk dengan cara apa pun yang diperlukan dengan tidak memanggil metode kelas dasar. Misalnya, tidak ada persyaratan untuk menetapkan parameter masuk ke properti kelas. Namun, Anda harus merujuk ke ComponentBase sumber referensi saat menyusun kode Anda tanpa memanggil metode kelas dasar karena memanggil metode siklus hidup lain dan memicu penyajian dengan cara yang kompleks.

Catatan

Tautan dokumentasi ke sumber referensi .NET biasanya memuat cabang default repositori, yang mewakili pengembangan saat ini untuk rilis .NET berikutnya. Untuk memilih tag rilis tertentu, gunakan daftar dropdown Beralih cabang atau tag. Untuk informasi lebih lanjut, lihat Cara memilih tag versi kode sumber ASP.NET Core (dotnet/AspNetCore.Docs #26205).

Jika Anda ingin mengandalkan logika inisialisasi dan penyajian ComponentBase.SetParametersAsync tetapi tidak memproses parameter masuk, Anda memiliki opsi untuk meneruskan yang kosong ParameterView ke metode kelas dasar:

await base.SetParametersAsync(ParameterView.Empty);

Jika penanganan aktivitas disediakan dalam kode pengembang, lepaskan di pembuangan. Untuk informasi selengkapnya, lihat bagian Dan pembuangan IDisposableIAsyncDisposable komponen.

Dalam contoh berikut, ParameterView.TryGetValue menetapkan Param nilai parameter ke value jika penguraian parameter rute berhasil Param . Jika value tidak null, nilai ditampilkan oleh komponen.

Meskipun pencocokan parameter rute tidak peka huruf besar/kecil, TryGetValue hanya cocok dengan nama parameter peka huruf besar/kecil dalam templat rute. Contoh berikut memerlukan penggunaan /{Param?} dalam templat rute untuk mendapatkan nilai dengan TryGetValue, bukan /{param?}. Jika /{param?} digunakan dalam skenario ini, TryGetValue mengembalikan false dan message tidak diatur ke salah satu message string.

SetParamsAsync.razor:

@page "/set-params-async/{Param?}"

<PageTitle>Set Parameters Async</PageTitle>

<h1>Set Parameters Async Example</h1>

<p>@message</p>

@code {
    private string message = "Not set";

    [Parameter]
    public string? Param { get; set; }

    public override async Task SetParametersAsync(ParameterView parameters)
    {
        if (parameters.TryGetValue<string>(nameof(Param), out var value))
        {
            if (value is null)
            {
                message = "The value of 'Param' is null.";
            }
            else
            {
                message = $"The value of 'Param' is {value}.";
            }
        }

        await base.SetParametersAsync(parameters);
    }
}
@page "/set-params-async/{Param?}"

<p>@message</p>

@code {
    private string message = "Not set";

    [Parameter]
    public string? Param { get; set; }

    public override async Task SetParametersAsync(ParameterView parameters)
    {
        if (parameters.TryGetValue<string>(nameof(Param), out var value))
        {
            if (value is null)
            {
                message = "The value of 'Param' is null.";
            }
            else
            {
                message = $"The value of 'Param' is {value}.";
            }
        }

        await base.SetParametersAsync(parameters);
    }
}
@page "/set-params-async/{Param?}"

<p>@message</p>

@code {
    private string message = "Not set";

    [Parameter]
    public string? Param { get; set; }

    public override async Task SetParametersAsync(ParameterView parameters)
    {
        if (parameters.TryGetValue<string>(nameof(Param), out var value))
        {
            if (value is null)
            {
                message = "The value of 'Param' is null.";
            }
            else
            {
                message = $"The value of 'Param' is {value}.";
            }
        }

        await base.SetParametersAsync(parameters);
    }
}
@page "/set-params-async/{Param?}"

<p>@message</p>

@code {
    private string message = "Not set";

    [Parameter]
    public string Param { get; set; }

    public override async Task SetParametersAsync(ParameterView parameters)
    {
        if (parameters.TryGetValue<string>(nameof(Param), out var value))
        {
            if (value is null)
            {
                message = "The value of 'Param' is null.";
            }
            else
            {
                message = $"The value of 'Param' is {value}.";
            }
        }

        await base.SetParametersAsync(parameters);
    }
}
@page "/set-params-async"
@page "/set-params-async/{Param}"

<p>@message</p>

@code {
    private string message = "Not set";

    [Parameter]
    public string Param { get; set; }

    public override async Task SetParametersAsync(ParameterView parameters)
    {
        if (parameters.TryGetValue<string>(nameof(Param), out var value))
        {
            if (value is null)
            {
                message = "The value of 'Param' is null.";
            }
            else
            {
                message = $"The value of 'Param' is {value}.";
            }
        }

        await base.SetParametersAsync(parameters);
    }
}

Inisialisasi komponen (OnInitialized{Async})

OnInitialized dan OnInitializedAsync digunakan secara eksklusif untuk menginisialisasi komponen selama seluruh masa pakai instans komponen. Nilai parameter dan perubahan nilai parameter seharusnya tidak memengaruhi inisialisasi yang dilakukan dalam metode ini. Misalnya, memuat opsi statis ke dalam daftar dropdown yang tidak berubah selama masa pakai komponen dan itu tidak bergantung pada nilai parameter dilakukan dalam salah satu metode siklus hidup ini. Jika nilai parameter atau perubahan nilai parameter memengaruhi status komponen, gunakan OnParametersSet{Async} sebagai gantinya.

Metode ini dipanggil ketika komponen diinisialisasi setelah menerima parameter awalnya di SetParametersAsync. Metode sinkron dipanggil sebelum metode asinkron.

Jika inisialisasi komponen induk sinkron digunakan, inisialisasi induk dijamin selesai sebelum inisialisasi komponen turunan. Jika inisialisasi komponen induk asinkron digunakan, urutan penyelesaian inisialisasi komponen induk dan anak tidak dapat ditentukan karena bergantung pada kode inisialisasi yang berjalan.

Untuk operasi sinkron, ambil alih OnInitialized:

OnInit.razor:

@page "/on-init"

<PageTitle>On Initialized</PageTitle>

<h1>On Initialized Example</h1>

<p>@message</p>

@code {
    private string? message;

    protected override void OnInitialized()
    {
        message = $"Initialized at {DateTime.Now}";
    }
}
@page "/on-init"

<p>@message</p>

@code {
    private string? message;

    protected override void OnInitialized()
    {
        message = $"Initialized at {DateTime.Now}";
    }
}
@page "/on-init"

<p>@message</p>

@code {
    private string? message;

    protected override void OnInitialized()
    {
        message = $"Initialized at {DateTime.Now}";
    }
}
@page "/on-init"

<p>@message</p>

@code {
    private string message;

    protected override void OnInitialized()
    {
        message = $"Initialized at {DateTime.Now}";
    }
}
@page "/on-init"

<p>@message</p>

@code {
    private string message;

    protected override void OnInitialized()
    {
        message = $"Initialized at {DateTime.Now}";
    }
}

Untuk melakukan operasi asinkron, ambil alih OnInitializedAsync dan gunakan await operator:

protected override async Task OnInitializedAsync()
{
    await ...
}

Jika kelas dasar kustom digunakan dengan logika inisialisasi kustom, panggil OnInitializedAsync di kelas dasar:

protected override async Task OnInitializedAsync()
{
    await ...

    await base.OnInitializedAsync();
}

Tidak perlu memanggil ComponentBase.OnInitializedAsync kecuali kelas dasar kustom digunakan dengan logika kustom. Untuk informasi selengkapnya, lihat bagian Metode siklus hidup kelas dasar.

Blazor aplikasi yang merender konten mereka di panggilan OnInitializedAsyncserver dua kali:

  • Setelah komponen awalnya dirender secara statis sebagai bagian dari halaman.
  • Untuk kedua kalinya ketika browser merender komponen.

Untuk mencegah kode pengembang masuk OnInitializedAsync agar tidak berjalan dua kali saat melakukan pra-penyajian, lihat bagian Koneksi ulang stateful setelah pra-penyajian . Konten di bagian berfokus pada Blazor Web Apps dan koneksi ulang statefulSignalR. Untuk mempertahankan status selama eksekusi kode inisialisasi saat melakukan prarender, lihat Prarender komponen ASP.NET CoreRazor.

Untuk mencegah kode pengembang masuk OnInitializedAsync agar tidak berjalan dua kali saat melakukan pra-penyajian, lihat bagian Koneksi ulang stateful setelah pra-penyajian . Meskipun konten di bagian berfokus pada Blazor Server dan koneksi ulang yang statefulSignalR, skenario untuk prarender dalam solusi yang Blazor WebAssembly dihosting (WebAssemblyPrerendered) melibatkan kondisi dan pendekatan serupa untuk mencegah menjalankan kode pengembang dua kali. Untuk mempertahankan status selama eksekusi kode inisialisasi saat melakukan prarender, lihat Merender dan mengintegrasikan komponen ASP.NET CoreRazor.

Blazor Saat aplikasi melakukan pra-penyajian, tindakan tertentu, seperti memanggil ke JavaScript (JS interop), tidak dimungkinkan. Komponen mungkin perlu dirender secara berbeda saat dirender sebelumnya. Untuk informasi selengkapnya, lihat bagian Pra-penyajian dengan interop JavaScript.

Jika penanganan aktivitas disediakan dalam kode pengembang, lepaskan di pembuangan. Untuk informasi selengkapnya, lihat bagian Pembuangan komponen denganIAsyncDisposableIDisposable.

Gunakan penyajian streaming dengan penyajian sisi server statis (SSR statis) atau pra-penyajian untuk meningkatkan pengalaman pengguna untuk komponen yang melakukan tugas OnInitializedAsync asinkron yang berjalan lama untuk sepenuhnya merender. Untuk informasi lebih lanjut, lihat perenderan komponen Razor ASP.NET Core.

Setelah parameter diatur (OnParametersSet{Async})

OnParametersSet atau OnParametersSetAsync dipanggil:

  • Setelah komponen diinisialisasi di OnInitialized atau OnInitializedAsync.

  • Ketika komponen induk merender dan memasok:

    • Jenis yang diketahui atau primitif tidak dapat diubah ketika setidaknya satu parameter telah berubah.
    • Parameter berjenis kompleks. Kerangka kerja tidak dapat mengetahui apakah nilai parameter berjenis kompleks telah bermutasi secara internal, sehingga kerangka kerja selalu memperlakukan parameter yang ditetapkan sebagai berubah ketika satu atau beberapa parameter berjenis kompleks ada.

    Untuk informasi selengkapnya tentang konvensi penyajian, lihat penyajian komponen ASP.NET CoreRazor.

Metode sinkron dipanggil sebelum metode asinkron.

Metode dapat dipanggil bahkan jika nilai parameter belum berubah. Perilaku ini menggaris bawahi kebutuhan pengembang untuk menerapkan logika tambahan dalam metode untuk memeriksa apakah nilai parameter memang telah berubah sebelum menginisialisasi ulang data atau status tergantung pada parameter tersebut.

Untuk komponen contoh berikut, navigasikan ke halaman komponen di URL:

  • Dengan tanggal mulai yang diterima oleh StartDate: /on-parameters-set/2021-03-19
  • Tanpa tanggal mulai, di mana StartDate ditetapkan nilai waktu lokal saat ini: /on-parameters-set

Catatan

Dalam rute komponen, tidak dimungkinkan untuk membatasi parameter dengan batasan datetime rute dan membuat parameter opsional.DateTime Oleh karena itu, komponen berikut OnParamsSet menggunakan dua @page arahan untuk menangani perutean dengan dan tanpa segmen tanggal yang disediakan di URL.

OnParamsSet.razor:

@page "/on-params-set"
@page "/on-params-set/{StartDate:datetime}"

<PageTitle>On Parameters Set</PageTitle>

<h1>On Parameters Set Example</h1>

<p>
    Pass a datetime in the URI of the browser's address bar. 
    For example, add <code>/1-1-2024</code> to the address.
</p>

<p>@message</p>

@code {
    private string? message;

    [Parameter]
    public DateTime StartDate { get; set; }

    protected override void OnParametersSet()
    {
        if (StartDate == default)
        {
            StartDate = DateTime.Now;

            message = $"No start date in URL. Default value applied " +
                $"(StartDate: {StartDate}).";
        }
        else
        {
            message = $"The start date in the URL was used " +
                $"(StartDate: {StartDate}).";
        }
    }
}
@page "/on-params-set"
@page "/on-params-set/{StartDate:datetime}"

<p>@message</p>

@code {
    private string? message;

    [Parameter]
    public DateTime StartDate { get; set; }

    protected override void OnParametersSet()
    {
        if (StartDate == default)
        {
            StartDate = DateTime.Now;

            message = $"No start date in URL. Default value applied (StartDate: {StartDate}).";
        }
        else
        {
            message = $"The start date in the URL was used (StartDate: {StartDate}).";
        }
    }
}
@page "/on-params-set"
@page "/on-params-set/{StartDate:datetime}"

<p>@message</p>

@code {
    private string? message;

    [Parameter]
    public DateTime StartDate { get; set; }

    protected override void OnParametersSet()
    {
        if (StartDate == default)
        {
            StartDate = DateTime.Now;

            message = $"No start date in URL. Default value applied (StartDate: {StartDate}).";
        }
        else
        {
            message = $"The start date in the URL was used (StartDate: {StartDate}).";
        }
    }
}
@page "/on-params-set"
@page "/on-params-set/{StartDate:datetime}"

<p>@message</p>

@code {
    private string message;

    [Parameter]
    public DateTime StartDate { get; set; }

    protected override void OnParametersSet()
    {
        if (StartDate == default)
        {
            StartDate = DateTime.Now;

            message = $"No start date in URL. Default value applied (StartDate: {StartDate}).";
        }
        else
        {
            message = $"The start date in the URL was used (StartDate: {StartDate}).";
        }
    }
}
@page "/on-params-set"
@page "/on-params-set/{StartDate:datetime}"

<p>@message</p>

@code {
    private string message;

    [Parameter]
    public DateTime StartDate { get; set; }

    protected override void OnParametersSet()
    {
        if (StartDate == default)
        {
            StartDate = DateTime.Now;

            message = $"No start date in URL. Default value applied (StartDate: {StartDate}).";
        }
        else
        {
            message = $"The start date in the URL was used (StartDate: {StartDate}).";
        }
    }
}

Pekerjaan asinkron saat menerapkan parameter dan nilai properti harus terjadi selama OnParametersSetAsync peristiwa siklus hidup:

protected override async Task OnParametersSetAsync()
{
    await ...
}

Jika kelas dasar kustom digunakan dengan logika inisialisasi kustom, panggil OnParametersSetAsync di kelas dasar:

protected override async Task OnParametersSetAsync()
{
    await ...

    await base.OnParametersSetAsync();
}

Tidak perlu memanggil ComponentBase.OnParametersSetAsync kecuali kelas dasar kustom digunakan dengan logika kustom. Untuk informasi selengkapnya, lihat bagian Metode siklus hidup kelas dasar.

Jika penanganan aktivitas disediakan dalam kode pengembang, lepaskan di pembuangan. Untuk informasi selengkapnya, lihat bagian Pembuangan komponen denganIAsyncDisposableIDisposable.

Untuk informasi selengkapnya tentang parameter dan batasan rute, lihat perutean dan navigasi inti Blazor ASP.NET.

Untuk contoh penerapan SetParametersAsync secara manual untuk meningkatkan performa dalam beberapa skenario, lihat praktik terbaik performa ASP.NET CoreBlazor.

Setelah render komponen (OnAfterRender{Async})

OnAfterRender dan OnAfterRenderAsync dipanggil setelah komponen dirender secara interaktif dan UI telah selesai memperbarui (misalnya, setelah elemen ditambahkan ke DOM browser). Referensi elemen dan komponen diisi pada saat ini. Gunakan tahap ini untuk melakukan langkah-langkah inisialisasi tambahan dengan konten yang dirender, seperti JS panggilan interop yang berinteraksi dengan elemen DOM yang dirender. Metode sinkron dipanggil sebelum metode asinkron.

Metode ini tidak dipanggil selama penyajian sebelumnya atau penyajian sisi server statis (SSR statis) di server karena proses tersebut tidak dilampirkan ke DOM browser langsung dan sudah selesai sebelum DOM diperbarui.

Untuk OnAfterRenderAsync, komponen tidak secara otomatis dirender ulang setelah penyelesaian yang dikembalikan Task untuk menghindari perulangan render tak terbatas.

OnAfterRender dan OnAfterRenderAsync dipanggil setelah komponen selesai dirender. Referensi elemen dan komponen diisi pada saat ini. Gunakan tahap ini untuk melakukan langkah-langkah inisialisasi tambahan dengan konten yang dirender, seperti JS panggilan interop yang berinteraksi dengan elemen DOM yang dirender. Metode sinkron dipanggil sebelum metode asinkron.

Metode ini tidak dipanggil selama pra-penyajian karena pra-penyajian tidak dilampirkan ke DOM browser langsung dan sudah selesai sebelum DOM diperbarui.

Untuk OnAfterRenderAsync, komponen tidak secara otomatis dirender ulang setelah penyelesaian yang dikembalikan Task untuk menghindari perulangan render tak terbatas.

Parameter firstRender untuk OnAfterRender dan OnAfterRenderAsync:

  • Diatur ke true pertama kali instans komponen dirender.
  • Dapat digunakan untuk memastikan bahwa pekerjaan inisialisasi hanya dilakukan sekali.

AfterRender.razor:

@page "/after-render"
@inject ILogger<AfterRender> Logger 

<PageTitle>After Render</PageTitle>

<h1>After Render Example</h1>

<p>
    <button @onclick="HandleClick">Log information (and trigger a render)</button>
</p>

<p>Study logged messages in the console.</p>

@code {
    protected override void OnAfterRender(bool firstRender)
    {
        Logger.LogInformation("OnAfterRender: firstRender = {FirstRender}", firstRender);
    }

    private void HandleClick()
    {
        Logger.LogInformation("HandleClick called");
    }
}
@page "/after-render"
@inject ILogger<AfterRender> Logger

<PageTitle>After Render</PageTitle>

<h1>After Render Example</h1>

<p>
    <button @onclick="HandleClick">Log information (and trigger a render)</button>
</p>

<p>Study logged messages in the console.</p>

@code {
    protected override void OnAfterRender(bool firstRender)
    {
        Logger.LogInformation("OnAfterRender: firstRender = {FirstRender}", firstRender);
    }

    private void HandleClick()
    {
        Logger.LogInformation("HandleClick called");
    }
}
@page "/after-render"
@inject ILogger<AfterRender> Logger 

<PageTitle>After Render</PageTitle>

<h1>After Render Example</h1>

<p>
    <button @onclick="HandleClick">Log information (and trigger a render)</button>
</p>

<p>Study logged messages in the console.</p>

@code {
    protected override void OnAfterRender(bool firstRender)
    {
        Logger.LogInformation("OnAfterRender: firstRender = {FirstRender}", firstRender);
    }

    private void HandleClick()
    {
        Logger.LogInformation("HandleClick called");
    }
}
@page "/after-render"
@using Microsoft.Extensions.Logging
@inject ILogger<AfterRender> Logger 

<h1>After Render Example</h1>

<p>
    <button @onclick="HandleClick">Log information (and trigger a render)</button>
</p>

<p>Study logged messages in the console.</p>

@code {
    protected override void OnAfterRender(bool firstRender)
    {
        Logger.LogInformation("OnAfterRender: firstRender = {FirstRender}", firstRender);
    }

    private void HandleClick()
    {
        Logger.LogInformation("HandleClick called");
    }
}
@page "/after-render"
@using Microsoft.Extensions.Logging
@inject ILogger<AfterRender> Logger

<h1>After Render Example</h1>

<p>
    <button @onclick="HandleClick">Log information (and trigger a render)</button>
</p>

<p>Study logged messages in the console.</p>

@code {
    protected override void OnAfterRender(bool firstRender)
    {
        Logger.LogInformation("OnAfterRender: firstRender = {FirstRender}", firstRender);
    }

    private void HandleClick()
    {
        Logger.LogInformation("HandleClick called");
    }
}

Sampel AfterRender.razor menghasilkan output berikut ke konsol saat halaman dimuat dan tombol dipilih:

OnAfterRender: firstRender = True
HandleClick called
OnAfterRender: firstRender = False

Pekerjaan asinkron segera setelah penyajian harus terjadi selama OnAfterRenderAsync peristiwa siklus hidup:

protected override async Task OnAfterRenderAsync(bool firstRender)
{
    ...
}

Jika kelas dasar kustom digunakan dengan logika inisialisasi kustom, panggil OnAfterRenderAsync di kelas dasar:

protected override async Task OnAfterRenderAsync(bool firstRender)
{
    ...

    await base.OnAfterRenderAsync(firstRender);
}

Tidak perlu memanggil ComponentBase.OnAfterRenderAsync kecuali kelas dasar kustom digunakan dengan logika kustom. Untuk informasi selengkapnya, lihat bagian Metode siklus hidup kelas dasar.

Bahkan jika Anda mengembalikan Task dari OnAfterRenderAsync, kerangka kerja tidak menjadwalkan siklus render lebih lanjut untuk komponen Anda setelah tugas tersebut selesai. Ini untuk menghindari perulangan render tak terbatas. Ini berbeda dari metode siklus hidup lainnya, yang menjadwalkan siklus render lebih lanjut setelah dikembalikan Task selesai.

OnAfterRender dan OnAfterRenderAsynctidak dipanggil selama proses pra-penyajian di server. Metode dipanggil ketika komponen dirender secara interaktif setelah pra-penyajian. Saat aplikasi melakukan pra-penyajian:

  1. Komponen dijalankan pada server untuk menghasilkan beberapa markup HTML statis dalam respons HTTP. Selama fase ini, OnAfterRender dan OnAfterRenderAsync tidak dipanggil.
  2. Blazor Ketika skrip (blazor.{server|webassembly|web}.js) dimulai di browser, komponen dimulai ulang dalam mode penyajian interaktif. Setelah komponen dimulai ulang, OnAfterRender dan OnAfterRenderAsyncdipanggil karena aplikasi tidak berada dalam fase pra-penyajian lagi.

Jika penanganan aktivitas disediakan dalam kode pengembang, lepaskan di pembuangan. Untuk informasi selengkapnya, lihat bagian Pembuangan komponen denganIAsyncDisposableIDisposable.

Metode siklus hidup kelas dasar

Saat mengesampingkan Blazormetode siklus hidup, tidak perlu memanggil metode siklus hidup kelas dasar untuk ComponentBase. Namun, komponen harus memanggil metode siklus hidup kelas dasar yang ditimpa dalam situasi berikut:

  • Saat mengambil ComponentBase.SetParametersAsyncalih , await base.SetParametersAsync(parameters); biasanya dipanggil karena metode kelas dasar memanggil metode siklus hidup lain dan memicu penyajian dengan cara yang kompleks. Untuk informasi selengkapnya, lihat bagian Kapan parameter diatur (SetParametersAsync).
  • Jika metode kelas dasar berisi logika yang harus dijalankan. Konsumen pustaka biasanya memanggil metode siklus hidup kelas dasar saat mewarisi kelas dasar karena kelas dasar pustaka sering memiliki logika siklus hidup kustom untuk dijalankan. Jika aplikasi menggunakan kelas dasar dari pustaka, lihat dokumentasi pustaka untuk panduan.

Dalam contoh berikut, base.OnInitialized(); dipanggil untuk memastikan bahwa metode kelas OnInitialized dasar dijalankan. Tanpa panggilan, BlazorRocksBase2.OnInitialized tidak dijalankan.

BlazorRocks2.razor:

@page "/blazor-rocks-2"
@inherits BlazorRocksBase2
@inject ILogger<BlazorRocks2> Logger

<PageTitle>Blazor Rocks!</PageTitle>

<h1>Blazor Rocks! Example 2</h1>

<p>
    @BlazorRocksText
</p>

@code {
    protected override void OnInitialized()
    {
        Logger.LogInformation("Initialization code of BlazorRocks2 executed!");

        base.OnInitialized();
    }
}
@page "/blazor-rocks-2"
@inherits BlazorRocksBase2
@inject ILogger<BlazorRocks2> Logger

<PageTitle>Blazor Rocks!</PageTitle>

<h1>Blazor Rocks! Example 2</h1>

<p>
    @BlazorRocksText
</p>

@code {
    protected override void OnInitialized()
    {
        Logger.LogInformation("Initialization code of BlazorRocks2 executed!");

        base.OnInitialized();
    }
}
@page "/blazor-rocks-2"
@inherits BlazorRocksBase2
@inject ILogger<BlazorRocks2> Logger

<PageTitle>Blazor Rocks!</PageTitle>

<h1>Blazor Rocks! Example 2</h1>

<p>
    @BlazorRocksText
</p>

@code {
    protected override void OnInitialized()
    {
        Logger.LogInformation("Initialization code of BlazorRocks2 executed!");

        base.OnInitialized();
    }
}
@page "/blazor-rocks-2"
@using Microsoft.Extensions.Logging
@inherits BlazorRocksBase2
@inject ILogger<BlazorRocks2> Logger

<h1>Blazor Rocks! Example 2</h1>

<p>
    @BlazorRocksText
</p>

@code {
    protected override void OnInitialized()
    {
        Logger.LogInformation("Initialization code of BlazorRocks2 executed!");

        base.OnInitialized();
    }
}
@page "/blazor-rocks-2"
@using Microsoft.Extensions.Logging
@inherits BlazorRocksBase2
@inject ILogger<BlazorRocks2> Logger

<h1>Blazor Rocks! Example 2</h1>

<p>
    @BlazorRocksText
</p>

@code {
    protected override void OnInitialized()
    {
        Logger.LogInformation("Initialization code of BlazorRocks2 executed!");

        base.OnInitialized();
    }
}

BlazorRocksBase2.cs:

using Microsoft.AspNetCore.Components;

namespace BlazorSample;

public class BlazorRocksBase2 : ComponentBase
{
    [Inject]
    private ILogger<BlazorRocksBase2> Logger { get; set; } = default!;

    public string BlazorRocksText { get; set; } =
        "Blazor rocks the browser!";

    protected override void OnInitialized()
    {
        Logger.LogInformation("Initialization code of BlazorRocksBase2 executed!");
    }
}
using Microsoft.AspNetCore.Components;

namespace BlazorSample;

public class BlazorRocksBase2 : ComponentBase
{
    [Inject]
    private ILogger<BlazorRocksBase2> Logger { get; set; } = default!;

    public string BlazorRocksText { get; set; } =
        "Blazor rocks the browser!";

    protected override void OnInitialized()
    {
        Logger.LogInformation("Initialization code of BlazorRocksBase2 executed!");
    }
}
using Microsoft.AspNetCore.Components;

namespace BlazorSample;

public class BlazorRocksBase2 : ComponentBase
{
    [Inject]
    private ILogger<BlazorRocksBase2> Logger { get; set; } = default!;

    public string BlazorRocksText { get; set; } =
        "Blazor rocks the browser!";

    protected override void OnInitialized()
    {
        Logger.LogInformation("Initialization code of BlazorRocksBase2 executed!");
    }
}
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Logging;

namespace BlazorSample;

public class BlazorRocksBase2 : ComponentBase
{
    [Inject]
    private ILogger<BlazorRocksBase2> Logger { get; set; } = default!;

    public string BlazorRocksText { get; set; } =
        "Blazor rocks the browser!";

    protected override void OnInitialized()
    {
        Logger.LogInformation("Initialization code of BlazorRocksBase2 executed!");
    }
}
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Logging;

namespace BlazorSample;

public class BlazorRocksBase2 : ComponentBase
{
    [Inject]
    private ILogger<BlazorRocksBase2> Logger { get; set; } = default!;

    public string BlazorRocksText { get; set; } =
        "Blazor rocks the browser!";

    protected override void OnInitialized()
    {
        Logger.LogInformation("Initialization code of BlazorRocksBase2 executed!");
    }
}

Perubahan status (StateHasChanged)

StateHasChanged memberi tahu komponen bahwa statusnya telah berubah. Jika berlaku, panggilan StateHasChanged menyebabkan komponen dirender.

StateHasChanged dipanggil secara otomatis untuk EventCallback metode. Untuk informasi selengkapnya tentang panggilan balik peristiwa, lihat penanganan peristiwa ASP.NET CoreBlazor.

Untuk informasi selengkapnya tentang penyajian komponen dan kapan harus memanggil StateHasChanged, termasuk kapan harus memanggilnya dengan ComponentBase.InvokeAsync, lihat ASP.NET penyajian komponen CoreRazor.

Menangani tindakan asinkron yang tidak lengkap saat dirender

Tindakan asinkron yang dilakukan dalam peristiwa siklus hidup mungkin belum selesai sebelum komponen dirender. Objek mungkin null atau tidak lengkap diisi dengan data saat metode siklus hidup sedang dijalankan. Berikan logika penyajian untuk mengonfirmasi bahwa objek diinisialisasi. Merender elemen UI tempat penampung (misalnya, pesan pemuatan) sementara objek adalah null.

Dalam komponen berikut, OnInitializedAsync ditimpa untuk secara asinkron menyediakan data peringkat film (movies). Ketika movies adalah null, pesan pemuatan ditampilkan kepada pengguna. Task Setelah dikembalikan oleh OnInitializedAsync selesai, komponen dirender dengan status yang diperbarui.

<h1>Sci-Fi Movie Ratings</h1>

@if (movies == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <ul>
        @foreach (var movie in movies)
        {
            <li>@movie.Title &mdash; @movie.Rating</li>
        }
    </ul>
}

@code {
    private Movies[]? movies;

    protected override async Task OnInitializedAsync()
    {
        movies = await GetMovieRatings(DateTime.Now);
    }
}

Menangani kesalahan

Untuk informasi tentang penanganan kesalahan selama eksekusi metode siklus hidup, lihat Menangani kesalahan di aplikasi ASP.NET CoreBlazor.

Koneksi ulang stateful setelah pra-penyajian

Saat melakukan pra-penyajian di server, komponen awalnya dirender secara statis sebagai bagian dari halaman. Setelah browser membuat SignalR koneksi kembali ke server, komponen dirender lagi dan interaktif. OnInitialized{Async} Jika metode siklus hidup untuk menginisialisasi komponen ada, metode dijalankan dua kali:

  • Ketika komponen dirender secara statis.
  • Setelah koneksi server dibuat.

Ini dapat mengakibatkan perubahan nyata dalam data yang ditampilkan di UI ketika komponen akhirnya dirender. Untuk menghindari perilaku ini, teruskan pengidentifikasi untuk menyimpan status selama pra-penyajian dan untuk mengambil status setelah pra-penyajian.

Kode berikut menunjukkan WeatherForecastService yang menghindari perubahan tampilan data karena pra-penyajian. Yang ditunggu (await Task.Delay(...)) mensimulasikan Delay penundaan singkat sebelum mengembalikan data dari GetForecastAsync metode .

Tambahkan IMemoryCache layanan dengan AddMemoryCache pada koleksi layanan dalam file aplikasi Program :

builder.Services.AddMemoryCache();

WeatherForecastService.cs:

using Microsoft.Extensions.Caching.Memory;

namespace BlazorSample;

public class WeatherForecastService(IMemoryCache memoryCache)
{
    private static readonly string[] summaries =
    [
        "Freezing", "Bracing", "Chilly", "Cool", "Mild",
        "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
    ];

    public IMemoryCache MemoryCache { get; } = memoryCache;

    public Task<WeatherForecast[]?> GetForecastAsync(DateOnly startDate)
    {
        return MemoryCache.GetOrCreateAsync(startDate, async e =>
        {
            e.SetOptions(new MemoryCacheEntryOptions
            {
                AbsoluteExpirationRelativeToNow =
                    TimeSpan.FromSeconds(30)
            });

            await Task.Delay(TimeSpan.FromSeconds(10));

            return Enumerable.Range(1, 5).Select(index => new WeatherForecast
            {
                Date = startDate.AddDays(index),
                TemperatureC = Random.Shared.Next(-20, 55),
                Summary = summaries[Random.Shared.Next(summaries.Length)]
            }).ToArray();
        });
    }
}
using Microsoft.Extensions.Caching.Memory;

public class WeatherForecastService
{
    private static readonly string[] summaries = new[]
    {
        "Freezing", "Bracing", "Chilly", "Cool", "Mild",
        "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
    };

    public WeatherForecastService(IMemoryCache memoryCache)
    {
        MemoryCache = memoryCache;
    }

    public IMemoryCache MemoryCache { get; }

    public Task<WeatherForecast[]?> GetForecastAsync(DateTime startDate)
    {
        return MemoryCache.GetOrCreateAsync(startDate, async e =>
        {
            e.SetOptions(new MemoryCacheEntryOptions
            {
                AbsoluteExpirationRelativeToNow =
                    TimeSpan.FromSeconds(30)
            });

            await Task.Delay(TimeSpan.FromSeconds(10));

            return Enumerable.Range(1, 5).Select(index => new WeatherForecast
            {
                Date = startDate.AddDays(index),
                TemperatureC = Random.Shared.Next(-20, 55),
                Summary = summaries[Random.Shared.Next(summaries.Length)]
            }).ToArray();
        });
    }
}
using Microsoft.Extensions.Caching.Memory;

public class WeatherForecastService
{
    private static readonly string[] summaries = new[]
    {
        "Freezing", "Bracing", "Chilly", "Cool", "Mild",
        "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
    };

    public WeatherForecastService(IMemoryCache memoryCache)
    {
        MemoryCache = memoryCache;
    }

    public IMemoryCache MemoryCache { get; }

    public Task<WeatherForecast[]> GetForecastAsync(DateTime startDate)
    {
        return MemoryCache.GetOrCreateAsync(startDate, async e =>
        {
            e.SetOptions(new MemoryCacheEntryOptions
            {
                AbsoluteExpirationRelativeToNow =
                    TimeSpan.FromSeconds(30)
            });

            await Task.Delay(TimeSpan.FromSeconds(10));

            return Enumerable.Range(1, 5).Select(index => new WeatherForecast
            {
                Date = startDate.AddDays(index),
                TemperatureC = Random.Shared.Next(-20, 55),
                Summary = summaries[Random.Shared.Next(summaries.Length)]
            }).ToArray();
        });
    }
}
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Memory;
using BlazorSample.Shared;

public class WeatherForecastService
{
    private static readonly string[] summaries = new[]
    {
        "Freezing", "Bracing", "Chilly", "Cool", "Mild",
        "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
    };

    public WeatherForecastService(IMemoryCache memoryCache)
    {
        MemoryCache = memoryCache;
    }

    public IMemoryCache MemoryCache { get; }

    public Task<WeatherForecast[]> GetForecastAsync(DateTime startDate)
    {
        return MemoryCache.GetOrCreateAsync(startDate, async e =>
        {
            e.SetOptions(new MemoryCacheEntryOptions
            {
                AbsoluteExpirationRelativeToNow =
                    TimeSpan.FromSeconds(30)
            });

            var rng = new Random();

            await Task.Delay(TimeSpan.FromSeconds(10));

            return Enumerable.Range(1, 5).Select(index => new WeatherForecast
            {
                Date = startDate.AddDays(index),
                TemperatureC = rng.Next(-20, 55),
                Summary = summaries[rng.Next(summaries.Length)]
            }).ToArray();
        });
    }
}
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Memory;
using BlazorSample.Shared;

public class WeatherForecastService
{
    private static readonly string[] summaries = new[]
    {
        "Freezing", "Bracing", "Chilly", "Cool", "Mild",
        "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
    };

    public WeatherForecastService(IMemoryCache memoryCache)
    {
        MemoryCache = memoryCache;
    }

    public IMemoryCache MemoryCache { get; }

    public Task<WeatherForecast[]> GetForecastAsync(DateTime startDate)
    {
        return MemoryCache.GetOrCreateAsync(startDate, async e =>
        {
            e.SetOptions(new MemoryCacheEntryOptions
            {
                AbsoluteExpirationRelativeToNow =
                    TimeSpan.FromSeconds(30)
            });

            var rng = new Random();

            await Task.Delay(TimeSpan.FromSeconds(10));

            return Enumerable.Range(1, 5).Select(index => new WeatherForecast
            {
                Date = startDate.AddDays(index),
                TemperatureC = rng.Next(-20, 55),
                Summary = summaries[rng.Next(summaries.Length)]
            }).ToArray();
        });
    }
}

Untuk informasi selengkapnya tentang RenderMode, lihat panduan ASP.NET CoreBlazorSignalR.

Konten di bagian ini berfokus pada Blazor Web Apps dan koneksi ulang statefulSignalR. Untuk mempertahankan status selama eksekusi kode inisialisasi saat melakukan prarender, lihat Prarender komponen ASP.NET CoreRazor.

Meskipun konten di bagian ini berfokus pada Blazor Server dan koneksi ulang yang statefulSignalR, skenario untuk prarender dalam solusi yang Blazor WebAssembly dihosting (WebAssemblyPrerendered) melibatkan kondisi dan pendekatan serupa untuk mencegah eksekusi kode pengembang dua kali. Untuk mempertahankan status selama eksekusi kode inisialisasi saat melakukan prarender, lihat Merender dan mengintegrasikan komponen ASP.NET CoreRazor.

Pra-penyajian dengan interop JavaScript

Bagian ini berlaku untuk aplikasi sisi server yang merender Razor komponen sebelumnya. Pra-penyajian tercakup dalam komponen Prerender ASP.NET CoreRazor.

Catatan

Navigasi internal untuk perutean interaktif di Blazor Web Apps tidak melibatkan permintaan konten halaman baru dari server. Oleh karena itu, pra-penyajian tidak terjadi untuk permintaan halaman internal. Jika aplikasi mengadopsi perutean interaktif, lakukan pemuatan ulang halaman penuh untuk contoh komponen yang menunjukkan perilaku prarender. Untuk informasi selengkapnya, lihat Prarender komponen ASP.NET CoreRazor.

Bagian ini berlaku untuk aplikasi sisi server dan aplikasi yang dihosting Blazor WebAssembly yang melakukan prarender Razor komponen. Pra-penyajian tercakup dalam Prarender dan mengintegrasikan komponen ASP.NET CoreRazor.

Saat aplikasi melakukan pra-penyajian, tindakan tertentu, seperti memanggil ke JavaScript (JS), tidak dimungkinkan.

Untuk contoh berikut, fungsi dipanggil setElementText1 dengan JSRuntimeExtensions.InvokeVoidAsync dan tidak mengembalikan nilai.

Catatan

Untuk panduan umum tentang JS lokasi dan rekomendasi kami untuk aplikasi produksi, lihat Lokasi JavaScript di aplikasi ASP.NET CoreBlazor.

<script>
  window.setElementText1 = (element, text) => element.innerText = text;
</script>

Peringatan

Contoh sebelumnya memodifikasi DOM secara langsung hanya untuk tujuan demonstrasi. Memodifikasi DOM secara langsung dengan JS tidak disarankan dalam sebagian besar skenario karena JS dapat mengganggu Blazorpelacakan perubahan. Untuk informasi selengkapnya, lihat ASP.NET Blazor interoperabilitas Core JavaScript (JS interop).

Peristiwa OnAfterRender{Async} siklus hidup tidak dipanggil selama proses pra-penyajian di server. Ambil alih OnAfterRender{Async} metode untuk menunda JS panggilan interop sampai setelah komponen dirender dan interaktif pada klien setelah pra-penyajian.

PrerenderedInterop1.razor:

@page "/prerendered-interop-1"
@using Microsoft.JSInterop
@inject IJSRuntime JS

<PageTitle>Prerendered Interop 1</PageTitle>

<h1>Prerendered Interop Example 1</h1>

<div @ref="divElement">Text during render</div>

@code {
    private ElementReference divElement;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            await JS.InvokeVoidAsync(
                "setElementText1", divElement, "Text after render");
        }
    }
}

Catatan

Contoh sebelumnya mencemari klien dengan fungsi global. Untuk pendekatan yang lebih baik dalam aplikasi produksi, lihat Isolasi JavaScript dalam modul JavaScript.

Contoh:

export setElementText1 = (element, text) => element.innerText = text;

Komponen berikut menunjukkan cara menggunakan JS interop sebagai bagian dari logika inisialisasi komponen dengan cara yang kompatibel dengan pra-penyajian. Komponen menunjukkan bahwa dimungkinkan untuk memicu pembaruan penyajian dari dalam OnAfterRenderAsync. Pengembang harus berhati-hati untuk menghindari pembuatan perulangan tak terbatas dalam skenario ini.

Untuk contoh berikut, fungsi dipanggil setElementText2 dengan IJSRuntime.InvokeAsync dan mengembalikan nilai.

Catatan

Untuk panduan umum tentang JS lokasi dan rekomendasi kami untuk aplikasi produksi, lihat Lokasi JavaScript di aplikasi ASP.NET CoreBlazor.

<script>
  window.setElementText2 = (element, text) => {
    element.innerText = text;
    return text;
  };
</script>

Peringatan

Contoh sebelumnya memodifikasi DOM secara langsung hanya untuk tujuan demonstrasi. Memodifikasi DOM secara langsung dengan JS tidak disarankan dalam sebagian besar skenario karena JS dapat mengganggu Blazorpelacakan perubahan. Untuk informasi selengkapnya, lihat ASP.NET Blazor interoperabilitas Core JavaScript (JS interop).

Di mana JSRuntime.InvokeAsync dipanggil, ElementReference hanya digunakan dalam OnAfterRenderAsync dan bukan dalam metode siklus hidup sebelumnya karena tidak ada elemen HTML DOM sampai setelah komponen dirender.

StateHasChangeddipanggil untuk merender komponen dengan status baru yang diperoleh dari JS panggilan interop (untuk informasi selengkapnya, lihat ASP.NET penyajian komponen CoreRazor). Kode tidak membuat perulangan tak terbatas karena StateHasChanged hanya dipanggil ketika data adalah null.

PrerenderedInterop2.razor:

@page "/prerendered-interop-2"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS

<PageTitle>Prerendered Interop 2</PageTitle>

<h1>Prerendered Interop Example 2</h1>

<p>
    Get value via JS interop call:
    <strong id="val-get-by-interop">@(infoFromJs ?? "No value yet")</strong>
</p>

<p>
    Set value via JS interop call: 
    <strong id="val-set-by-interop" @ref="divElement"></strong>
</p>



@code {
    private string? infoFromJs;
    private ElementReference divElement;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender && infoFromJs == null)
        {
            infoFromJs = await JS.InvokeAsync<string>(
                "setElementText2", divElement, "Hello from interop call!");

            StateHasChanged();
        }
    }
}

Catatan

Contoh sebelumnya mencemari klien dengan fungsi global. Untuk pendekatan yang lebih baik dalam aplikasi produksi, lihat Isolasi JavaScript dalam modul JavaScript.

Contoh:

export setElementText2 = (element, text) => {
  element.innerText = text;
  return text;
};

Pembuangan komponen dengan IDisposable dan IAsyncDisposable

Jika komponen mengimplementasikan IDisposable atau IAsyncDisposable, kerangka kerja memanggil pembuangan sumber daya saat komponen dihapus dari UI. Jangan mengandalkan waktu yang tepat ketika metode ini dijalankan. Misalnya, IAsyncDisposable dapat dipicu sebelum atau sesudah asinkron Task yang ditunggu dipanggil OnInitalizedAsync atau selesai. Selain itu, kode pembuangan objek tidak boleh mengasumsikan bahwa objek yang dibuat selama inisialisasi atau metode siklus hidup lainnya ada.

Komponen tidak perlu diimplementasikan IDisposable dan IAsyncDisposable secara bersamaan. Jika keduanya diimplementasikan, kerangka kerja hanya menjalankan kelebihan beban asinkron.

Kode pengembang harus memastikan bahwa IAsyncDisposable implementasi tidak membutuhkan waktu lama untuk diselesaikan.

Pembuangan referensi objek interop JavaScript

Contoh di seluruh artikel interop JavaScript (JS) menunjukkan pola pembuangan objek umum:

JS referensi objek interop diimplementasikan sebagai peta yang dikunci oleh pengidentifikasi di sisi JS panggilan interop yang membuat referensi. Ketika pembuangan objek dimulai dari .NET atau JS sisi, Blazor menghapus entri dari peta, dan objek dapat menjadi sampah yang dikumpulkan selama tidak ada referensi kuat lainnya ke objek yang ada.

Minimal, selalu buang objek yang dibuat di sisi .NET untuk menghindari kebocoran memori terkelola .NET.

Tugas pembersihan DOM selama pembuangan komponen

Untuk informasi selengkapnya, lihat ASP.NET Blazor interoperabilitas Core JavaScript (JS interop).

Untuk panduan tentang JSDisconnectedException kapan sirkuit terputus, lihat ASP.NET Blazor interoperabilitas Core JavaScript (JS interop). Untuk panduan penanganan kesalahan interop JavaScript umum, lihat bagian interop JavaScript di Menangani kesalahan di aplikasi ASP.NET CoreBlazor.

Sinkron IDisposable

Untuk tugas pembuangan sinkron, gunakan IDisposable.Dispose.

Komponen berikut:

  • IDisposable Menerapkan dengan direktif@implementsRazor.
  • objMembuang , yang merupakan jenis yang mengimplementasikan IDisposable.
  • Pemeriksaan null dilakukan karena obj dibuat dalam metode siklus hidup (tidak ditampilkan).
@implements IDisposable

...

@code {
    ...

    public void Dispose()
    {
        obj?.Dispose();
    }
}

Jika satu objek memerlukan pembuangan, lambda dapat digunakan untuk membuang objek ketika Dispose dipanggil. Contoh berikut muncul di artikel penyajian komponen ASP.NET Core Razor dan menunjukkan penggunaan ekspresi lambda untuk pembuangan Timer.

TimerDisposal1.razor:

@page "/timer-disposal-1"
@using System.Timers
@implements IDisposable

<PageTitle>Timer Disposal 1</PageTitle>

<h1>Timer Disposal Example 1</h1>

<p>Current count: @currentCount</p>

@code {
    private int currentCount = 0;
    private Timer timer = new(1000);

    protected override void OnInitialized()
    {
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            currentCount++;
            StateHasChanged();
        });
    }

    public void Dispose() => timer.Dispose();
}

CounterWithTimerDisposal1.razor:

@page "/counter-with-timer-disposal-1"
@using System.Timers
@implements IDisposable

<h1>Counter with <code>Timer</code> disposal</h1>

<p>Current count: @currentCount</p>

@code {
    private int currentCount = 0;
    private Timer timer = new(1000);

    protected override void OnInitialized()
    {
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            currentCount++;
            StateHasChanged();
        });
    }

    public void Dispose() => timer.Dispose();
}

CounterWithTimerDisposal1.razor:

@page "/counter-with-timer-disposal-1"
@using System.Timers
@implements IDisposable

<h1>Counter with <code>Timer</code> disposal</h1>

<p>Current count: @currentCount</p>

@code {
    private int currentCount = 0;
    private Timer timer = new(1000);

    protected override void OnInitialized()
    {
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            currentCount++;
            StateHasChanged();
        });
    }

    public void Dispose() => timer.Dispose();
}

CounterWithTimerDisposal1.razor:

@page "/counter-with-timer-disposal-1"
@using System.Timers
@implements IDisposable

<h1>Counter with <code>Timer</code> disposal</h1>

<p>Current count: @currentCount</p>

@code {
    private int currentCount = 0;
    private Timer timer = new(1000);

    protected override void OnInitialized()
    {
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            currentCount++;
            StateHasChanged();
        });
    }

    public void Dispose() => timer.Dispose();
}

CounterWithTimerDisposal1.razor:

@page "/counter-with-timer-disposal-1"
@using System.Timers
@implements IDisposable

<h1>Counter with <code>Timer</code> disposal</h1>

<p>Current count: @currentCount</p>

@code {
    private int currentCount = 0;
    private Timer timer = new Timer(1000);

    protected override void OnInitialized()
    {
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            currentCount++;
            StateHasChanged();
        });
    }

    public void Dispose() => timer.Dispose();
}

Catatan

Dalam contoh sebelumnya, panggilan ke StateHasChanged dibungkus oleh panggilan ke ComponentBase.InvokeAsync karena panggilan balik dipanggil di luar Blazorkonteks sinkronisasi . Untuk informasi lebih lanjut, lihat perenderan komponen Razor ASP.NET Core.

Jika objek dibuat dalam metode siklus hidup, seperti OnInitialized{Async}, periksa null sebelum memanggil Dispose.

TimerDisposal2.razor:

@page "/timer-disposal-2"
@using System.Timers
@implements IDisposable

<PageTitle>Timer Disposal 2</PageTitle>

<h1>Timer Disposal Example 2</h1>

<p>Current count: @currentCount</p>

@code {
    private int currentCount = 0;
    private Timer? timer;

    protected override void OnInitialized()
    {
        timer = new Timer(1000);
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            currentCount++;
            StateHasChanged();
        });
    }

    public void Dispose() => timer?.Dispose();
}

CounterWithTimerDisposal2.razor:

@page "/counter-with-timer-disposal-2"
@using System.Timers
@implements IDisposable

<h1>Counter with <code>Timer</code> disposal</h1>

<p>Current count: @currentCount</p>

@code {
    private int currentCount = 0;
    private Timer? timer;

    protected override void OnInitialized()
    {
        timer = new Timer(1000);
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            currentCount++;
            StateHasChanged();
        });
    }

    public void Dispose() => timer?.Dispose();
}

CounterWithTimerDisposal2.razor:

@page "/counter-with-timer-disposal-2"
@using System.Timers
@implements IDisposable

<h1>Counter with <code>Timer</code> disposal</h1>

<p>Current count: @currentCount</p>

@code {
    private int currentCount = 0;
    private Timer? timer;

    protected override void OnInitialized()
    {
        timer = new Timer(1000);
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            currentCount++;
            StateHasChanged();
        });
    }

    public void Dispose() => timer?.Dispose();
}

CounterWithTimerDisposal2.razor:

@page "/counter-with-timer-disposal-2"
@using System.Timers
@implements IDisposable

<h1>Counter with <code>Timer</code> disposal</h1>

<p>Current count: @currentCount</p>

@code {
    private int currentCount = 0;
    private Timer timer;

    protected override void OnInitialized()
    {
        timer = new Timer(1000);
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            currentCount++;
            StateHasChanged();
        });
    }

    public void Dispose() => timer?.Dispose();
}

CounterWithTimerDisposal2.razor:

@page "/counter-with-timer-disposal-2"
@using System.Timers
@implements IDisposable

<h1>Counter with <code>Timer</code> disposal</h1>

<p>Current count: @currentCount</p>

@code {
    private int currentCount = 0;
    private Timer timer;

    protected override void OnInitialized()
    {
        timer = new Timer(1000);
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            currentCount++;
            StateHasChanged();
        });
    }

    public void Dispose() => timer?.Dispose();
}

Untuk informasi selengkapnya, lihat:

Asynchronous IAsyncDisposable

Untuk tugas pembuangan asinkron, gunakan IAsyncDisposable.DisposeAsync.

Komponen berikut:

  • IAsyncDisposable Menerapkan dengan direktif@implementsRazor.
  • objMembuang , yang merupakan jenis yang tidak dikelola yang mengimplementasikan IAsyncDisposable.
  • Pemeriksaan null dilakukan karena obj dibuat dalam metode siklus hidup (tidak ditampilkan).
@implements IAsyncDisposable

...

@code {
    ...

    public async ValueTask DisposeAsync()
    {
        if (obj is not null)
        {
            await obj.DisposeAsync();
        }
    }
}

Untuk informasi selengkapnya, lihat:

Penugasan null ke objek yang dibuang

Biasanya, tidak perlu menetapkan null ke objek yang dibuang setelah memanggil/DisposeDisposeAsync . Kasus langka untuk ditetapkan null meliputi yang berikut ini:

  • Jika jenis objek diimplementasikan dengan buruk dan tidak mentolerir panggilan berulang ke Dispose/DisposeAsync, tetapkan null setelah pembuangan untuk dengan anggun melewati panggilan lebih lanjut ke .Dispose/DisposeAsync
  • Jika proses berumur panjang terus memegang referensi ke objek yang dibuang, menetapkan null memungkinkan pengumpul sampah untuk membebaskan objek terlepas dari proses berumur panjang yang memegang referensi untuk itu.

Ini adalah skenario yang tidak biasa. Untuk objek yang diimplementasikan dengan benar dan bersifat normal, tidak ada gunanya menetapkan null ke objek yang dibuang. Dalam kasus yang jarang terjadi di mana objek harus ditetapkan , sebaiknya dokumentasikan nullalasan dan mencari solusi yang mencegah kebutuhan untuk menetapkan null.

StateHasChanged

Catatan

Memanggil StateHasChanged dan DisposeDisposeAsync tidak didukung. StateHasChanged mungkin dipanggil sebagai bagian dari merobek perender, jadi meminta pembaruan UI pada saat itu tidak didukung.

Penangan kejadian

Selalu berhenti berlangganan penanganan aktivitas dari peristiwa .NET. Contoh formulir berikut Blazor menunjukkan cara berhenti berlangganan penanganan aktivitas dalam Dispose metode :

  • Bidang privat dan pendekatan lambda

    @implements IDisposable
    
    <EditForm ... EditContext="editContext" ...>
        ...
        <button type="submit" disabled="@formInvalid">Submit</button>
    </EditForm>
    
    @code {
        ...
    
        private EventHandler<FieldChangedEventArgs>? fieldChanged;
    
        protected override void OnInitialized()
        {
            editContext = new(model);
    
            fieldChanged = (_, __) =>
            {
                ...
            };
    
            editContext.OnFieldChanged += fieldChanged;
        }
    
        public void Dispose()
        {
            editContext.OnFieldChanged -= fieldChanged;
        }
    }
    
  • Pendekatan metode privat

    @implements IDisposable
    
    <EditForm ... EditContext="editContext" ...>
        ...
        <button type="submit" disabled="@formInvalid">Submit</button>
    </EditForm>
    
    @code {
        ...
    
        protected override void OnInitialized()
        {
            editContext = new(model);
            editContext.OnFieldChanged += HandleFieldChanged;
        }
    
        private void HandleFieldChanged(object sender, FieldChangedEventArgs e)
        {
            ...
        }
    
        public void Dispose()
        {
            editContext.OnFieldChanged -= HandleFieldChanged;
        }
    }
    

Untuk informasi selengkapnya, lihat bagian Dan pembuangan IDisposableIAsyncDisposable komponen.

Untuk informasi selengkapnya tentang EditForm komponen dan formulir, lihat gambaran umum formulir ASP.NET Core Blazor dan artikel formulir lainnya di simpul Formulir.

Fungsi, metode, dan ekspresi anonim

Saat fungsi, metode, atau ekspresi anonim, digunakan, tidak perlu menerapkan IDisposable dan berhenti berlangganan delegasi. Namun, gagal berhenti berlangganan delegasi adalah masalah ketika objek yang mengekspos peristiwa keluar dari masa pakai komponen yang mendaftarkan delegasi. Ketika ini terjadi, kebocoran memori menghasilkan karena delegasi terdaftar menjaga objek asli tetap hidup. Oleh karena itu, hanya gunakan pendekatan berikut ketika Anda tahu bahwa acara mendelegasikan dibuang dengan cepat. Ketika ragu tentang masa pakai objek yang memerlukan pembuangan, berlangganan metode delegasi dan membuang delegasi dengan benar seperti yang ditunjukkan contoh sebelumnya.

  • Pendekatan metode lambda anonim (pembuangan eksplisit tidak diperlukan):

    private void HandleFieldChanged(object sender, FieldChangedEventArgs e)
    {
        formInvalid = !editContext.Validate();
        StateHasChanged();
    }
    
    protected override void OnInitialized()
    {
        editContext = new(starship);
        editContext.OnFieldChanged += (s, e) => HandleFieldChanged((editContext)s, e);
    }
    
  • Pendekatan ekspresi lambda anonim (pembuangan eksplisit tidak diperlukan):

    private ValidationMessageStore? messageStore;
    
    [CascadingParameter]
    private EditContext? CurrentEditContext { get; set; }
    
    protected override void OnInitialized()
    {
        ...
    
        messageStore = new(CurrentEditContext);
    
        CurrentEditContext.OnValidationRequested += (s, e) => messageStore.Clear();
        CurrentEditContext.OnFieldChanged += (s, e) => 
            messageStore.Clear(e.FieldIdentifier);
    }
    

    Contoh lengkap kode sebelumnya dengan ekspresi lambda anonim muncul di artikel validasi formulir ASP.NET CoreBlazor.

Untuk informasi selengkapnya, lihat Membersihkan sumber daya yang tidak dikelola dan topik yang mengikutinya tentang menerapkan Dispose metode dan DisposeAsync .

Pekerjaan latar belakang yang dapat dibatalkan

Komponen sering melakukan pekerjaan latar belakang yang berjalan lama, seperti melakukan panggilan jaringan (HttpClient) dan berinteraksi dengan database. Disarankan untuk menghentikan pekerjaan latar belakang untuk menghemat sumber daya sistem dalam beberapa situasi. Misalnya, operasi asinkron latar belakang tidak secara otomatis berhenti saat pengguna menavigasi jauh dari komponen.

Alasan lain mengapa item kerja latar belakang mungkin memerlukan pembatalan meliputi:

  • Tugas latar belakang yang dijalankan dimulai dengan data input yang rusak atau parameter pemrosesan.
  • Kumpulan item kerja latar belakang yang dijalankan saat ini harus diganti dengan sekumpulan item kerja baru.
  • Prioritas tugas yang saat ini dijalankan harus diubah.
  • Aplikasi harus dimatikan untuk penyebaran ulang server.
  • Sumber daya server menjadi terbatas, membutuhkan penjadwalan ulang item kerja latar belakang.

Untuk menerapkan pola kerja latar belakang yang dapat dibatalkan dalam komponen:

Dalam contoh berikut:

  • await Task.Delay(5000, cts.Token); mewakili pekerjaan latar belakang asinkron yang berjalan lama.
  • BackgroundResourceMethod mewakili metode latar belakang yang berjalan lama yang seharusnya tidak dimulai jika Resource dibuang sebelum metode dipanggil.

BackgroundWork.razor:

@page "/background-work"
@implements IDisposable
@inject ILogger<BackgroundWork> Logger

<PageTitle>Background Work</PageTitle>

<h1>Background Work Example</h1>

<p>
    <button @onclick="LongRunningWork">Trigger long running work</button>
    <button @onclick="Dispose">Trigger Disposal</button>
</p>
<p>Study logged messages in the console.</p>
<p>
    If you trigger disposal within 10 seconds of page load, the 
    <code>BackgroundResourceMethod</code> isn't executed.
</p>
<p>
    If disposal occurs after <code>BackgroundResourceMethod</code> is called but before action
    is taken on the resource, an <code>ObjectDisposedException</code> is thrown by 
    <code>BackgroundResourceMethod</code>, and the resource isn't processed.
</p>

@code {
    private Resource resource = new();
    private CancellationTokenSource cts = new();
    private IList<string> messages = [];

    protected async Task LongRunningWork()
    {
        Logger.LogInformation("Long running work started");

        await Task.Delay(10000, cts.Token);

        cts.Token.ThrowIfCancellationRequested();
        resource.BackgroundResourceMethod(Logger);
    }

    public void Dispose()
    {
        Logger.LogInformation("Executing Dispose");

        if (!cts.IsCancellationRequested)
        {
            cts.Cancel();
        }
        
        cts?.Dispose();
        resource?.Dispose();
    }

    private class Resource : IDisposable
    {
        private bool disposed;

        public void BackgroundResourceMethod(ILogger<BackgroundWork> logger)
        {
            logger.LogInformation("BackgroundResourceMethod: Start method");

            if (disposed)
            {
                logger.LogInformation("BackgroundResourceMethod: Disposed");
                throw new ObjectDisposedException(nameof(Resource));
            }

            // Take action on the Resource

            logger.LogInformation("BackgroundResourceMethod: Action on Resource");
        }

        public void Dispose() => disposed = true;
    }
}
@page "/background-work"
@using System.Threading
@using Microsoft.Extensions.Logging
@implements IDisposable
@inject ILogger<BackgroundWork> Logger

<button @onclick="LongRunningWork">Trigger long running work</button>
<button @onclick="Dispose">Trigger Disposal</button>

@code {
    private Resource resource = new();
    private CancellationTokenSource cts = new();

    protected async Task LongRunningWork()
    {
        Logger.LogInformation("Long running work started");

        await Task.Delay(5000, cts.Token);

        cts.Token.ThrowIfCancellationRequested();
        resource.BackgroundResourceMethod(Logger);
    }

    public void Dispose()
    {
        Logger.LogInformation("Executing Dispose");
        cts.Cancel();
        cts.Dispose();
        resource?.Dispose();
    }

    private class Resource : IDisposable
    {
        private bool disposed;

        public void BackgroundResourceMethod(ILogger<BackgroundWork> logger)
        {
            logger.LogInformation("BackgroundResourceMethod: Start method");

            if (disposed)
            {
                logger.LogInformation("BackgroundResourceMethod: Disposed");
                throw new ObjectDisposedException(nameof(Resource));
            }

            // Take action on the Resource

            logger.LogInformation("BackgroundResourceMethod: Action on Resource");
        }

        public void Dispose()
        {
            disposed = true;
        }
    }
}
@page "/background-work"
@using System.Threading
@using Microsoft.Extensions.Logging
@implements IDisposable
@inject ILogger<BackgroundWork> Logger

<button @onclick="LongRunningWork">Trigger long running work</button>
<button @onclick="Dispose">Trigger Disposal</button>

@code {
    private Resource resource = new();
    private CancellationTokenSource cts = new();

    protected async Task LongRunningWork()
    {
        Logger.LogInformation("Long running work started");

        await Task.Delay(5000, cts.Token);

        cts.Token.ThrowIfCancellationRequested();
        resource.BackgroundResourceMethod(Logger);
    }

    public void Dispose()
    {
        Logger.LogInformation("Executing Dispose");
        cts.Cancel();
        cts.Dispose();
        resource?.Dispose();
    }

    private class Resource : IDisposable
    {
        private bool disposed;

        public void BackgroundResourceMethod(ILogger<BackgroundWork> logger)
        {
            logger.LogInformation("BackgroundResourceMethod: Start method");

            if (disposed)
            {
                logger.LogInformation("BackgroundResourceMethod: Disposed");
                throw new ObjectDisposedException(nameof(Resource));
            }

            // Take action on the Resource

            logger.LogInformation("BackgroundResourceMethod: Action on Resource");
        }

        public void Dispose()
        {
            disposed = true;
        }
    }
}
@page "/background-work"
@using System.Threading
@using Microsoft.Extensions.Logging
@implements IDisposable
@inject ILogger<BackgroundWork> Logger

<button @onclick="LongRunningWork">Trigger long running work</button>
<button @onclick="Dispose">Trigger Disposal</button>

@code {
    private Resource resource = new();
    private CancellationTokenSource cts = new();

    protected async Task LongRunningWork()
    {
        Logger.LogInformation("Long running work started");

        await Task.Delay(5000, cts.Token);

        cts.Token.ThrowIfCancellationRequested();
        resource.BackgroundResourceMethod(Logger);
    }

    public void Dispose()
    {
        Logger.LogInformation("Executing Dispose");
        cts.Cancel();
        cts.Dispose();
        resource?.Dispose();
    }

    private class Resource : IDisposable
    {
        private bool disposed;

        public void BackgroundResourceMethod(ILogger<BackgroundWork> logger)
        {
            logger.LogInformation("BackgroundResourceMethod: Start method");

            if (disposed)
            {
                logger.LogInformation("BackgroundResourceMethod: Disposed");
                throw new ObjectDisposedException(nameof(Resource));
            }

            // Take action on the Resource

            logger.LogInformation("BackgroundResourceMethod: Action on Resource");
        }

        public void Dispose()
        {
            disposed = true;
        }
    }
}
@page "/background-work"
@using System.Threading
@using Microsoft.Extensions.Logging
@implements IDisposable
@inject ILogger<BackgroundWork> Logger

<button @onclick="LongRunningWork">Trigger long running work</button>
<button @onclick="Dispose">Trigger Disposal</button>

@code {
    private Resource resource = new Resource();
    private CancellationTokenSource cts = new CancellationTokenSource();

    protected async Task LongRunningWork()
    {
        Logger.LogInformation("Long running work started");

        await Task.Delay(5000, cts.Token);

        cts.Token.ThrowIfCancellationRequested();
        resource.BackgroundResourceMethod(Logger);
    }

    public void Dispose()
    {
        Logger.LogInformation("Executing Dispose");
        cts.Cancel();
        cts.Dispose();
        resource?.Dispose();
    }

    private class Resource : IDisposable
    {
        private bool disposed;

        public void BackgroundResourceMethod(ILogger<BackgroundWork> logger)
        {
            logger.LogInformation("BackgroundResourceMethod: Start method");

            if (disposed)
            {
                logger.LogInformation("BackgroundResourceMethod: Disposed");
                throw new ObjectDisposedException(nameof(Resource));
            }

            // Take action on the Resource

            logger.LogInformation("BackgroundResourceMethod: Action on Resource");
        }

        public void Dispose()
        {
            disposed = true;
        }
    }
}

Blazor Server peristiwa koneksi ulang

Peristiwa siklus hidup komponen yang tercakup dalam artikel ini beroperasi secara terpisah dari penanganan aktivitas koneksi ulang sisi server. SignalR Ketika koneksi ke klien hilang, hanya pembaruan UI yang terganggu. Pembaruan UI dilanjutkan saat koneksi dibuat ulang. Untuk informasi selengkapnya tentang peristiwa dan konfigurasi penanganan sirkuit, lihat panduan ASP.NET CoreBlazorSignalR.

Sumber Daya Tambahan:

Menangani pengecualian yang tertangkap di luar Razor siklus hidup komponen