ASP.NET Core Razor コンポーネント

Blazor アプリは、'' Razor コンポーネント'' を使用してビルドされます。 コンポーネントは、動的な動作を有効にするための処理ロジックを使用するユーザー インターフェイス (UI) の自己完結型の部分です。 コンポーネントは、入れ子にしたり、再利用したり、プロジェクト間で共有したり、MVC および Razor ページ アプリで使用したりすることができます。

コンポーネント クラス

コンポーネントは、.razor ファイル拡張子の Razor コンポーネント ファイルで C# と HTML マークアップの組み合わせを使用して実装されます。

Razor の構文

コンポーネントでは Razor 構文を使用します。 コンポーネント、''ディレクティブ'' および ''ディレクティブ属性'' では、2 つの Razor 機能が広く使用されています。 これらは、Razor マークアップに表示される @ というプレフィックスが付いた予約キーワードです。

  • ディレクティブ: コンポーネント マークアップの解析方法または関数を変更します。 たとえば、@page ディレクティブでは、ルート テンプレートでルーティング可能なコンポーネントを指定します。このディレクティブには、ブラウザーの特定の URL でのユーザーの要求により直接到達できます。
  • ディレクティブ属性: コンポーネント要素の解析方法または関数を変更します。 たとえば、<input> 要素の @bind ディレクティブ属性では、データをその要素の値にバインドします。

コンポーネントで使用されるディレクティブとディレクティブ属性については、この記事および Blazor ドキュメント セットの他の記事で詳しく説明されています。 Razor 構文に関する一般的な情報については、「ASP.NET Core の Razor 構文リファレンス」を参照してください。

名前

コンポーネントの名前は大文字で始める必要があります。

  • ProductDetail.razor は有効です。
  • productDetail.razor が無効です。

Blazor ドキュメント全体で使用される共通の Blazor の名前付け規則は次のとおりです。

  • コンポーネント ファイルのパスでは、パスカル ケース† が使用され、コンポーネントのコード例が示される前に表示されます。 パスは一般的なフォルダーの場所を示します。 たとえば、Pages/ProductDetail.razor は、ProductDetail コンポーネントのファイル名が ProductDetail.razor で、そのコンポーネントがアプリの Pages フォルダーに存在することを示します。
  • ルーティング可能なコンポーネントのコンポーネント ファイル パスは、コンポーネントのルート テンプレート内の単語間のスペースに表示されるハイフン付きの URL に一致します。 たとえば、ルート テンプレートが /product-detail (@page "/product-detail") の ProductDetail コンポーネントは、ブラウザーの相対 URL /product-detail で要求されます。

†パスカル ケース (大文字のキャメル ケース) は、スペースと句読点を使用せず、大文字の各単語の最初の文字 (最初の単語を含む) を使用する名前付け規則です。

ルーティング

Blazor でのルーティングは、@page ディレクティブを使用するアプリ内のアクセス可能な各コンポーネントへのルート テンプレートを提供することで実現します。 @page ディレクティブを含む Razor ファイルがコンパイルされると、生成されたクラスに、ルート テンプレートを指定する RouteAttribute が指定されます。 実行時に、ルーターによって RouteAttribute を持つコンポーネント クラスが検索され、要求された URL に一致するルート テンプレートを使用するコンポーネントがレンダリングされます。

次の HelloWorld コンポーネントでは、/hello-world のルート テンプレートを使用します。 コンポーネントのレンダリングされた Web ページには、相対 URL /hello-world で到達できます。 既定のプロトコル、ホスト、およびポートを使用して Blazor アプリをローカルで実行する場合、HelloWorld コンポーネントはブラウザーの https://localhost:5001/hello-world で要求されます。 Web ページを生成するコンポーネントは通常、Pages フォルダーに存在しますが、入れ子になったフォルダー内などのコンポーネントを保持するために任意のフォルダーを使用できます。

Pages/HelloWorld.razor:

@page "/hello-world"

<h1>Hello World!</h1>
@page "/hello-world"

<h1>Hello World!</h1>

コンポーネントをアプリの UI ナビゲーションに追加するかどうかに関係なく、前のコンポーネントはブラウザーの /hello-world で読み込まれます。 必要に応じて、コンポーネントを NavMenu コンポーネントに追加し、そのコンポーネントへのリンクがアプリの UI ベースのナビゲーションに表示されるようにすることができます。

前の HelloWorld コンポーネントでは、次の NavLink コンポーネントを NavMenu コンポーネントに追加します。 順不同のリスト タグ (<ul>...</ul>) の間に、新しいリスト項目 (<li>...</li>) の NavLink コンポーネントを追加します。

Shared/NavMenu.razor:

<li class="nav-item px-3">
    <NavLink class="nav-link" href="hello-world">
        <span class="oi oi-list-rich" aria-hidden="true"></span> Hello World!
    </NavLink>
</li>

NavLink および NavMenu コンポーネントの説明などの詳細については、「ASP.NET Core Blazor のルーティング」を参照してください。

マークアップ

コンポーネントの UI は、Razor マークアップ、C#、HTML で構成される Razor 構文を使用して定義されます。 アプリがコンパイルされると、HTML マークアップと C# のレンダリング ロジックはコンポーネント クラスに変換されます。 生成されたクラスの名前は、ファイルの名前と一致します。

コンポーネント クラスのメンバーは、1 つまたは複数の @code ブロック内で定義されます。 @code ブロックでは、コンポーネントの状態が指定され、C# で処理されます。

  • プロパティとフィールドの初期化子。
  • 親コンポーネントとルート パラメーターによって渡される引数からのパラメーター値。
  • ユーザー イベント処理、ライフサイクル イベント、およびカスタム コンポーネント ロジックのメソッド。

コンポーネント メンバーは、@ 記号で始まる C# 式を使ったレンダリング ロジックで使用されます。 たとえば、フィールド名の前に @ を付けることによって、C# フィールドがレンダリングされます。 次の Markup コンポーネントでは、以下を評価およびレンダリングします。

  • 見出し要素の CSS プロパティ値 headingFontStylefont-style
  • 見出し要素のコンテンツの headingText

Pages/Markup.razor:

@page "/markup"

<h1 style="font-style:@headingFontStyle">@headingText</h1>

@code {
    private string headingFontStyle = "italic";
    private string headingText = "Put on your new Blazor!";
}
@page "/markup"

<h1 style="font-style:@headingFontStyle">@headingText</h1>

@code {
    private string headingFontStyle = "italic";
    private string headingText = "Put on your new Blazor!";
}

注意

Blazor ドキュメント全体の例では、プライベート メンバーの private アクセス修飾子を指定します。 プライベート メンバーは、コンポーネントのクラスにスコープ指定されます。 しかし、C# では、アクセス修飾子が存在しない場合、private アクセス修飾子が想定されるため、独自のコードでメンバーを "private" として明示的にマークすることは省略可能です。 アクセス修飾子の詳細については、「アクセス修飾子 (C# プログラミング ガイド)」を参照してください。

Blazor フレームワークでは、コンポーネントを内部的に レンダリング ツリーとして処理します。これは、コンポーネントの ドキュメント オブジェクト モデル (DOM)カスケード スタイル シートオブジェクト モデル (CSSOM) の組み合わせです。 コンポーネントが最初にレンダリングされた後に、そのコンポーネントのレンダリング ツリーがイベントに応じて再生成されます。 Blazor によって新旧のレンダリング ツリーが比較され、表示目的でブラウザーの DOM に変更がすべて適用されます。 詳細については、「ASP.NET Core Blazor コンポーネントのレンダリング」を参照してください。

コンポーネントは通常の C# クラスであり、プロジェクト内の任意の場所に配置できます。 Web ページを生成するコンポーネントは、通常、Pages フォルダーに存在します。 ページ以外のコンポーネントは、多くの場合、Shared フォルダー、またはプロジェクトに追加されたカスタム フォルダーに配置されます。

入れ子になったコンポーネント

コンポーネントには、HTML 構文を使用して宣言することで、他のコンポーネントを含めることができます。 コンポーネントを使うためのマークアップは、そのコンポーネントの種類をタグ名とする HTML タグのようになります。

他のコンポーネントで見出しを表示するために使用できる次の Heading コンポーネントについて考えてみます。

Shared/Heading.razor:

<h1 style="font-style:@headingFontStyle">Heading Example</h1>

@code {
    private string headingFontStyle = "italic";
}
<h1 style="font-style:@headingFontStyle">Heading Example</h1>

@code {
    private string headingFontStyle = "italic";
}

HeadingExample コンポーネント内の次のマークアップでは、<Heading /> タグが表示される位置に、前の Heading コンポーネントをレンダリングします。

Pages/HeadingExample.razor:

@page "/heading-example"

<Heading />
@page "/heading-example"

<Heading />

同じ名前空間内のコンポーネント名と一致しない最初の文字が大文字の HTML 要素がコンポーネントに含まれている場合、要素に予期しない名前が付いていることを示す警告が出力されます。 コンポーネントの名前空間に @using ディレクティブを追加すると、コンポーネントを使用できるようになり、警告が解決されます。 詳細については、「名前空間」セクションを参照してください。

このセクションに示されている Heading コンポーネントの例には @page ディレクティブがないため、Heading コンポーネントには、ブラウザーで直接要求を使用してユーザーが直接アクセスすることはできません。 しかし、@page ディレクティブを持つコンポーネントは、他のコンポーネントの入れ子にすることができます。 Razor ファイルの先頭に @page "/heading" を含めることによって Heading コンポーネントに直接アクセスできた場合は、/heading/heading-example の両方でブラウザー要求に対してコンポーネントがレンダリングされます。

名前空間

一般に、コンポーネントの名前空間は、アプリのルート名前空間と、アプリ内のコンポーネントの場所 (フォルダー) から派生します。 アプリのルート名前空間が BlazorSample で、Counter コンポーネントが Pages フォルダーに存在する場合:

  • Counter コンポーネントの名前空間は BlazorSample.Pages になります。
  • コンポーネントの完全修飾型名は BlazorSample.Pages.Counter になります。

コンポーネントを保持するカスタム フォルダーの場合は、@using ディレクティブを親コンポーネントまたはアプリの _Imports.razor ファイルに追加します。 次の例では、Components フォルダー内のコンポーネントを使用できるようにします。

@using BlazorSample.Components

注意

_Imports.razor ファイルの @using ディレクティブは、C# ファイル (.cs) ではなく Razor ファイル (.razor) にのみ適用されます。

コンポーネントは、完全修飾名を使用して参照することもできます。この場合、@using ディレクティブは必要ありません。 次の例では、アプリの Components フォルダーで ProductDetail コンポーネントを直接参照します。

<BlazorSample.Components.ProductDetail />

Razor で作成されるコンポーネントの名前空間は、(優先順に並んだ) 以下に基づきます。

  • Razor ファイルのマークアップ内の @namespace ディレクティブ (@namespace BlazorSample.CustomNamespace など)。
  • プロジェクト ファイル内のプロジェクトの RootNamespace (<RootNamespace>BlazorSample</RootNamespace> など)。
  • プロジェクト ファイルのファイル名 (.csproj) から取得されたプロジェクト名、およびプロジェクト ルートからコンポーネントへのパス。 たとえば、フレームワークでは、プロジェクト名前空間が BlazorSample (BlazorSample.csproj) の {PROJECT ROOT}/Pages/Index.razor は、Index コンポーネントの名前空間BlazorSample.Pagesに解決されます。 {PROJECT ROOT} はプロジェクトのルート パスです。 コンポーネントは C# の名前のバインド規則に従います。 この例の Index コンポーネントの場合、スコープ内のコンポーネントは、次のすべてのコンポーネントです。
    • 同じフォルダー (Pages) に含まれるもの。
    • 別の名前空間を明示的に指定しない、プロジェクトのルート内のコンポーネント。

以下はサポートされて いません

  • global:: 修飾。
  • 別名が付けられた using ステートメントによるコンポーネントのインポート。 たとえば、@using Foo = Bar はサポートされていません。
  • 部分修飾名。 たとえば、@using BlazorSample をコンポーネントに追加してから、アプリの Shared フォルダー (Shared/NavMenu.razor) の NavMenuコンポーネントを <Shared.NavMenu></Shared.NavMenu> で参照することはできません。

部分クラスのサポート

コンポーネントは C# 部分クラスとして生成され、次のいずれかの方法を使用して作成されます。

  • 1 つのファイルには、1 つまたは複数の @code ブロックで定義されている C# コード、HTML マークアップ、および Razor マークアップが含まれています。 Blazor プロジェクト テンプレートでは、この 1 つのファイルの方法を使用してコンポーネントを定義します。
  • HTML および Razor マークアップは、Razor ファイル (.razor) に配置されます。 C# コードは、部分クラスとして定義されている分離コード ファイル (.cs) に配置されます。

注意

コンポーネント固有のスタイルを定義するコンポーネント スタイル シートは、個別のファイル (.css) です。 Blazor CSS の分離については、「ASP.NET Core Blazor の CSS の分離」で後ほど説明します。

次の例は、Blazor プロジェクト テンプレートから生成されたアプリ内の @code ブロックを含む既定の Counter コンポーネントを示しています。 マークアップと C# コードは同じファイル内にあります。 これは、コンポーネントの作成で最も一般的に使用される方法です。

Pages/Counter.razor:

@page "/counter"

<h1>Counter</h1>

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

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

@code {
    private int currentCount = 0;

    private void IncrementCount()
    {
        currentCount++;
    }
}
@page "/counter"

<h1>Counter</h1>

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

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

@code {
    private int currentCount = 0;

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

次の Counter コンポーネントでは、部分クラスで分離コード ファイルを使用して、C# コードから HTML と Razor マークアップを分割します。

Pages/CounterPartialClass.razor:

@page "/counter-partial-class"

<h1>Counter</h1>

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

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

Pages/CounterPartialClass.razor.cs:

namespace BlazorSample.Pages
{
    public partial class CounterPartialClass
    {
        private int currentCount = 0;

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

_Imports.razor ファイルの @using ディレクティブは、C# ファイル (.cs) ではなく Razor ファイル (.razor) にのみ適用されます。 必要に応じて、部分クラス ファイルに名前空間を追加します。

コンポーネントで使用される一般的な名前空間は次のとおりです。

using System.Net.Http;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.Forms;
using Microsoft.AspNetCore.Components.Routing;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.Web.Virtualization;
using Microsoft.JSInterop;
using System.Net.Http;
using Microsoft.AspNetCore.Components.Forms;
using Microsoft.AspNetCore.Components.Routing;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.JSInterop;

一般的な名前空間には、アプリの名前空間と、そのアプリの Shared フォルダーに対応する名前空間も含まれます。

using BlazorSample;
using BlazorSample.Shared;

基本クラスの指定

@inherits ディレクティブは、コンポーネントの基本クラスを指定するために使用されます。 次の例は、コンポーネントで基本クラスを継承し、コンポーネントのプロパティとメソッドを提供する方法を示しています。 BlazorRocksBase 基本クラスは、ComponentBase から派生します。

Pages/BlazorRocks.razor:

@page "/blazor-rocks"
@inherits BlazorRocksBase

<h1>@BlazorRocksText</h1>
@page "/blazor-rocks"
@inherits BlazorRocksBase

<h1>@BlazorRocksText</h1>

BlazorRocksBase.cs:

using Microsoft.AspNetCore.Components;

namespace BlazorSample
{
    public class BlazorRocksBase : ComponentBase
    {
        public string BlazorRocksText { get; set; } =
            "Blazor rocks the browser!";
    }
}
using Microsoft.AspNetCore.Components;

namespace BlazorSample
{
    public class BlazorRocksBase : ComponentBase
    {
        public string BlazorRocksText { get; set; } =
            "Blazor rocks the browser!";
    }
}

コンポーネントのパラメーター

''コンポーネント パラメーター'' によりデータがコンポーネントに渡されます。これらのパラメーターは、[Parameter] 属性を指定したコンポーネント クラス上で、パブリック C# プロパティを使用して定義されます。 次の例では、組み込みの参照型 (System.String) とユーザー定義の参照型 (PanelBody) がコンポーネント パラメーターとして渡されます。

PanelBody.cs:

public class PanelBody
{
    public string Text { get; set; }
    public string Style { get; set; }
}
public class PanelBody
{
    public string Text { get; set; }
    public string Style { get; set; }
}

Shared/ParameterChild.razor:

<div class="card w-25" style="margin-bottom:15px">
    <div class="card-header font-weight-bold">@Title</div>
    <div class="card-body" style="font-style:@Body.Style">
        @Body.Text
    </div>
</div>

@code {
    [Parameter]
    public string Title { get; set; } = "Set By Child";

    [Parameter]
    public PanelBody Body { get; set; } =
        new()
        {
            Text = "Set by child.",
            Style = "normal"
        };
}
<div class="card w-25" style="margin-bottom:15px">
    <div class="card-header font-weight-bold">@Title</div>
    <div class="card-body" style="font-style:@Body.Style">
        @Body.Text
    </div>
</div>

@code {
    [Parameter]
    public string Title { get; set; } = "Set By Child";

    [Parameter]
    public PanelBody Body { get; set; } =
        new PanelBody()
        {
            Text = "Set by child.",
            Style = "normal"
        };
}

警告

コンポーネント パラメーターの初期値の指定はサポートされていますが、コンポーネントが初めてレンダリングされた後に独自のパラメーターに書き込むコンポーネントは作成しないでください。 詳細については、この記事の「上書きされたパラメーター」セクションをご覧ください。

ParameterChild コンポーネントの Title および Body コンポーネント パラメーターは、コンポーネントのインスタンスをレンダリングする HTML タグの引数によって設定されます。 次の ParameterParent コンポーネントでは、2 つの ParameterChild コンポーネントがレンダリングされます。

  • 1 つ目の ParameterChild コンポーネントは、パラメーター引数を指定せずにレンダリングされます。
  • 2 つ目の ParameterChild コンポーネントでは、明示的な C# 式を使用して PanelBody のプロパティの値を設定する ParameterParent コンポーネントからの TitleBody の値を受け取ります。

Pages/ParameterParent.razor:

@page "/parameter-parent"

<h1>Child component (without attribute values)</h1>

<ParameterChild />

<h1>Child component (with attribute values)</h1>

<ParameterChild Title="Set by Parent"
                Body="@(new PanelBody() { Text = "Set by parent.", Style = "italic" })" />
@page "/parameter-parent"

<h1>Child component (without attribute values)</h1>

<ParameterChild />

<h1>Child component (with attribute values)</h1>

<ParameterChild Title="Set by Parent"
                Body="@(new PanelBody() { Text = "Set by parent.", Style = "italic" })" />

ParameterParent コンポーネントでコンポーネントのパラメーター値が指定されていない場合、ParameterParent コンポーネントの次のレンダリングされた HTML マークアップに ParameterChild コンポーネントの既定値が表示されます。 ParameterParent コンポーネントでコンポーネントのパラメーター値が指定されている場合は、その ParameterChild コンポーネントの既定値が置き換えられます。

注意

明確にするために、レンダリングされた CSS スタイル クラスは次のレンダリングされた HTML マークアップには表示されていません。

<h1>Child component (without attribute values)</h1>

<div>
    <div>Set By Child</div>
    <div>Set by child.</div>
</div>

<h1>Child component (with attribute values)</h1>

<div>
    <div>Set by Parent</div>
    <div>Set by parent.</div>
</div>

Razor の予約済みの @ 記号を使用して、メソッドの C# フィールド、プロパティ、または結果を HTML 属性値としてコンポーネント パラメーターに代入します。 次の ParameterParent2 コンポーネントでは、上記の ParameterChild コンポーネントの 4 つのインスタンスが表示され、それらの Title パラメーター値が次のように設定されます。

  • title フィールドの値。
  • GetTitle C# メソッドの結果。
  • 暗黙的な C# 式を使用する ToLongDateString による長い形式での現在のローカル日付。
  • panelData オブジェクトの Title プロパティ。

Pages/ParameterParent2.razor:

@page "/parameter-parent-2"

<ParameterChild Title="@title" />

<ParameterChild Title="@GetTitle()" />

<ParameterChild Title="@DateTime.Now.ToLongDateString()" />

<ParameterChild Title="@panelData.Title" />

@code {
    private string title = "From Parent field";
    private PanelData panelData = new();

    private string GetTitle()
    {
        return "From Parent method";
    }

    private class PanelData
    {
        public string Title { get; set; } = "From Parent object";
    }
}
@page "/parameter-parent-2"

<ParameterChild Title="@title" />

<ParameterChild Title="@GetTitle()" />

<ParameterChild Title="@DateTime.Now.ToLongDateString()" />

<ParameterChild Title="@panelData.Title" />

@code {
    private string title = "From Parent field";
    private PanelData panelData = new PanelData();

    private string GetTitle()
    {
        return "From Parent method";
    }

    private class PanelData
    {
        public string Title { get; set; } = "From Parent object";
    }
}

注意

C# メンバーをコンポーネント パラメーターに代入する場合は、メンバーにプレフィックスとして @ 記号を付け、パラメーターの HTML 属性にはプレフィックスは付けません。

正しい:

<ParameterChild Title="@title" />

正しくない:

<ParameterChild @Title="title" />

Razor ページ (.cshtml) とは異なり、Blazor では、コンポーネントのレンダリング中に Razor 式で非同期処理を実行することはできません。 これは、Blazor が対話型 UI をレンダリングするように設計されているためです。 対話型 UI の場合、画面には常に何かが表示されている必要があるため、レンダリング フローをブロックしても意味はありません。 代わりに、非同期処理は、いずれかの非同期ライフサイクル イベント中に実行されます。 非同期ライフサイクル イベントが発生するたびに、コンポーネントは再びレンダリングされる可能性があります。 次の Razor 構文はサポートされて いません

<ParameterChild Title="@await ..." />

上記の例に含まれるコードでは、アプリのビルド時に "コンパイラ エラー" が発生します。

'await' 演算子は、非同期メソッド内でのみ使用できます。 このメソッドを 'async' 修飾子でマークし、その戻り値の型を 'Task' に変更することを検討してください。

上記の例で非同期的に Title パラメーターの値を取得するには、次の例に示すように、コンポーネントで OnInitializedAsync ライフサイクル イベントを使用できます。

<ParameterChild Title="@title" />

@code {
    private string title;
    
    protected override async Task OnInitializedAsync()
    {
        title = await ...;
    }
}

詳細については、「ASP.NET Core Razor コンポーネントのライフサイクル」を参照してください。

パラメーターに代入するために、明示的な Razor 式を使用してテキストを式の結果と連結することは、サポートされて いません。 次の例では、テキスト "Set by " とオブジェクトのプロパティ値を連結しようとしています。 この構文は Razor ページ (.cshtml) でサポートされていますが、コンポーネントで子の Title パラメーターに代入する場合は無効です。 次の Razor 構文はサポートされて いません

<ParameterChild Title="Set by @(panelData.Title)" />

上記の例に含まれるコードでは、アプリのビルド時に "コンパイラ エラー" が発生します。

コンポーネント属性では、複合コンテンツ (C# とマークアップの混合) はサポートされていません。

合成値の割り当てをサポートするには、メソッド、フィールド、またはプロパティを使用します。 次の例では、C# メソッド GetTitle で、"Set by " とオブジェクトのプロパティ値の連結を実行します。

Pages/ParameterParent3.razor:

@page "/parameter-parent-3"

<ParameterChild Title="@GetTitle()" />

@code {
    private PanelData panelData = new();

    private string GetTitle() => $"Set by {panelData.Title}";

    private class PanelData
    {
        public string Title { get; set; } = "Parent";
    }
}
@page "/parameter-parent-3"

<ParameterChild Title="@GetTitle()" />

@code {
    private PanelData panelData = new PanelData();

    private string GetTitle() => $"Set by {panelData.Title}";

    private class PanelData
    {
        public string Title { get; set; } = "Parent";
    }
}

詳細については、「ASP.NET Core の Razor 構文リファレンス」を参照してください。

警告

コンポーネント パラメーターの初期値の指定はサポートされていますが、コンポーネントが初めてレンダリングされた後に独自のパラメーターに書き込むコンポーネントは作成しないでください。 詳細については、この記事の「上書きされたパラメーター」セクションをご覧ください。

コンポーネント パラメーターは "自動プロパティ" として宣言する必要があります。つまり、それらの getset アクセサーにカスタム ロジックを含めることはできません。 たとえば、次の StartData プロパティは自動プロパティです。

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

get または set アクセサーにカスタム ロジックを配置しないでください。なぜなら、コンポーネント パラメーターの目的は、親コンポーネントから子コンポーネントに情報をフローさせるためのチャネルとして使用することだけだからです。 子コンポーネントのプロパティの set アクセサーに、親コンポーネントの再レンダリングが発生する原因となるロジックが含まれている場合、レンダリングの無限ループが発生します。

受け取ったパラメーター値を変換するには、次のようにします。

  • パラメーター プロパティは、指定された生データを表す自動プロパティのままにしておきます。
  • 異なるプロパティまたはメソッドを作成し、パラメーター プロパティに基づいて変換されたデータを指定します。

OnParametersSetAsync をオーバーライドし、新しいデータを受け取るたびに受け取ったパラメーターを変換します。

コンポーネント パラメーターに初期値を書き込むことはサポートされています。これは、初期値の代入によって、Blazor の自動コンポーネント レンダリングが妨げられることはないからです。 DateTime.Now での現在のローカル DateTimeStartData への次の代入は、コンポーネントで有効な構文です。

[Parameter]
public DateTime StartData { get; set; } = DateTime.Now;

DateTime.Now の初期代入後は、開発者コードで StartData に値を代入 しない でください。 詳細については、この記事の「上書きされたパラメーター」セクションをご覧ください。

ルート パラメーター

コンポーネントでは、@page ディレクティブのルート テンプレートでルート パラメーターを指定できます。 Blazor ルーターでは、ルート パラメーターを使用して、対応するコンポーネント パラメーターが設定されます。

省略可能なルート パラメーターがサポートされています。 次の例では、省略可能なパラメーター text を使用して、ルート セグメントの値をコンポーネントの Text プロパティに割り当てます。 セグメントが存在しない場合、Text の値は OnInitialized ライフサイクル メソッドで "fantastic" に設定されます。

Pages/RouteParameter.razor:

@page "/route-parameter/{text?}"

<h1>Blazor is @Text!</h1>

@code {
    [Parameter]
    public string Text { get; set; }

    protected override void OnInitialized()
    {
        Text = Text ?? "fantastic";
    }
}

Pages/RouteParameter.razor:

@page "/route-parameter"
@page "/route-parameter/{text}"

<h1>Blazor is @Text!</h1>

@code {
    [Parameter]
    public string Text { get; set; }

    protected override void OnInitialized()
    {
        Text = Text ?? "fantastic";
    }
}

省略可能なルート パラメーターはサポートされていないため、前の例では 2 つの @page ディレクティブが適用されます。 1 つ目の @page ディレクティブでは、ルート パラメーターを指定せずにコンポーネントへの移動を許可します。 2 番目の @page ディレクティブでは、{text} ルート パラメーターを受け取り、その値を Text プロパティに割り当てます。

複数のフォルダーにわたりパスをキャプチャするキャッチオール ルート パラメーター ({*pageRoute}) の詳細については、「ASP.NET Core Blazor のルーティング」を参照してください。

上書きされたパラメーター

Blazor フレームワークでは、一般に安全な親から子へのパラメーターの割り当てが行われます。

  • パラメーターが予期せずに上書きされることはありません。
  • 副作用は最小限に抑えられます。 たとえば、追加のレンダリングは、無限のレンダリング ループが作成される可能性があるため、回避されます。

子コンポーネントは、親コンポーネントのレンダリング時に既存の値を上書きする可能性がある新しいパラメーター値を受け取ります。 子コンポーネントでパラメーター値が誤って上書きされることは、1 つまたは複数のデータバインド パラメーターを使用してコンポーネントを開発しており、開発者が子のパラメーターに直接書き込む場合に多く発生します。

  • 子コンポーネントは、親コンポーネントの 1 つまたは複数のパラメーター値を使用してレンダリングされます。
  • 子によって、パラメーターの値が直接書き込まれます。
  • 親コンポーネントがレンダリングされ、子のパラメーターの値が上書きされます。

パラメーター値の上書きの可能性は、子コンポーネントのプロパティ set アクセサーにも及びます。

重要

一般的なガイダンスとして、コンポーネントが初めてレンダリングされた後に、独自のパラメーターに直接書き込むコンポーネントを作成しないでください。

次が実行される、問題のある Expander コンポーネントについて考えてみましょう。

  • 子コンテンツのレンダリング。
  • コンポーネント パラメーター (Expanded) を使用した、子コンテンツの表示の切り替え。
  • コンポーネントによって、Expanded パラメーターに直接書き込まれます。これは上書きされるパラメーターの問題を示しているため、回避する必要があります。

次の Expander コンポーネントでこのシナリオに不適切な方法が示された後、正しい方法を示すために変更された Expander コンポーネントが表示されます。 説明されている動作を体験するために、次の例をローカルのサンプル アプリで使用することができます。

Shared/Expander.razor:

<div @onclick="Toggle" class="card bg-light mb-3" style="width:30rem">
    <div class="card-body">
        <h2 class="card-title">Toggle (<code>Expanded</code> = @Expanded)</h2>

        @if (Expanded)
        {
            <p class="card-text">@ChildContent</p>
        }
    </div>
</div>

@code {
    [Parameter]
    public bool Expanded { get; set; }

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

    private void Toggle()
    {
        Expanded = !Expanded;
    }
}
<div @onclick="Toggle" class="card bg-light mb-3" style="width:30rem">
    <div class="card-body">
        <h2 class="card-title">Toggle (<code>Expanded</code> = @Expanded)</h2>

        @if (Expanded)
        {
            <p class="card-text">@ChildContent</p>
        }
    </div>
</div>

@code {
    [Parameter]
    public bool Expanded { get; set; }

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

    private void Toggle()
    {
        Expanded = !Expanded;
    }
}

Expander コンポーネントは、StateHasChanged を呼び出す可能性のある次の ExpanderExample 親コンポーネントに追加されます。

Pages/ExpanderExample.razor:

@page "/expander-example"

<Expander Expanded="true">
    Expander 1 content
</Expander>

<Expander Expanded="true" />

<button @onclick="StateHasChanged">
    Call StateHasChanged
</button>
@page "/expander-example"

<Expander Expanded="true">
    Expander 1 content
</Expander>

<Expander Expanded="true" />

<button @onclick="StateHasChanged">
    Call StateHasChanged
</button>

初期状態では、Expanded プロパティが切り替えられると、Expander コンポーネントはそれぞれ独立して動作します。 子コンポーネントの状態は、想定どおりのままです。 親で StateHasChanged が呼び出されると、最初の子コンポーネントの Expanded パラメーターが初期値 (true) にリセットされます。 2 つめの Expander コンポーネントの Expanded 値はリセットされません。これは、2 つめのコンポーネントでは子コンテンツがレンダリングされないためです。

前のシナリオでの状態を維持するには、Expander コンポーネントで "プライベート フィールド" を使用して、切り替え状態を維持します。

次の変更された Expander コンポーネント:

  • 親から Expanded コンポーネント パラメーター値を受け入れます。
  • コンポーネント パラメーター値を、OnInitialized イベントの "プライベート フィールド" (expanded) に割り当てます。
  • プライベート フィールドを使用して、その内部のトグル状態を維持します。これは、パラメーターに直接書き込まれないようにする方法を示しています。

注意

このセクションのアドバイスは、コンポーネント パラメーター set アクセサーの同様のロジックに及ぶため、同様の望ましくない副作用が発生する可能性があります。

Shared/Expander.razor:

<div @onclick="Toggle" class="card bg-light mb-3" style="width:30rem">
    <div class="card-body">
        <h2 class="card-title">Toggle (<code>expanded</code> = @expanded)</h2>

        @if (expanded)
        {
            <p class="card-text">@ChildContent</p>
        }
    </div>
</div>

@code {
    private bool expanded;

    [Parameter]
    public bool Expanded { get; set; }

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

    protected override void OnInitialized()
    {
        expanded = Expanded;
    }

    private void Toggle()
    {
        expanded = !expanded;
    }
}
<div @onclick="Toggle" class="card bg-light mb-3" style="width:30rem">
    <div class="card-body">
        <h2 class="card-title">Toggle (<code>expanded</code> = @expanded)</h2>

        @if (expanded)
        {
            <p class="card-text">@ChildContent</p>
        }
    </div>
</div>

@code {
    private bool expanded;

    [Parameter]
    public bool Expanded { get; set; }

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

    protected override void OnInitialized()
    {
        expanded = Expanded;
    }

    private void Toggle()
    {
        expanded = !expanded;
    }
}

詳細については、Blazor両方向のバインド エラー (dotnet/aspnetcore #24599) に関するページを参照してください。

子コンテンツ

コンポーネントでは、別のコンポーネントのコンテンツを設定できます。 代入コンポーネントでは、子コンポーネントの開始および終了タグの間にコンテンツを指定します。

次の例では、RenderFragmentChild コンポーネントに、RenderFragment としてレンダリングする UI のセグメントを表す ChildContent プロパティがあります。 コンポーネントの Razor マークアップ内の ChildContent の位置は、コンテンツが最終的な HTML 出力にレンダリングされる場所です。

Shared/RenderFragmentChild.razor:

<div class="card w-25" style="margin-bottom:15px">
    <div class="card-header font-weight-bold">Child content</div>
    <div class="card-body">@ChildContent</div>
</div>

@code {
    [Parameter]
    public RenderFragment ChildContent { get; set; }
}
<div class="card w-25" style="margin-bottom:15px">
    <div class="card-header font-weight-bold">Child content</div>
    <div class="card-body">@ChildContent</div>
</div>

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

重要

RenderFragment コンテンツを受け取るプロパティは、規則によって ChildContent という名前にする必要があります。

次の RenderFragmentParent コンポーネントでは、子コンポーネントの開始および終了タグ内にコンテンツを配置することによって RenderFragmentChild をレンダリングするためのコンテンツを指定します。

Pages/RenderFragmentParent.razor:

@page "/render-fragment-parent"

<h1>Render child content</h1>

<RenderFragmentChild>
    Content of the child component is supplied
    by the parent component.
</RenderFragmentChild>
@page "/render-fragment-parent"

<h1>Render child content</h1>

<RenderFragmentChild>
    Content of the child component is supplied
    by the parent component.
</RenderFragmentChild>

Blazor による子コンテンツのレンダリング方法により、for ループ内のコンポーネントのレンダリングでは、インクリメントするループ変数が RenderFragmentChild コンポーネントのコンテンツ内で使用されている場合、ローカル インデックス変数が必要になります。 次の例は、前の RenderFragmentParent コンポーネントに追加できます。

<h1>Three children with an index variable</h1>

@for (int c = 0; c < 3; c++)
{
    var current = c;

    <RenderFragmentChild>
        Count: @current
    </RenderFragmentChild>
}

または、for ループではなく、Enumerable.Range と共に foreach ループを使用します。 次の例は、前の RenderFragmentParent コンポーネントに追加できます。

<h1>Second example of three children with an index variable</h1>

@foreach (var c in Enumerable.Range(0,3))
{
    <RenderFragmentChild>
        Count: @c
    </RenderFragmentChild>
}

RenderFragment をコンポーネント UI のテンプレートとして使用する方法については、次の記事を参照してください。

属性スプラッティングと任意のパラメーター

コンポーネントでは、コンポーネントの宣言されたパラメーターに加えて、追加の属性をキャプチャしてレンダリングできます。 追加の属性は、ディクショナリに取り込んでから、@attributes Razor ディレクティブ属性を使用してコンポーネントがレンダリングされるときに要素に "スプラッティング" できます。 このシナリオは、さまざまなカスタマイズをサポートするマークアップ要素を生成するコンポーネントを定義する場合に便利です。 たとえば、多くのパラメーターをサポートする <input> に対して、属性を個別に定義するのは面倒な場合があります。

次の Splat コンポーネントでは、以下のことを行います。

  • 1 つ目の <input> 要素 ( id="useIndividualParams" ) では、個々のコンポーネント パラメーターを使用します。
  • 2 つ目の <input> 要素 (id="useAttributesDict") では、属性のスプラッティングを使用します。

Pages/Splat.razor:

@page "/splat"

<input id="useIndividualParams"
       maxlength="@maxlength"
       placeholder="@placeholder"
       required="@required"
       size="@size" />

<input id="useAttributesDict"
       @attributes="InputAttributes" />

@code {
    private string maxlength = "10";
    private string placeholder = "Input placeholder text";
    private string required = "required";
    private string size = "50";

    private Dictionary<string, object> InputAttributes { get; set; } =
        new()
        {
            { "maxlength", "10" },
            { "placeholder", "Input placeholder text" },
            { "required", "required" },
            { "size", "50" }
        };
}
@page "/splat"

<input id="useIndividualParams"
       maxlength="@maxlength"
       placeholder="@placeholder"
       required="@required"
       size="@size" />

<input id="useAttributesDict"
       @attributes="InputAttributes" />

@code {
    private string maxlength = "10";
    private string placeholder = "Input placeholder text";
    private string required = "required";
    private string size = "50";

    private Dictionary<string, object> InputAttributes { get; set; } =
        new Dictionary<string, object>()
        {
            { "maxlength", "10" },
            { "placeholder", "Input placeholder text" },
            { "required", "required" },
            { "size", "50" }
        };
}

Web ページにレンダリングされる <input> 要素は同じです。

<input id="useIndividualParams"
       maxlength="10"
       placeholder="Input placeholder text"
       required="required"
       size="50">

<input id="useAttributesDict"
       maxlength="10"
       placeholder="Input placeholder text"
       required="required"
       size="50">

任意の属性を受け入れるには、CaptureUnmatchedValues プロパティを true に設定したコンポーネント パラメーターを定義します。

@code {
    [Parameter(CaptureUnmatchedValues = true)]
    public Dictionary<string, object> InputAttributes { get; set; }
}

[Parameter]CaptureUnmatchedValues プロパティにより、パラメーターを他のパラメーターと一致しないすべての属性と一致させることができます。 1 つのコンポーネントで、CaptureUnmatchedValues を持つパラメーターは 1 つだけ定義できます。 CaptureUnmatchedValues で使用されるプロパティの型は、文字列キーを使って Dictionary<string, object> から代入できる必要があります。 このシナリオでは、IEnumerable<KeyValuePair<string, object>> または IReadOnlyDictionary<string, object> も使用できます。

要素属性の位置を基準とした @attributes の位置は重要です。 @attributes が要素にスプラッティングされると、属性は右から左 (最後から最初) に処理されます。 子コンポーネントを使用する次の親コンポーネントの例を考えてみます。

Shared/AttributeOrderChild1.razor:

<div @attributes="AdditionalAttributes" extra="5" />

@code {
    [Parameter(CaptureUnmatchedValues = true)]
    public IDictionary<string, object> AdditionalAttributes { get; set; }
}
<div @attributes="AdditionalAttributes" extra="5" />

@code {
    [Parameter(CaptureUnmatchedValues = true)]
    public IDictionary<string, object> AdditionalAttributes { get; set; }
}

Pages/AttributeOrderParent1.razor:

@page "/attribute-order-parent-1"

<AttributeOrderChild1 extra="10" />
@page "/attribute-order-parent-1"

<AttributeOrderChild1 extra="10" />

AttributeOrderChild1 コンポーネントの extra 属性が @attributes の右側に設定されています。 属性は右から左 (最後から最初) に処理されるため、追加の属性によって渡された場合に、AttributeOrderParent1 コンポーネントのレンダリングされる <div> に、extra="5" が含まれます。

<div extra="5" />

次の例では、子コンポーネントの <div>extra@attributes の順序が逆になります。

Shared/AttributeOrderChild2.razor:

<div extra="5" @attributes="AdditionalAttributes" />

@code {
    [Parameter(CaptureUnmatchedValues = true)]
    public IDictionary<string, object> AdditionalAttributes { get; set; }
}
<div extra="5" @attributes="AdditionalAttributes" />

@code {
    [Parameter(CaptureUnmatchedValues = true)]
    public IDictionary<string, object> AdditionalAttributes { get; set; }
}

Pages/AttributeOrderParent2.razor:

@page "/attribute-order-parent-2"

<AttributeOrderChild2 extra="10" />
@page "/attribute-order-parent-2"

<AttributeOrderChild2 extra="10" />

追加の属性によって渡された場合に、親コンポーネントのレンダリングされる Web ページの <div> には extra="10" が含まれます。

<div extra="10" />

コンポーネントへの参照をキャプチャする

コンポーネント参照を使用すると、コマンドを発行するためのコンポーネント インスタンスを参照することができます。 コンポーネント参照をキャプチャするには:

  • 子コンポーネントに @ref 属性を追加します。
  • 子コンポーネントと同じ型のフィールドを定義します。

コンポーネントがレンダリングされると、フィールドにコンポーネント インスタンスが設定されます。 その後、そのインスタンスに対して .NET メソッドを呼び出すことができます。

ChildMethod が呼び出されたときにメッセージをログに記録する次の ReferenceChild コンポーネントについて考えてみます。

Shared/ReferenceChild.razor:

@using Microsoft.Extensions.Logging
@inject ILogger<ReferenceChild> logger

@code {
    public void ChildMethod(int value)
    {
        logger.LogInformation("Received {Value} in ChildMethod", value);
    }
}
@using Microsoft.Extensions.Logging
@inject ILogger<ReferenceChild> logger

@code {
    public void ChildMethod(int value)
    {
        logger.LogInformation("Received {Value} in ChildMethod", value);
    }
}

コンポーネント参照は、コンポーネントがレンダリングされた後にのみ設定され、その出力には ReferenceChild 要素が含まれます。 コンポーネントがレンダリングされるまで、参照するものはありません。

コンポーネントのレンダリングが終了した後にコンポーネント参照を操作するには、OnAfterRender または OnAfterRenderAsync メソッドを使用します。

イベント ハンドラーで参照変数を使用するには、ラムダ式を使用するか、OnAfterRender または OnAfterRenderAsync メソッドでイベント ハンドラー デリゲートを割り当てます。 これにより、イベント ハンドラーが割り当てられる前に参照変数が確実に割り当てられます。

次のラムダ手法では、前の ReferenceChild コンポーネントを使用します。

Pages/ReferenceParent1.razor:

@page "/reference-parent-1"

<button @onclick="@(() => childComponent.ChildMethod(5))">
    Call <code>ReferenceChild.ChildMethod</code> with an argument of 5
</button>

<ReferenceChild @ref="childComponent" />

@code {
    private ReferenceChild childComponent;
}
@page "/reference-parent-1"

<button @onclick="@(() => childComponent.ChildMethod(5))">
    Call <code>ReferenceChild.ChildMethod</code> with an argument of 5
</button>

<ReferenceChild @ref="childComponent" />

@code {
    private ReferenceChild childComponent;
}

次のデリゲート手法では、上記の ReferenceChild コンポーネントを使用します。

Pages/ReferenceParent2.razor:

@page "/reference-parent-2"

<button @onclick="callChildMethod">
    Call <code>ReferenceChild.ChildMethod</code> with an argument of 5
</button>

<ReferenceChild @ref="childComponent" />

@code {
    private ReferenceChild childComponent;
    private Action callChildMethod;

    protected override void OnAfterRender(bool firstRender)
    {
        if (firstRender)
        {
            callChildMethod = CallChildMethod;
        }
    }

    private void CallChildMethod()
    {
        childComponent.ChildMethod(5);
    }
}
@page "/reference-parent-2"

<button @onclick="callChildMethod">
    Call <code>ReferenceChild.ChildMethod</code> with an argument of 5
</button>

<ReferenceChild @ref="childComponent" />

@code {
    private ReferenceChild childComponent;
    private Action callChildMethod;

    protected override void OnAfterRender(bool firstRender)
    {
        if (firstRender)
        {
            callChildMethod = CallChildMethod;
        }
    }

    private void CallChildMethod()
    {
        childComponent.ChildMethod(5);
    }
}

コレクションを使用して、ループ内のコンポーネントを参照します。 次に例を示します。

  • コンポーネントは List<T> に追加されます。
  • List<T> のコンポーネント インデックスで対応するコンポーネントの ChildMethod をトリガーするコンポーネントごとに、ボタンが作成されます。

前の ReferenceChild コンポーネントを使用する Pages/ReferenceParent3.razor は次のとおりです。

@page "/reference-parent-3"

<ul>
    @for (int i = 0; i < 5; i++)
    {
        var index = i;
        var v = r.Next(1000);

        <li>
            <ReferenceChild @ref="childComponent" />
            <button @onclick="@(() => callChildMethod(index, v))">
                Component index @index: Call <code>ReferenceChild.ChildMethod(@v)</code>
            </button>
        </li>
    }
</ul>

@code {
    private Random r = new();
    private List<ReferenceChild> components = new();
    private Action<int, int> callChildMethod;

    private ReferenceChild childComponent
    {
        set => components.Add(value);
    }

    protected override void OnAfterRender(bool firstRender)
    {
        if (firstRender)
        {
            callChildMethod = CallChildMethod;
        }
    }

    private void CallChildMethod(int index, int value)
    {
        components.ElementAt(index).ChildMethod(value);
    }
}
@page "/reference-parent-3"

<ul>
    @for (int i = 0; i < 5; i++)
    {
        var index = i;
        var v = r.Next(1000);

        <li>
            <ReferenceChild @ref="childComponent" />
            <button @onclick="@(() => callChildMethod(index, v))">
                Component index @index: Call <code>ReferenceChild.ChildMethod(@v)</code>
            </button>
        </li>
    }
</ul>

@code {
    private Random r = new Random();
    private List<ReferenceChild> components = new List<ReferenceChild>();
    private Action<int, int> callChildMethod;

    private ReferenceChild childComponent
    {
        set => components.Add(value);
    }

    protected override void OnAfterRender(bool firstRender)
    {
        if (firstRender)
        {
            callChildMethod = CallChildMethod;
        }
    }

    private void CallChildMethod(int index, int value)
    {
        components.ElementAt(index).ChildMethod(value);
    }
}

コンポーネント参照の取り込みでは、要素参照の取り込みと同様の構文を使用しますが、コンポーネント参照の取り込みは JavaScript 相互運用機能ではありません。 コンポーネント参照は、JavaScript コードに渡されません。 コンポーネント参照は、.NET コードでのみ使用されます。

重要

子コンポーネントの状態を変えるためにコンポーネント参照を使用 しない でください。 代わりに、通常の宣言型コンポーネント パラメーターを使用して、子コンポーネントにデータを渡します。 コンポーネント パラメーターを使用すると、子コンポーネントが正しいタイミングで自動的にレンダリングされます。 詳細については、「ASP.NET Core Blazor データ バインディング」記事のコンポーネント パラメーターに関するセクションを参照してください。

同期コンテキスト

Blazor では、同期コンテキスト (SynchronizationContext) を使用して、1 つの実行の論理スレッドを強制します。 コンポーネントのライフサイクル メソッドと、Blazor によって発生するイベント コールバックは、同期コンテキストで実行されます。

Blazor Server の同期コンテキストでは、ブラウザーの WebAssembly モデル (シングル スレッド) と厳密に一致するように、シングルスレッド環境のエミュレートが試行されます。 どの時点でも、作業は 1 つのスレッドでのみ実行され、1 つの論理スレッドであるという印象になります。 2 つの操作が同時に実行されることはありません。

スレッドをブロックする呼び出しを避ける

一般に、コンポーネントでは次のメソッドは呼び出さないでください。 次のメソッドでは実行スレッドがブロックされます。そのため、基になる Task が完了するまで、アプリの動作が再開されなくなります。

注意

このセクションに示されているスレッド ブロック メソッドを使用する Blazor ドキュメントの例では、推奨されるコーディング ガイダンスとしてではなく、デモンストレーション目的でのみメソッドを使用しています。 たとえば、いくつかのコンポーネント コードのデモでは、Thread.Sleep を呼び出して、実行時間の長いプロセスをシミュレートします。

状態を更新するために外部でコンポーネント メソッドを呼び出す

タイマーやその他の通知などの外部イベントに基づいてコンポーネントを更新する必要がある場合は、InvokeAsync メソッドを使用します。これにより、Blazor の同期コンテキストにコードの実行がディスパッチされます。 たとえば、リッスンしているコンポーネントに、更新状態について通知できる次の ''通知サービス'' を考えてみます。 Update メソッドは、アプリ内のどこからでも呼び出すことができます。

Notifier.cs:

using System;
using System.Threading.Tasks;

public class Notifier
{
    public async Task Update(string key, int value)
    {
        if (Notify != null)
        {
            await Notify.Invoke(key, value);
        }
    }

    public event Func<string, int, Task> Notify;
}
using System;
using System.Threading.Tasks;

public class Notifier
{
    public async Task Update(string key, int value)
    {
        if (Notify != null)
        {
            await Notify.Invoke(key, value);
        }
    }

    public event Func<string, int, Task> Notify;
}

Notifier サービスを登録するには、次のようにします。

  • Blazor WebAssembly アプリで、Program.Main のシングルトンとしてサービスを登録します。

    builder.Services.AddSingleton<Notifier>();
    
  • Blazor Server アプリで、Startup.ConfigureServices にスコープ指定されたものとしてサービスを登録します。

    services.AddScoped<Notifier>();
    

Notifier サービスを使用して、コンポーネントを更新します。

Pages/NotifierExample.razor:

@page "/notifier-example"
@implements IDisposable
@inject Notifier Notifier

<p>Last update: @lastNotification.key = @lastNotification.value</p>

@code {
    private (string key, int value) lastNotification;

    protected override void OnInitialized()
    {
        Notifier.Notify += OnNotify;
    }

    public async Task OnNotify(string key, int value)
    {
        await InvokeAsync(() =>
        {
            lastNotification = (key, value);
            StateHasChanged();
        });
    }

    public void Dispose()
    {
        Notifier.Notify -= OnNotify;
    }
}
@page "/notifier-example"
@implements IDisposable
@inject Notifier Notifier

<p>Last update: @lastNotification.key = @lastNotification.value</p>

@code {
    private (string key, int value) lastNotification;

    protected override void OnInitialized()
    {
        Notifier.Notify += OnNotify;
    }

    public async Task OnNotify(string key, int value)
    {
        await InvokeAsync(() =>
        {
            lastNotification = (key, value);
            StateHasChanged();
        });
    }

    public void Dispose()
    {
        Notifier.Notify -= OnNotify;
    }
}

前の例の場合:

  • Blazor の同期コンテキスト外で Notifier からコンポーネントの OnNotify メソッドが呼び出されます。 InvokeAsync を使用して、正しいコンテキストに切り替え、レンダリングをキューに登録します。 詳細については、「ASP.NET Core Blazor コンポーネントのレンダリング」を参照してください。
  • コンポーネントでは IDisposable を実装します。 OnNotify デリゲートのサブスクライブは Dispose メソッドで解除されます。このメソッドは、コンポーネントが破棄されたときにフレームワークによって呼び出されます。 詳細については、「ASP.NET Core Razor コンポーネントのライフサイクル」を参照してください。

@ キーを使用して要素とコンポーネントの保存を制御する

要素またはコンポーネントのリストをレンダリングし、その後に要素またはコンポーネントが変更された場合、Blazor では、前のどの要素やコンポーネントを保持できるか、およびモデル オブジェクトをそれらにどのようにマップするかを決定する必要があります。 通常、このプロセスは自動で、無視できますが、プロセスの制御が必要になる場合があります。

次の Details および People コンポーネントについて考えてみます。

  • Details コンポーネントでは、<input> 要素に表示される親の People コンポーネントからデータ (Data) を受け取ります。 表示される任意の <input> 要素では、<input> 要素のいずれかを選択すると、ユーザーからページのフォーカスを受け取ることができます。
  • People コンポーネントでは、Details コンポーネントを使用して表示する person オブジェクトのリストを作成します。 3 秒ごとに、新しい個人がコレクションに追加されます。

このデモで次のことを行うことができます。

  • レンダリングされた複数の Details コンポーネントの中から <input> を選択する。
  • people コレクションの自動拡大時のページのフォーカスの動作を調べる。

Shared/Details.razor:

<input value="@Data" />

@code {
    [Parameter]
    public string Data { get; set; }
}
<input value="@Data" />

@code {
    [Parameter]
    public string Data { get; set; }
}

次の People コンポーネントでは、個人を OnTimerCallback に追加する各イテレーションによって、Blazor でコレクション全体がリビルドされます。 ページのフォーカスは、<input> 要素の ''同じインデックス'' 位置に留まります。そのため、個人が追加されるたびにフォーカスが移動します。 ''ユーザーが選択した内容からフォーカスを移動することは、望ましい動作ではありません。 '' 次のコンポーネントでの不適切な動作を示した後、ユーザーのエクスペリエンスを向上させるために @key ディレクティブ属性が使用されます。

Pages/People.razor:

@page "/people"
@using System.Timers
@implements IDisposable

<ol>
    @foreach (var person in people)
    {
        <li>
            <Details Data="@person.Data" />
        </li>
    }
</ol>

@code {
    private Timer timer = new Timer(3000);

    public List<Person> people =
        new()
        {
            { new Person { Data = "Person 1" } },
            { new Person { Data = "Person 2" } },
            { new Person { Data = "Person 3" } }
        };

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

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            people.Insert(0,
                new Person
                {
                    Data = $"INSERTED {DateTime.Now.ToString("hh:mm:ss tt")}"
                });
            StateHasChanged();
        });
    }

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

    public class Person
    {
        public string Data { get; set; }
    }
}
@page "/people"
@using System.Timers
@implements IDisposable

<ol>
    @foreach (var person in people)
    {
        <li>
            <Details Data="@person.Data" />
        </li>
    }
</ol>

@code {
    private Timer timer = new Timer(3000);

    public List<Person> people =
        new List<Person>()
        {
            { new Person { Data = "Person 1" } },
            { new Person { Data = "Person 2" } },
            { new Person { Data = "Person 3" } }
        };

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

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            people.Insert(0,
                new Person
                {
                    Data = $"INSERTED {DateTime.Now.ToString("hh:mm:ss tt")}"
                });
            StateHasChanged();
        });
    }

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

    public class Person
    {
        public string Data { get; set; }
    }
}

people コレクションのコンテンツは、挿入、削除、または順序変更されたエントリによって変更されます。 再レンダリングによって、表示動作に違いが生じる可能性があります。 people コレクションに個人が挿入されるたびに、現在フォーカスがある要素の ''前の要素'' でフォーカスを受け取ります。 ユーザーのフォーカスは失われます。

要素またはコンポーネントのコレクションへのマッピング プロセスは、@key ディレクティブ属性を使用して制御できます。 @key を使用すると、キーの値に基づいて要素またはコンポーネントが確実に保持されます。 前の例の Details コンポーネントが person 項目にキー指定されている場合、Blazor では、変更されていない Details コンポーネントのレンダリングが無視されます。

people コレクションで @key ディレクティブ属性を使用するように People コンポーネントを変更するには、<Details> 要素を次のように更新します。

<Details @key="person" Data="@person.Data" />

people コレクションが変更されても、Details インスタンスと person インスタンス間の関連付けは保持されます。 コレクションの先頭に Person が挿入されると、その対応する位置に 1 つの新しい Details インスタンスが挿入されます。 他のインスタンスは変更されません。 そのため、コレクションに人々が追加されても、ユーザーのフォーカスは失われません。

@key ディレクティブ属性が使用されている場合、他のコレクションの更新でも同じ動作になります。

  • コレクションからインスタンスが削除された場合、対応するコンポーネント インスタンスのみが UI から除去されます。 他のインスタンスは変更されません。
  • コレクション エントリの順序が変更された場合、対応するコンポーネント インスタンスは UI で保持され、順序が変更されます。

重要

キーは、各コンテナー要素やコンポーネントに対してローカルです。 キーはドキュメント全体でグローバルに比較されません。

@ キーを使用する場面

一般に、リストがレンダリングされ (たとえば、foreach ブロックで)、@key を定義するための適切な値が存在する場合は常に、@key を使用することに意味があります。

次の例に示すように、@key を使用して、オブジェクトが変更されない場合に要素またはコンポーネントのサブツリーを保持することもできます。

例 1:

<li @key="person">
    <input value="@person.Data" />
</li>

例 2:

<div @key="person">
    @* other HTML elements *@
</div>

person インスタンスが変更された場合、@key 属性ディレクティブにより、Blazor に次のことが強制されます。

  • <li> または<div> の全体およびその子孫を破棄する。
  • 新しい要素とコンポーネントを使用して、UI 内でサブツリーをリビルドする。

これは、コレクションがサブツリー内で変更されたときに UI の状態が確実に保持されないようにするのに役立ちます。

@ キーを使用しない場面

@key でレンダリングすると、パフォーマンスが低下します。 パフォーマンスの低下は大きくありませんが、要素やコンポーネントを保持することによって、アプリにメリットがある場合にのみ @key を指定してください。

@key を使用しない場合でも、Blazor では可能な限り、子要素とコンポーネント インスタンスが保持されます。 @key を使用する唯一の利点は、マッピングを選択する Blazor ではなく、保持されているコンポーネント インスタンスにモデル インスタンスをマップする "方法" を制御することです。

@ キーに使用する値

一般に、@key には、次のいずれかの値を指定するのが適切です。

  • モデル オブジェクト インスタンス。 たとえば、前の例では Person インスタンス (person) が使用されていました。 これにより、オブジェクト参照の等価性に基づいて保持されます。
  • 一意識別子。 たとえば、一意識別子は intstring、または Guid 型の主キー値を基にすることができます。

@key に使用される値は確実に競合しないようにしてください。 同じ親要素内で競合する値が検出された場合、Blazor では、古い要素やコンポーネントを新しい要素やコンポーネントに確定的にマップできないため、例外がスローされます。 個別の値 (オブジェクト インスタンスや主キー値など) のみを使用してください。

属性を適用する

属性は、@attribute ディレクティブを使用してコンポーネントに適用できます。 次の例では、[Authorize] 属性をコンポーネントのクラスに適用しています。

@page "/"
@attribute [Authorize]

条件付き HTML 要素属性

HTML 要素属性プロパティは、.NET 値に基づいて条件付きで設定されます。 値が false または null の場合、プロパティは設定されません。 値が true の場合は、プロパティが設定されます。

次の例では、IsCompleted により、<input> 要素の checked プロパティが設定されるかどうかが決定されます。

Pages/ConditionalAttribute.razor:

@page "/conditional-attribute"

<label>
    <input type="checkbox" checked="@IsCompleted" />
    Is Completed?
</label>

<button @onclick="@(() => IsCompleted = !IsCompleted)">
    Change IsCompleted
</button>

@code {
    [Parameter]
    public bool IsCompleted { get; set; }
}
@page "/conditional-attribute"

<label>
    <input type="checkbox" checked="@IsCompleted" />
    Is Completed?
</label>

<button @onclick="@(() => IsCompleted = !IsCompleted)">
    Change IsCompleted
</button>

@code {
    [Parameter]
    public bool IsCompleted { get; set; }
}

詳細については、「ASP.NET Core の Razor 構文リファレンス」を参照してください。

警告

.NET 型が bool の場合、aria-pressed などの一部の HTML 属性が正しく機能しません。 そのような場合は、bool ではなく string 型を使用します。

生 HTML

通常、文字列は DOM テキスト ノードを使用してレンダリングされます。つまり、それらに含まれている可能性のあるすべてのマークアップが無視され、リテラル テキストとして扱われます。 生 HTML をレンダリングするには、HTML コンテンツを MarkupString 値にラップします。 値は HTML または SVG として解析され、DOM に挿入されます。

警告

信頼されていないソースから構築された生 HTML をレンダリングすることは、セキュリティ リスク であるため、常に 避ける必要があります。

次の例では、MarkupString 型を使用して、コンポーネントのレンダリングされた出力に静的 HTML コンテンツのブロックを追加しています。

Pages/MarkupStringExample.razor:

@page "/markup-string-example"

@((MarkupString)myMarkup)

@code {
    private string myMarkup =
        "<p class=\"text-danger\">This is a dangerous <em>markup string</em>.</p>";
}
@page "/markup-string-example"

@((MarkupString)myMarkup)

@code {
    private string myMarkup =
        "<p class=\"text-danger\">This is a dangerous <em>markup string</em>.</p>";
}

Razor テンプレート

レンダリング フラグメントは、UI スニペットを定義するための Razor テンプレート構文を使用して定義できます。 Razor テンプレートでは次の形式を使用します。

@<{HTML tag}>...</{HTML tag}>

次の例では、RenderFragmentRenderFragment<TValue> の値を指定し、コンポーネント内にテンプレートを直接レンダリングする方法を示しています。 レンダリング フラグメントは、引数としてテンプレート コンポーネントに渡すこともできます。

Pages/RazorTemplate.razor:

@page "/razor-template"

@timeTemplate

@petTemplate(new Pet { Name = "Nutty Rex" })

@code {
    private RenderFragment timeTemplate = @<p>The time is @DateTime.Now.</p>;
    private RenderFragment<Pet> petTemplate = (pet) => @<p>Pet: @pet.Name</p>;

    private class Pet
    {
        public string Name { get; set; }
    }
}
@page "/razor-template"

@timeTemplate

@petTemplate(new Pet { Name = "Nutty Rex" })

@code {
    private RenderFragment timeTemplate = @<p>The time is @DateTime.Now.</p>;
    private RenderFragment<Pet> petTemplate = (pet) => @<p>Pet: @pet.Name</p>;

    private class Pet
    {
        public string Name { get; set; }
    }
}

前のコードのレンダリングされた結果:

<p>The time is 4/19/2021 8:54:46 AM.</p>
<p>Pet: Nutty Rex</p>

静的な資産

静的資産の場合、Blazor では ASP.NET Core アプリの規則に従います。 静的資産は、プロジェクトの web root (wwwroot) フォルダー、または wwwroot フォルダーの下のフォルダーにあります。

静的アセットの Web ルートを参照するには、ベース相対パス (/) を使用します。 次の例では、logo.png が物理的に {PROJECT ROOT}/wwwroot/images フォルダーに配置されています。 {PROJECT ROOT} は、アプリのプロジェクト ルートです。

<img alt="Company logo" src="/images/logo.png" />

コンポーネントでは、チルダ スラッシュ表記 (~/) はサポートされて いません

アプリのベース パスの設定の詳細については、「ASP.NET Core Blazor のホストと展開」を参照してください。

タグ ヘルパーはコンポーネントでサポートされない

Tag Helpers はコンポーネントでサポートされていません。 Blazor にタグ ヘルパーのような機能を提供するには、タグ ヘルパーと同じ機能を持つコンポーネントを作成し、代わりにそのコンポーネントを使用します。

スケーラブル ベクター グラフィックス (SVG) イメージ

Blazor では HTML がレンダリングされるため、スケーラブル ベクター グラフィックス (SVG) 画像 (.svg) などのブラウザーでサポートされている画像は、<img> タグを介してサポートされます。

<img alt="Example image" src="image.svg" />

同様に、SVG 画像は、スタイルシート ファイル (.css) の CSS 規則でサポートされています。

.element-class {
    background-image: url("image.svg");
}

ただし、インライン SVG マークアップは、すべてのシナリオでサポートされているわけではありません。 <svg> タグを Razor ファイル (.razor) に直接配置した場合、基本的な画像レンダリングはサポートされますが、多くの高度なシナリオはまだサポートされていません。 たとえば、<use> タグは現在考慮されないため、一部の SVG タグで @bind を使用できません。 詳細については、Blazor の SVG サポート (dotnet/aspnetcore #18271)に関する記事を参照してください。

空白文字のレンダリング動作

@preservewhitespace ディレクティブが trueの値と共に使用されている場合を除き、既定では、次の場合に余分な空白が削除されます。

  • 要素内の先頭または末尾。
  • RenderFragment/RenderFragment<TValue> パラメーター内の先頭または末尾 (たとえば、別のコンポーネントに渡された子コンテンツ)。
  • @if または @foreach のような、C# コード ブロックの前か後にある。

空白文字の削除は、white-space: pre などの CSS ルールを使用するときに、レンダリングされた出力に影響を与えることがあります。 このパフォーマンスの最適化を無効にして、空白を保持するには、次のいずれかの操作を実行します。

  • Razor ファイル (.razor) の先頭に @preservewhitespace true ディレクティブを追加し、特定のコンポーネントに設定を適用する。
  • _Imports.razor ファイル内に @preservewhitespace true ディレクティブを追加し、サブディレクトリまたはプロジェクト全体に設定を適用する。

ほとんどの場合、アプリでは一般的に通常の動作が続行されるため (ただし、速くなります)、何の措置も必要ありません。 空白文字を削除すると特定のコンポーネントでレンダリングの問題が発生する場合は、そのコンポーネントで @preservewhitespace true を使用し、この最適化を無効にします。

空白文字は、コンポーネントのソース マークアップに保持されます。 空白文字のみのテキストは、視覚効果がないときでも、ブラウザーの DOM にレンダリングされます。

次のコンポーネント マークアップについて考えてみます。

<ul>
    @foreach (var item in Items)
    {
        <li>
            @item.Text
        </li>
    }
</ul>

前の例では、次の不要な空白文字がレンダリングされます。

  • @foreach コード ブロックの外側。
  • <li> 要素の前後。
  • @item.Text 出力の前後。

100 項目のリストの場合、空白文字の領域が 400 を超えます。 レンダリングされる出力に視覚的に影響する余分な空白文字はありません。

コンポーネントの静的 HTML をレンダリングする場合、タグ内の空白文字は保持されません。 たとえば、コンポーネント <img> ファイル (.razor) で次の Razor タグのレンダリングされる出力を表示します。

<img     alt="Example image"   src="img.png"     />

前のマークアップからの空白文字は保持されません。

<img alt="Example image" src="img.png" />