Bagikan melalui


penyajian komponen ASP.NET Core Razor

Catatan

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

Peringatan

Versi ASP.NET Core ini tidak lagi didukung. Untuk informasi selengkapnya, lihat Kebijakan Dukungan .NET dan .NET Core. Untuk rilis saat ini, lihat versi .NET 9 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 9 dari artikel ini.

Artikel ini menjelaskan Razor penyajian komponen di aplikasi ASP.NET Core Blazor, termasuk kapan harus memanggil StateHasChanged untuk secara manual memicu komponen agar direnderkan.

Konvensi penyajian untuk ComponentBase

Komponen harus dirender saat pertama kali ditambahkan ke hierarki komponen oleh komponen induk. Ini adalah satu-satunya waktu komponen harus diperlihatkan. Komponen dapat dirender di lain waktu sesuai dengan logika dan konvensi mereka sendiri.

Razor komponen mewarisi dari ComponentBase kelas dasar, yang berisi logika untuk memanggil render ulang pada waktu berikut:

Komponen yang diwarisi dari ComponentBase melewatkan proses rerender karena pembaruan parameter jika salah satu kondisi berikut ini terpenuhi:

  • Semua parameter berasal dari sekumpulan jenis yang diketahui† atau jenis primitif apa pun yang belum berubah sejak kumpulan parameter sebelumnya ditetapkan.

    † Kerangka Blazor kerja menggunakan sekumpulan aturan bawaan dan pemeriksaan jenis parameter eksplisit untuk deteksi perubahan. Aturan dan jenis ini dapat berubah kapan saja. Untuk informasi selengkapnya, lihat ChangeDetection API di sumber referensi ASP.NET Core.

    Catatan

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

  • Penggantian metode komponen mengembalikan (implementasi default selalu mengembalikan ).

Mengontrol alur penyajian

Dalam kebanyakan kasus, konvensi ComponentBase menghasilkan subset komponen yang dirender ulang dengan benar setelah suatu peristiwa terjadi. Pengembang biasanya tidak diharuskan untuk menyediakan logika manual untuk memberi tahu kerangka kerja komponen mana yang akan dirender dan kapan harus merendernya. Efek keseluruhan dari konvensi kerangka kerja adalah bahwa komponen yang menerima suatu peristiwa mengulang render dirinya sendiri, yang secara rekursif memicu rendering ulang komponen keturunan yang nilai parameternya mungkin telah berubah.

Untuk informasi selengkapnya tentang implikasi performa konvensi kerangka kerja dan cara mengoptimalkan hierarki komponen aplikasi untuk penyajian, lihat praktik terbaik performa penyajian ASP.NET CoreBlazor.

Penyajian streaming

Gunakan penyajian streaming dengan penyajian sisi server statis (SSR statis) atau pra-penyajian untuk mengalirkan memperbarui konten pada aliran respons dan meningkatkan pengalaman pengguna untuk komponen yang melakukan tugas asinkron yang berjalan lama agar dapat sepenuhnya dirender.

Misalnya, pertimbangkan komponen yang membuat kueri database atau panggilan API web yang berjalan lama untuk merender data saat halaman dimuat. Biasanya, tugas asinkron yang dijalankan sebagai bagian dari penyajian komponen sisi server harus diselesaikan sebelum respons yang dirender dikirim, yang dapat menunda pemuatan halaman. Setiap keterlambatan yang signifikan dalam merender halaman membahayakan pengalaman pengguna. Untuk meningkatkan pengalaman pengguna, rendering streaming awalnya merender seluruh halaman dengan cepat dengan konten pengganti sementara operasi asinkron dijalankan. Setelah operasi selesai, konten yang diperbarui dikirim ke klien pada koneksi respons yang sama dan ditambal ke DOM.

Penyajian streaming mengharuskan server untuk menghindari buffer output. Data respons harus mengalir ke klien saat data dihasilkan. Untuk host yang memberlakukan buffering, penyajian streaming dapat menurun dengan lancar, dan halaman berhasil dimuat tanpa menggunakan penyajian streaming.

Untuk melakukan streaming pembaruan konten saat menggunakan penyajian sisi server statis (SSR statis) atau pra-penyajian, terapkan atribut [StreamRendering] di .NET 9 atau yang lebih baru (gunakan [StreamRendering(true)] di .NET 8) ke komponen. Penyajian streaming harus diaktifkan secara eksplisit karena pembaruan yang dialirkan dapat menyebabkan konten pada halaman bergeser. Komponen tanpa atribut secara otomatis mengadopsi penyajian streaming jika komponen induk menggunakan fitur tersebut. Teruskan false ke atribut dalam komponen anak untuk menonaktifkan fitur pada titik tersebut dan di bagian subtree komponen selanjutnya. Atribut ini fungsional saat diterapkan ke komponen yang disediakan oleh Razor perpustakaan kelas.

Contoh di bawah ini didasarkan pada komponen Weather dalam aplikasi yang dibuat menggunakan templat Blazor Web App proyek. Panggilan untuk Task.Delay mensimulasikan pengambilan data cuaca secara asinkron. Pada awalnya, komponen merender konten tempat penampung ("Loading...") tanpa menunggu penundaan asinkron selesai. Ketika penundaan asinkron selesai dan konten data cuaca dihasilkan, konten tersebut dialirkan ke dalam respons dan dimasukkan ke dalam tabel prakiraan cuaca.

Weather.razor:

@page "/weather"
@attribute [StreamRendering]

...

@if (forecasts == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <table class="table">
        ...
        <tbody>
            @foreach (var forecast in forecasts)
            {
                <tr>
                    <td>@forecast.Date.ToShortDateString()</td>
                    <td>@forecast.TemperatureC</td>
                    <td>@forecast.TemperatureF</td>
                    <td>@forecast.Summary</td>
                </tr>
            }
        </tbody>
    </table>
}

@code {
    ...

    private WeatherForecast[]? forecasts;

    protected override async Task OnInitializedAsync()
    {
        await Task.Delay(500);

        ...

        forecasts = ...
    }
}

Menghentikan penyegaran antarmuka (ShouldRender)

Setiap kali komponen dirender, ShouldRender dipanggil. Ambil alih ShouldRender untuk mengatur pemutakhiran UI. Jika implementasi mengembalikan true, UI di-refresh.

Bahkan jika ShouldRender ditimpa, komponen selalu dirender pada awalnya.

ControlRender.razor:

@page "/control-render"

<PageTitle>Control Render</PageTitle>

<h1>Control Render Example</h1>

<label>
    <input type="checkbox" @bind="shouldRender" />
    Should Render?
</label>

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

<p>
    <button @onclick="IncrementCount">Click me</button>
</p>

@code {
    private int currentCount = 0;
    private bool shouldRender = true;

    protected override bool ShouldRender() => shouldRender;

    private void IncrementCount() => currentCount++;
}
@page "/control-render"

<PageTitle>Control Render</PageTitle>

<h1>Control Render Example</h1>

<label>
    <input type="checkbox" @bind="shouldRender" />
    Should Render?
</label>

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

<p>
    <button @onclick="IncrementCount">Click me</button>
</p>

@code {
    private int currentCount = 0;
    private bool shouldRender = true;

    protected override bool ShouldRender() => shouldRender;

    private void IncrementCount() => currentCount++;
}
@page "/control-render"

<label>
    <input type="checkbox" @bind="shouldRender" />
    Should Render?
</label>

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

<p>
    <button @onclick="IncrementCount">Click me</button>
</p>

@code {
    private int currentCount = 0;
    private bool shouldRender = true;

    protected override bool ShouldRender()
    {
        return shouldRender;
    }

    private void IncrementCount()
    {
        currentCount++;
    }
}
@page "/control-render"

<label>
    <input type="checkbox" @bind="shouldRender" />
    Should Render?
</label>

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

<p>
    <button @onclick="IncrementCount">Click me</button>
</p>

@code {
    private int currentCount = 0;
    private bool shouldRender = true;

    protected override bool ShouldRender()
    {
        return shouldRender;
    }

    private void IncrementCount()
    {
        currentCount++;
    }
}
@page "/control-render"

<label>
    <input type="checkbox" @bind="shouldRender" />
    Should Render?
</label>

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

<p>
    <button @onclick="IncrementCount">Click me</button>
</p>

@code {
    private int currentCount = 0;
    private bool shouldRender = true;

    protected override bool ShouldRender()
    {
        return shouldRender;
    }

    private void IncrementCount()
    {
        currentCount++;
    }
}
@page "/control-render"

<label>
    <input type="checkbox" @bind="shouldRender" />
    Should Render?
</label>

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

<p>
    <button @onclick="IncrementCount">Click me</button>
</p>

@code {
    private int currentCount = 0;
    private bool shouldRender = true;

    protected override bool ShouldRender()
    {
        return shouldRender;
    }

    private void IncrementCount()
    {
        currentCount++;
    }
}

Untuk informasi selengkapnya tentang praktik terbaik performa yang berkaitan dengan ShouldRender, lihat praktik terbaik performa penyajian inti Blazor ASP.NET.

StateHasChanged

Memanggil StateHasChanged akan mengantrekan rerender untuk terjadi ketika utas utama aplikasi tidak sibuk.

Komponen diantrekan untuk rendering, dan tidak diantrekan lagi jika sudah ada rendering yang tertunda. Jika komponen memanggil StateHasChanged lima kali berturut-turut dalam perulangan, komponen hanya merender sekali. Perilaku ini dikodekan dalam ComponentBase, yang memeriksa terlebih dahulu apakah telah mengantre rerender sebelum mengantri yang tambahan.

Komponen dapat merender beberapa kali selama siklus yang sama, yang biasanya terjadi ketika komponen memiliki anak yang berinteraksi satu sama lain:

  • Komponen induk merender beberapa anak.
  • Komponen anak merender dan memicu pembaruan pada komponen induk.
  • Komponen induk dirender ulang dengan status baru.

Desain ini memungkinkan untuk StateHasChanged dipanggil jika perlu tanpa risiko memperkenalkan penyajian yang tidak perlu. Anda selalu dapat mengontrol perilaku ini dalam komponen individual dengan menerapkan IComponent secara langsung dan mengelola pengendaliannya secara manual ketika komponen dirender.

Pertimbangkan metode berikut IncrementCount yang menaikkan jumlah, memanggil StateHasChanged, dan menaikkan jumlah lagi:

private void IncrementCount()
{
    currentCount++;
    StateHasChanged();
    currentCount++;
}

Menelusuri kode di debugger, Anda mungkin berpikir bahwa pembaruan jumlah di UI untuk eksekusi pertama di currentCount++ terjadi segera setelah StateHasChanged dipanggil. Namun, UI tidak menunjukkan jumlah yang diperbarui pada saat itu karena pemrosesan sinkron yang berlangsung untuk eksekusi metode ini. Tidak ada kesempatan bagi perender untuk merender komponen sampai setelah penanganan aktivitas selesai. UI menampilkan peningkatan dalam kedua proses eksekusi currentCount++dalam satu render.

Jika Anda menunggu sesuatu antara baris currentCount++, panggilan yang ditunggu-tunggu memberi perender kesempatan melakukan rendering. Ini telah menyebabkan beberapa pengembang memanggil fungsi Delay dengan penundaan satu milidetik dalam komponen mereka untuk memungkinkan render terjadi, tetapi kami tidak merekomendasikan memperlambat aplikasi secara sembarangan untuk menjadwalkan render.

Pendekatan terbaik adalah menunggu Task.Yield, yang akan memaksa komponen untuk memproses kode secara asinkron dan merender selama batch saat ini, dengan render kedua dalam batch terpisah setelah tugas yang dihasilkan menjalankan kelanjutan proses tersebut.

Pertimbangkan metode yang direvisi IncrementCount berikut, yang memperbarui UI dua kali karena render yang diantrekan oleh StateHasChanged dilakukan ketika tugas dihasilkan dengan panggilan ke Task.Yield:

private async Task IncrementCount()
{
    currentCount++;
    StateHasChanged();
    await Task.Yield();
    currentCount++;
}

Berhati-hatilah untuk tidak memanggil StateHasChanged secara tidak perlu, yang merupakan kesalahan umum yang memberlakukan biaya penyajian yang tidak perlu. Kode seharusnya tidak perlu memanggil StateHasChanged saat:

  • Secara rutin menangani peristiwa, baik secara sinkron atau asinkron, karena ComponentBase memicu render untuk sebagian besar penanganan aktivitas rutin.
  • Menerapkan logika siklus hidup yang khas, seperti OnInitialized atau OnParametersSetAsync, baik secara sinkron atau asinkron, karena ComponentBase memicu render untuk peristiwa siklus hidup yang khas.

Namun, mungkin masuk akal untuk memanggil StateHasChanged dalam kasus yang dijelaskan di bagian berikut dari artikel ini:

Handler asinkron melibatkan beberapa tahap asinkron

Karena cara tugas didefinisikan dalam .NET, penerima Task hanya dapat mengamati penyelesaian akhirnya, bukan status asinkron menengah. Oleh karena itu, ComponentBase hanya dapat memicu penyajian kembali ketika Task pertama kali dikembalikan dan ketika akhirnya Task selesai. Kerangka kerja tidak dapat mengetahui untuk merender komponen pada titik-titik perantara lainnya, seperti ketika IAsyncEnumerable<T>mengembalikan data dalam serangkaian Task perantara. Jika Anda ingin merender ulang pada titik perantara, panggil StateHasChanged pada titik-titik tersebut.

Pertimbangkan komponen berikut CounterState1 , yang memperbarui hitungan empat kali setiap kali IncrementCount metode dijalankan:

  • Render otomatis terjadi setelah peningkatan pertama dan terakhir dari currentCount.
  • Render manual dijalankan oleh panggilan ke StateHasChanged ketika framework tidak otomatis melakukan render ulang di titik pemrosesan perantara di mana currentCount dinaikkan.

CounterState1.razor:

@page "/counter-state-1"

<PageTitle>Counter State 1</PageTitle>

<h1>Counter State Example 1</h1>

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

<p>
    <button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
</p>

@code {
    private int currentCount = 0;

    private async Task IncrementCount()
    {
        currentCount++;
        // Renders here automatically

        await Task.Delay(1000);
        currentCount++;
        StateHasChanged();

        await Task.Delay(1000);
        currentCount++;
        StateHasChanged();

        await Task.Delay(1000);
        currentCount++;
        // Renders here automatically
    }
}
@page "/counter-state-1"

<PageTitle>Counter State 1</PageTitle>

<h1>Counter State Example 1</h1>

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

<p>
    <button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
</p>

@code {
    private int currentCount = 0;

    private async Task IncrementCount()
    {
        currentCount++;
        // Renders here automatically

        await Task.Delay(1000);
        currentCount++;
        StateHasChanged();

        await Task.Delay(1000);
        currentCount++;
        StateHasChanged();

        await Task.Delay(1000);
        currentCount++;
        // Renders here automatically
    }
}
@page "/counter-state-1"

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

<p>
    <button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
</p>

@code {
    private int currentCount = 0;

    private async Task IncrementCount()
    {
        currentCount++;
        // Renders here automatically

        await Task.Delay(1000);
        currentCount++;
        StateHasChanged();

        await Task.Delay(1000);
        currentCount++;
        StateHasChanged();

        await Task.Delay(1000);
        currentCount++;
        // Renders here automatically
    }
}
@page "/counter-state-1"

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

<p>
    <button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
</p>

@code {
    private int currentCount = 0;

    private async Task IncrementCount()
    {
        currentCount++;
        // Renders here automatically

        await Task.Delay(1000);
        currentCount++;
        StateHasChanged();

        await Task.Delay(1000);
        currentCount++;
        StateHasChanged();

        await Task.Delay(1000);
        currentCount++;
        // Renders here automatically
    }
}
@page "/counter-state-1"

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

<p>
    <button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
</p>

@code {
    private int currentCount = 0;

    private async Task IncrementCount()
    {
        currentCount++;
        // Renders here automatically

        await Task.Delay(1000);
        currentCount++;
        StateHasChanged();

        await Task.Delay(1000);
        currentCount++;
        StateHasChanged();

        await Task.Delay(1000);
        currentCount++;
        // Renders here automatically
    }
}
@page "/counter-state-1"

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

<p>
    <button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
</p>

@code {
    private int currentCount = 0;

    private async Task IncrementCount()
    {
        currentCount++;
        // Renders here automatically

        await Task.Delay(1000);
        currentCount++;
        StateHasChanged();

        await Task.Delay(1000);
        currentCount++;
        StateHasChanged();

        await Task.Delay(1000);
        currentCount++;
        // Renders here automatically
    }
}

Menerima panggilan dari sesuatu yang berada di luar sistem pemrosesan Blazor dan penanganan peristiwa

ComponentBase hanya tahu tentang metode siklus hidupnya sendiri dan peristiwa yang dipicu oleh Blazor. ComponentBase tidak tahu tentang peristiwa lain yang mungkin terjadi dalam kode. Misalnya, setiap peristiwa C# yang dipicu oleh penyimpanan data kustom tidak diketahui oleh Blazor. Agar peristiwa tersebut memicu pengulangan untuk menampilkan nilai yang diperbarui di UI, panggil StateHasChanged.

Pertimbangkan komponen berikut CounterState2 yang menggunakan System.Timers.Timer untuk memperbarui hitungan secara berkala dan panggilan StateHasChanged untuk memperbarui UI:

  • OnTimerCallback berjalan di luar alur penyajian terkelola Blazoratau pemberitahuan peristiwa. Oleh karena itu, OnTimerCallback harus memanggil StateHasChanged karena Blazor tidak mengetahui perubahan currentCount pada panggilan balik.
  • Komponen mengimplementasikan IDisposable, di mana Timer dibuang ketika metode Dispose dipanggil oleh kerangka kerja. Untuk informasi selengkapnya, lihat RazorASP.NET Core .

Karena panggilan balik dipanggil di luar konteks sinkronisasi Blazor, komponen harus membungkus logika OnTimerCallback ke dalam ComponentBase.InvokeAsync untuk memindahkannya ke konteks sinkronisasi renderer. Ini setara dengan mengalihkan ke utas UI dalam kerangka kerja UI lainnya. StateHasChanged hanya dapat dipanggil dari konteks sinkronisasi perender dan melemparkan pengecualian jika tidak:

System.InvalidOperationException: 'Thread saat ini tidak terkait dengan Dispatcher. Gunakan InvokeAsync() untuk mengalihkan eksekusi ke Dispatcher saat memicu penyajian atau status komponen.'

CounterState2.razor:

@page "/counter-state-2"
@using System.Timers
@implements IDisposable

<PageTitle>Counter State 2</PageTitle>

<h1>Counter State Example 2</h1>

<p>
    This counter demonstrates <code>Timer</code> disposal.
</p>

<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();
}
@page "/counter-state-2"
@using System.Timers
@implements IDisposable

<PageTitle>Counter State 2</PageTitle>

<h1>Counter State Example 2</h1>

<p>
    This counter demonstrates <code>Timer</code> disposal.
</p>

<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();
}
@page "/counter-state-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 = new(1000);

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

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

    public void Dispose() => timer.Dispose();
}
@page "/counter-state-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 = new(1000);

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

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

    public void Dispose() => timer.Dispose();
}
@page "/counter-state-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 = new(1000);

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

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

    public void Dispose() => timer.Dispose();
}
@page "/counter-state-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 = 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();
}

Untuk merender komponen di luar subtree yang dirender oleh peristiwa tertentu

UI mungkin melibatkan:

  1. Mengirimkan event ke satu komponen.
  2. Mengubah beberapa keadaan.
  3. Merender ulang komponen yang sama sekali berbeda yang bukan merupakan turunan dari komponen yang menerima event.

Salah satu cara untuk menangani skenario ini adalah dengan menyediakan kelas manajemen status, seringkali sebagai layanan injeksi dependensi (DI), disuntikkan ke beberapa komponen. Ketika satu komponen memanggil metode pada manajer status, manajer status menaikkan peristiwa C# yang kemudian diterima oleh komponen independen.

Untuk pendekatan mengelola keadaan, lihat sumber berikut:

Untuk pendekatan manajer status, peristiwa C# berada di luar alur penyajian Blazor . Panggil StateHasChanged pada komponen lain yang ingin Anda render ulang sebagai respons terhadap peristiwa pengelola status.

Pendekatan manajer status mirip dengan kasus sebelumnya di bagian System.Timers.Timer dengan . Karena tumpukan panggilan eksekusi biasanya tetap pada konteks sinkronisasi perender, panggilan InvokeAsync biasanya tidak diperlukan. Memanggil InvokeAsync hanya diperlukan jika logika lolos dari konteks sinkronisasi, seperti memanggil ContinueWith pada Task atau menunggu Task dengan ConfigureAwait(false). Untuk informasi selengkapnya, lihat bagian Menerima panggilan dari sesuatu yang eksternal ke sistem Blazor rendering dan penanganan peristiwa.

Indikator kemajuan pemuatan WebAssembly untuk Blazor Web Apps

Indikator kemajuan pemuatan tidak ada di aplikasi yang dibuat dari Blazor Web App templat proyek. Fitur indikator kemajuan pemuatan baru direncanakan untuk rilis .NET di masa mendatang. Sementara itu, aplikasi dapat mengadopsi kode kustom untuk membuat indikator kemajuan pemuatan. Untuk informasi selengkapnya, lihat ASP.NET Core Blazor startup.