Bagikan melalui


ASP.NET Nilai dan parameter berskala Core Blazor

Catatan

Ini bukan versi terbaru dari artikel ini. Untuk rilis saat ini, lihat versi .NET 8 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 8 dari artikel ini.

Penting

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

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

Artikel ini menjelaskan cara mengalirkan data dari komponen leluhur Razor ke komponen keturunan.

Nilai dan parameter berskala menyediakan cara mudah untuk mengalirkan data ke hierarki komponen dari komponen leluhur ke sejumlah komponen keturunan. Tidak seperti parameter Komponen, nilai dan parameter berskala tidak memerlukan penetapan atribut untuk setiap komponen turunan tempat data digunakan. Nilai dan parameter berskala juga memungkinkan komponen untuk berkoordinasi satu sama lain di seluruh hierarki komponen.

Catatan

Contoh kode dalam artikel ini mengadopsi jenis referensi nullable (NRTs) dan .NET compiler null-state static analysis, yang didukung di ASP.NET Core di .NET 6 atau yang lebih baru. Saat menargetkan ASP.NET Core 5.0 atau yang lebih lama, hapus penunjukan jenis null (?) dari CascadingType?jenis , , @ActiveTab?, ITab?RenderFragment?, TabSet?, dan string? dalam contoh artikel.

Nilai berskala tingkat akar

Nilai kaskading tingkat akar dapat didaftarkan untuk seluruh hierarki komponen. Nilai dan langganan berskala bernama untuk pemberitahuan pembaruan didukung.

Kelas berikut digunakan dalam contoh bagian ini.

Dalek.cs:

// "Dalek" ©Terry Nation https://www.imdb.com/name/nm0622334/
// "Doctor Who" ©BBC https://www.bbc.co.uk/programmes/b006q2x0

namespace BlazorSample;

public class Dalek
{
    public int Units { get; set; }
}

Pendaftaran berikut dibuat dalam file aplikasi Program dengan AddCascadingValue:

  • Dalek dengan nilai properti untuk Units didaftarkan sebagai nilai kaskading tetap.
  • Pendaftaran kedua Dalek dengan nilai properti yang berbeda untuk Units diberi nama "AlphaGroup".
builder.Services.AddCascadingValue(sp => new Dalek { Units = 123 });
builder.Services.AddCascadingValue("AlphaGroup", sp => new Dalek { Units = 456 });

Komponen berikut Daleks menampilkan nilai berskala.

Daleks.razor:

@page "/daleks"

<PageTitle>Daleks</PageTitle>

<h1>Root-level Cascading Value Example</h1>

<ul>
    <li>Dalek Units: @Dalek?.Units</li>
    <li>Alpha Group Dalek Units: @AlphaGroupDalek?.Units</li>
</ul>

<p>
    Dalek© <a href="https://www.imdb.com/name/nm0622334/">Terry Nation</a><br>
    Doctor Who© <a href="https://www.bbc.co.uk/programmes/b006q2x0">BBC</a>
</p>

@code {
    [CascadingParameter]
    public Dalek? Dalek { get; set; }

    [CascadingParameter(Name = "AlphaGroup")]
    public Dalek? AlphaGroupDalek { get; set; }
}

Dalam contoh berikut, Dalek terdaftar sebagai nilai berskala menggunakan CascadingValueSource<T>, di mana <T> adalah jenisnya. Bendera isFixed menunjukkan apakah nilai diperbaiki. Jika false, semua penerima berlangganan pemberitahuan pembaruan, yang dikeluarkan dengan memanggil NotifyChangedAsync. Langganan membuat overhead dan mengurangi performa, jadi atur isFixed ke true jika nilai tidak berubah.

builder.Services.AddCascadingValue(sp =>
{
    var dalek = new Dalek { Units = 789 };
    var source = new CascadingValueSource<Dalek>(dalek, isFixed: false);
    return source;
});

Peringatan

Mendaftarkan jenis komponen sebagai nilai kaskading tingkat akar tidak mendaftarkan layanan tambahan untuk jenis atau mengizinkan aktivasi layanan dalam komponen.

Perlakukan layanan yang diperlukan secara terpisah dari nilai kaskading, mendaftarkannya secara terpisah dari jenis berskala.

Hindari menggunakan AddCascadingValue untuk mendaftarkan jenis komponen sebagai nilai berskala. Sebagai gantinya <Router>...</Router> , bungkus dalam Routes komponen (Components/Routes.razor) dengan komponen dan adopsi penyajian sisi server interaktif global (SSR interaktif). Misalnya, lihat bagian CascadingValue komponen .

CascadingValue komponen

Komponen leluhur menyediakan nilai berskala menggunakan Blazor komponen kerangka kerja CascadingValue , yang membungkus subtree hierarki komponen dan memasok nilai tunggal ke semua komponen dalam subtreenya.

Contoh berikut menunjukkan alur informasi tema ke hierarki komponen untuk menyediakan kelas gaya CSS ke tombol dalam komponen turunan.

Kelas C# berikut ThemeInfo menentukan informasi tema.

Catatan

Untuk contoh di bagian ini, namespace aplikasi adalah BlazorSample. Saat bereksperimen dengan kode di aplikasi sampel Anda sendiri, ubah namespace aplikasi ke namespace aplikasi sampel Anda.

ThemeInfo.cs:

namespace BlazorSample;

public class ThemeInfo
{
    public string? ButtonClass { get; set; }
}
namespace BlazorSample.UIThemeClasses;

public class ThemeInfo
{
    public string? ButtonClass { get; set; }
}
namespace BlazorSample.UIThemeClasses;

public class ThemeInfo
{
    public string? ButtonClass { get; set; }
}
namespace BlazorSample.UIThemeClasses
{
    public class ThemeInfo
    {
        public string ButtonClass { get; set; }
    }
}
namespace BlazorSample.UIThemeClasses
{
    public class ThemeInfo
    {
        public string ButtonClass { get; set; }
    }
}

Komponen tata letak berikut menentukan informasi tema (ThemeInfo) sebagai nilai bertingkat untuk semua komponen yang membentuk isi Body tata letak properti. ButtonClass diberi nilai btn-success, yang merupakan gaya tombol Bootstrap. Setiap komponen turunan dalam hierarki komponen dapat menggunakan ButtonClass properti melalui nilai berskala ThemeInfo .

MainLayout.razor:

@inherits LayoutComponentBase

<div class="page">
    <div class="sidebar">
        <NavMenu />
    </div>

    <main>
        <div class="top-row px-4">
            <a href="https://learn.microsoft.com/aspnet/core/" target="_blank">About</a>
        </div>

        <CascadingValue Value="@theme">
            <article class="content px-4">
                @Body
            </article>
        </CascadingValue>
    </main>
</div>

<div id="blazor-error-ui" data-nosnippet>
    An unhandled error has occurred.
    <a href="" class="reload">Reload</a>
    <a class="dismiss">🗙</a>
</div>

@code {
    private ThemeInfo theme = new() { ButtonClass = "btn-success" };
}
@inherits LayoutComponentBase
@using BlazorSample.UIThemeClasses

<div class="page">
    <div class="sidebar">
        <NavMenu />
    </div>

    <main>
        <div class="top-row px-4">
            <a href="https://docs.microsoft.com/aspnet/" target="_blank">About</a>
        </div>

        <CascadingValue Value="@theme">
            <article class="content px-4">
                @Body
            </article>
        </CascadingValue>
    </main>
</div>

@code {
    private ThemeInfo theme = new() { ButtonClass = "btn-success" };
}
@inherits LayoutComponentBase
@using BlazorSample.UIThemeClasses

<div class="page">
    <div class="sidebar">
        <NavMenu />
    </div>

    <main>
        <CascadingValue Value="@theme">
            <div class="content px-4">
                @Body
            </div>
        </CascadingValue>
    </main>
</div>

@code {
    private ThemeInfo theme = new() { ButtonClass = "btn-success" };
}
@inherits LayoutComponentBase
@using BlazorSample.UIThemeClasses

<div class="page">
    <div class="sidebar">
        <NavMenu />
    </div>

    <div class="main">
        <CascadingValue Value="@theme">
            <div class="content px-4">
                @Body
            </div>
        </CascadingValue>
    </div>
</div>

@code {
    private ThemeInfo theme = new() { ButtonClass = "btn-success" };
}
@inherits LayoutComponentBase
@using BlazorSample.UIThemeClasses

<div class="sidebar">
    <NavMenu />
</div>

<div class="main">
    <CascadingValue Value="theme">
        <div class="content px-4">
            @Body
        </div>
    </CascadingValue>
</div>

@code {
    private ThemeInfo theme = new ThemeInfo { ButtonClass = "btn-success" };
}

Blazor Web Apps menyediakan pendekatan alternatif untuk nilai bertingkat yang berlaku lebih luas ke aplikasi daripada melengkungkannya melalui satu file tata letak:

  • Bungkus markup Routes komponen dalam CascadingValue komponen untuk menentukan data sebagai nilai berskala untuk semua komponen aplikasi.

    Contoh data kaskade ThemeInfo berikut dari Routes komponen.

    Routes.razor:

    <CascadingValue Value="theme">
        <Router ...>
            ...
        </Router>
    </CascadingValue>
    
    @code {
        private ThemeInfo theme = new() { ButtonClass = "btn-success" };
    }
    

    Catatan

    Membungkus Routes instans komponen dalam App komponen (Components/App.razor) dengan CascadingValue komponen tidak didukung.

  • Tentukan nilai berskala tingkat akar sebagai layanan dengan memanggil AddCascadingValue metode ekstensi pada penyusun kumpulan layanan.

    Contoh data kaskade ThemeInfo berikut dari Program file.

    Program.cs

    builder.Services.AddCascadingValue(sp => 
        new ThemeInfo() { ButtonClass = "btn-primary" });
    

Untuk informasi selengkapnya, lihat bagian berikut dari artikel ini:

atribut [CascadingParameter]

Untuk menggunakan nilai kaskade, komponen descendent mendeklarasikan parameter kaskade menggunakan [CascadingParameter] atribut . Nilai berskala terikat ke parameter berskala berdasarkan jenis. Kaskade beberapa nilai dengan jenis yang sama tercakup di bagian Beberapa nilai Berjendela nanti di artikel ini.

Komponen berikut mengikat ThemeInfo nilai berskala ke parameter kaskading, secara opsional menggunakan nama yang sama dari ThemeInfo. Parameter digunakan untuk mengatur kelas CSS untuk tombol Increment Counter (Themed) .

ThemedCounter.razor:

@page "/themed-counter"

<PageTitle>Themed Counter</PageTitle>

<h1>Themed Counter Example</h1>

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

<p>
    <button @onclick="IncrementCount">
        Increment Counter (Unthemed)
    </button>
</p>

<p>
    <button 
        class="btn @(ThemeInfo is not null ? ThemeInfo.ButtonClass : string.Empty)" 
        @onclick="IncrementCount">
        Increment Counter (Themed)
    </button>
</p>

@code {
    private int currentCount = 0;

    [CascadingParameter]
    protected ThemeInfo? ThemeInfo { get; set; }

    private void IncrementCount()
    {
        currentCount++;
    }
}
@page "/themed-counter"
@using BlazorSample.UIThemeClasses

<h1>Themed Counter</h1>

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

<p>
    <button @onclick="IncrementCount">
        Increment Counter (Unthemed)
    </button>
</p>

<p>
    <button 
        class="btn @(ThemeInfo is not null ? ThemeInfo.ButtonClass : string.Empty)" 
        @onclick="IncrementCount">
        Increment Counter (Themed)
    </button>
</p>

@code {
    private int currentCount = 0;

    [CascadingParameter]
    protected ThemeInfo? ThemeInfo { get; set; }

    private void IncrementCount()
    {
        currentCount++;
    }
}
@page "/themed-counter"
@using BlazorSample.UIThemeClasses

<h1>Themed Counter</h1>

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

<p>
    <button @onclick="IncrementCount">
        Increment Counter (Unthemed)
    </button>
</p>

<p>
    <button 
        class="btn @(ThemeInfo is not null ? ThemeInfo.ButtonClass : string.Empty)" 
        @onclick="IncrementCount">
        Increment Counter (Themed)
    </button>
</p>

@code {
    private int currentCount = 0;

    [CascadingParameter]
    protected ThemeInfo? ThemeInfo { get; set; }

    private void IncrementCount()
    {
        currentCount++;
    }
}
@page "/themed-counter"
@using BlazorSample.UIThemeClasses

<h1>Themed Counter</h1>

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

<p>
    <button @onclick="IncrementCount">
        Increment Counter (Unthemed)
    </button>
</p>

<p>
    <button class="btn @ThemeInfo.ButtonClass" @onclick="IncrementCount">
        Increment Counter (Themed)
    </button>
</p>

@code {
    private int currentCount = 0;

    [CascadingParameter]
    protected ThemeInfo ThemeInfo { get; set; }

    private void IncrementCount()
    {
        currentCount++;
    }
}
@page "/themed-counter"
@using BlazorSample.UIThemeClasses

<h1>Themed Counter</h1>

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

<p>
    <button @onclick="IncrementCount">
        Increment Counter (Unthemed)
    </button>
</p>

<p>
    <button class="btn @ThemeInfo.ButtonClass" @onclick="IncrementCount">
        Increment Counter (Themed)
    </button>
</p>

@code {
    private int currentCount = 0;

    [CascadingParameter]
    protected ThemeInfo ThemeInfo { get; set; }

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

Mirip dengan parameter komponen reguler, komponen yang menerima parameter kaskading dirender saat nilai kaskading diubah. Misalnya, mengonfigurasi instans ThemedCounter tema yang berbeda menyebabkan komponen dari bagian CascadingValue komponen dirender ulang.

MainLayout.razor:

<main>
    <div class="top-row px-4">
        <a href="https://docs.microsoft.com/aspnet/" target="_blank">About</a>
    </div>

    <CascadingValue Value="theme">
        <article class="content px-4">
            @Body
        </article>
    </CascadingValue>
    <button @onclick="ChangeToDarkTheme">Dark mode</button>
</main>

@code {
    private ThemeInfo theme = new() { ButtonClass = "btn-success" };

    private void ChangeToDarkTheme()
    {
        theme = new() { ButtonClass = "btn-secondary" };
    }
}

CascadingValue<TValue>.IsFixed dapat digunakan untuk menunjukkan bahwa parameter berskala tidak berubah setelah inisialisasi.

Nilai/parameter berskala dan batas mode render

Parameter berskala tidak meneruskan data di seluruh batas mode render:

  • Sesi interaktif berjalan dalam konteks yang berbeda dari halaman yang menggunakan penyajian sisi server statis (SSR statis). Tidak ada persyaratan bahwa server yang memproduksi halaman bahkan merupakan komputer yang sama yang menghosting beberapa sesi Server Interaktif nanti, termasuk untuk komponen WebAssembly di mana server adalah komputer yang berbeda dengan klien. Manfaat penyajian sisi server statis (SSR statis) adalah untuk mendapatkan performa penuh penyajian HTML stateless murni.

  • Status yang melintasi batas antara penyajian statis dan interaktif harus dapat diserialisasikan. Komponen adalah objek arbitrer yang mereferensikan rantai besar objek lain, termasuk perender, kontainer DI, dan setiap instans layanan DI. Anda harus secara eksplisit menyebabkan status diserialisasikan dari SSR statis agar tersedia dalam komponen yang dirender secara interaktif berikutnya. Dua pendekatan diadopsi:

    • Blazor Melalui kerangka kerja, parameter yang diteruskan di seluruh SSR statis ke batas penyajian interaktif diserialisasikan secara otomatis jika JSon-serializable, atau kesalahan dilemparkan.
    • Status yang disimpan dalam diserialisasikan PersistentComponentState dan dipulihkan secara otomatis jika JSdapat diserialisasiKAN, atau kesalahan dilemparkan.

Parameter berjenjang tidak JSdapat diserialisasikan ON karena pola penggunaan umum untuk parameter berjenjang agak seperti layanan DI. Seringkali ada varian parameter kaskading khusus platform, sehingga tidak akan membantu pengembang jika kerangka kerja menghentikan pengembang agar tidak memiliki versi khusus interaktif server atau versi khusus WebAssembly. Selain itu, banyak nilai parameter berjenjang secara umum tidak dapat diserialisasikan, sehingga tidak praktis untuk memperbarui aplikasi yang ada jika Anda harus berhenti menggunakan semua nilai parameter berjenjang yang tidak dapat diserialisasi.

Rekomendasi:

  • Jika Anda perlu membuat status tersedia untuk semua komponen interaktif sebagai parameter berskala, sebaiknya gunakan nilai kaskading tingkat akar. Pola pabrik tersedia, dan aplikasi dapat memancarkan nilai yang diperbarui setelah pengaktifan aplikasi. Nilai kaskading tingkat akar tersedia untuk semua komponen, termasuk komponen interaktif, karena diproses sebagai layanan DI.

  • Untuk penulis pustaka komponen, Anda dapat membuat metode ekstensi untuk konsumen pustaka yang mirip dengan yang berikut ini:

    builder.Services.AddLibraryCascadingParameters();
    

    Instruksikan pengembang untuk memanggil metode ekstensi Anda. Ini adalah alternatif yang baik untuk menginstruksikan mereka untuk menambahkan <RootComponent> komponen dalam komponennya MainLayout .

Kaskade beberapa nilai

Untuk menyimpan beberapa nilai dengan jenis yang sama dalam subtree yang sama, berikan string unik Name untuk setiap CascadingValue komponen dan atribut yang [CascadingParameter] sesuai.

Dalam contoh berikut, dua CascadingValue komponen kaskade instans yang berbeda dari CascadingType:

<CascadingValue Value="parentCascadeParameter1" Name="CascadeParam1">
    <CascadingValue Value="ParentCascadeParameter2" Name="CascadeParam2">
        ...
    </CascadingValue>
</CascadingValue>

@code {
    private CascadingType? parentCascadeParameter1;

    [Parameter]
    public CascadingType? ParentCascadeParameter2 { get; set; }
}

Dalam komponen turunan, parameter berkaskade menerima nilai kaskadenya dari komponen leluhur dengan Name:

@code {
    [CascadingParameter(Name = "CascadeParam1")]
    protected CascadingType? ChildCascadeParameter1 { get; set; }

    [CascadingParameter(Name = "CascadeParam2")]
    protected CascadingType? ChildCascadeParameter2 { get; set; }
}

Meneruskan data di seluruh hierarki komponen

Parameter berskala juga memungkinkan komponen untuk meneruskan data di seluruh hierarki komponen. Pertimbangkan contoh set tab UI berikut, di mana komponen kumpulan tab mempertahankan serangkaian tab individual.

Catatan

Untuk contoh di bagian ini, namespace aplikasi adalah BlazorSample. Saat bereksperimen dengan kode di aplikasi sampel Anda sendiri, ubah namespace ke namespace aplikasi sampel Anda.

Buat ITab antarmuka yang diterapkan tab dalam folder bernama UIInterfaces.

UIInterfaces/ITab.cs:

using Microsoft.AspNetCore.Components;

namespace BlazorSample.UIInterfaces;

public interface ITab
{
    RenderFragment ChildContent { get; }
}

Catatan

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

Komponen berikut TabSet mempertahankan sekumpulan tab. Komponen kumpulan Tab tab, yang dibuat nanti di bagian ini, berikan item daftar (<li>...</li>) untuk daftar (<ul>...</ul>).

Komponen anak Tab tidak secara eksplisit diteruskan sebagai parameter ke TabSet. Sebaliknya, komponen anak Tab adalah bagian dari konten anak dari TabSet. Namun, TabSet masih memerlukan referensi setiap Tab komponen sehingga dapat merender header dan tab aktif. Untuk mengaktifkan koordinasi ini tanpa memerlukan kode tambahan, TabSet komponen dapat menyediakan dirinya sebagai nilai bertingkat yang kemudian diambil oleh komponen keturunan Tab .

TabSet.razor:

@using BlazorSample.UIInterfaces

<!-- Display the tab headers -->

<CascadingValue Value="this">
    <ul class="nav nav-tabs">
        @ChildContent
    </ul>
</CascadingValue>

<!-- Display body for only the active tab -->

<div class="nav-tabs-body p-4">
    @ActiveTab?.ChildContent
</div>

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

    public ITab? ActiveTab { get; private set; }

    public void AddTab(ITab tab)
    {
        if (ActiveTab is null)
        {
            SetActiveTab(tab);
        }
    }

    public void SetActiveTab(ITab tab)
    {
        if (ActiveTab != tab)
        {
            ActiveTab = tab;
            StateHasChanged();
        }
    }
}

Tab Komponen turunan menangkap yang berisi TabSet sebagai parameter berskala. Komponen Tab menambahkan diri mereka ke TabSet dan berkoordinasi untuk mengatur tab aktif.

Tab.razor:

@using BlazorSample.UIInterfaces
@implements ITab

<li>
    <a @onclick="ActivateTab" class="nav-link @TitleCssClass" role="button">
        @Title
    </a>
</li>

@code {
    [CascadingParameter]
    public TabSet? ContainerTabSet { get; set; }

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

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

    private string? TitleCssClass => 
        ContainerTabSet?.ActiveTab == this ? "active" : null;

    protected override void OnInitialized()
    {
        ContainerTabSet?.AddTab(this);
    }

    private void ActivateTab()
    {
        ContainerTabSet?.SetActiveTab(this);
    }
}

Komponen berikut ExampleTabSet menggunakan TabSet komponen , yang berisi tiga Tab komponen.

ExampleTabSet.razor:

@page "/example-tab-set"

<TabSet>
    <Tab Title="First tab">
        <h4>Greetings from the first tab!</h4>

        <label>
            <input type="checkbox" @bind="showThirdTab" />
            Toggle third tab
        </label>
    </Tab>

    <Tab Title="Second tab">
        <h4>Hello from the second tab!</h4>
    </Tab>

    @if (showThirdTab)
    {
        <Tab Title="Third tab">
            <h4>Welcome to the disappearing third tab!</h4>
            <p>Toggle this tab from the first tab.</p>
        </Tab>
    }
</TabSet>

@code {
    private bool showThirdTab;
}

Sumber Daya Tambahan: