ASP.NET Core のビュー コンポーネント

作成者: Rick Anderson

ビュー コンポーネント

ビュー コンポーネントは部分ビューと似ていますが、はるかに強力なものです。 ビュー コンポーネントはモデル バインドを使用せず、ビュー コンポーネントの呼び出し時に渡されるデータに依存します。 この記事はコントローラーとビューを使用して作成されましたが、ビュー コンポーネントは Razor Pages で利用できます。

ビュー コンポーネントの特徴は次のとおりです。

  • 応答全体ではなく、チャンクをレンダリングします。
  • コントローラーとビューの間にあるのと同じ関心の分離とテストの容易性の利点があります。
  • パラメーターとビジネス ロジックを含めることができます。
  • 通常、レイアウト ページから呼び出されます。

ビュー コンポーネントは、次のような部分ビューには複雑すぎる、再利用可能なレンダリング ロジックを想定しています。

  • 動的なナビゲーション メニュー
  • タグ クラウド (データベースのクエリを実行する場所)
  • サインイン パネル
  • ショッピング カート
  • 新着情報の記事
  • ブログのサイドバーのコンテンツ
  • すべてのページでレンダリングされ、ユーザーのサインイン状態に応じて、サインアウトまたはサインするためのリンクを表示するサインイン パネル

ビュー コンポーネントは次の 2 つの部分で構成されています。

  • クラス (通常は ViewComponent から派生)
  • 返される結果 (通常はビュー)。

コントローラーと同様に、ビュー コンポーネントは POCO の場合がありますが、ほとんどの開発者は ViewComponent から派生させて、利用できるメソッドとプロパティを活用しています。

ビュー コンポーネントが、アプリの仕様を満たしているかどうかを検討する場合は、代わりに Razor コンポーネントを使用することを検討してください。 Razor コンポーネントでも、C# コードとマークアップを組み合わせて、再利用可能な UI ユニットを生成できます。 Razor コンポーネントは、クライアント側で UI ロジックと構成を提供する場合の開発者の生産性のために設計されています。 詳細については、「ASP.NET Core Razor コンポーネント」を参照してください。 MVC または Razor Pages アプリに Razor コンポーネントを組み込む方法については、「ASP.NET Core Razor コンポーネントを ASP.NET Core アプリに統合する」をご覧ください。

ビュー コンポーネントを作成する

このセクションには、ビュー コンポーネントを作成するための高レベルの要件が含まれています。 記事の後半で、各ステップの詳細を検証し、ビュー コンポーネントを作成します。

ビュー コンポーネント クラス

ビュー コンポーネント クラスは、次のいずれかによって作成できます。

  • ViewComponent から派生させる
  • [ViewComponent] 属性でクラスを装飾するか、[ViewComponent] 属性でクラスから派生させる
  • 名前がサフィックス ViewComponent で終わるクラスを作成する

コントローラーと同様に、ビュー コンポーネントは、パブリック クラス、入れ子にされていないクラス、および非抽象クラスである必要があります。 ビュー コンポーネント名は、ViewComponent サフィックスを除いたクラス名です。 また、これは Name プロパティを使用して、明示的に指定することもできます。

ビュー コンポーネント クラスの特徴は次のとおりです。

  • コンストラクターの依存性の注入をサポートします
  • コントローラーのライフサイクルに関与しないため、フィルター はビュー コンポーネントで使用できません

大文字と小文字を区別しない ViewComponent サフィックスを持つクラスがビュー コンポーネントとして扱われないようにするには、[NonViewComponent] 属性を使用してクラスを修飾します。

using Microsoft.AspNetCore.Mvc;

[NonViewComponent]
public class ReviewComponent
{
    public string Status(string name) => JobStatus.GetCurrentStatus(name);
}

ビュー コンポーネント メソッド

ビュー コンポーネントは、そのロジックを次で定義します。

  • Task<IViewComponentResult> を返す InvokeAsync メソッド。
  • IViewComponentResult を返す Invoke 同期メソッド。

パラメーターは、モデル バインドではなく、ビュー コンポーネントから直接取得します。 ビュー コンポーネントが要求を直接処理することはありません。 通常、ビュー コンポーネントは、モデルを初期化し、View メソッドを呼び出してビューに渡します。 要約すると、ビュー コンポーネント メソッドの特徴は次のとおりです。

  • Task<IViewComponentResult> を返す InvokeAsync メソッドまたは IViewComponentResult を返す同期 Invoke メソッドを定義します。
  • 通常、モデルを初期化し、ViewComponent.View メソッドを呼び出してビューに渡します。
  • パラメーターは HTTP ではなく、呼び出し元のメソッドから取得されます。 モデル バインドはありません。
  • HTTP エンドポイントとして直接アクセスすることはできません。 これらは通常、ビューで呼び出されます。 ビュー コンポーネントでは要求が処理されません。
  • 現在の HTTP 要求からの詳細ではなく、シグネチャでオーバーロードされます。

ビューの検索パス

ランタイムでは、次のパスでビューを検索します。

  • /Views/{コントローラー名}/Components/{ビュー コンポーネント名}/{ビュー名}
  • /Views/Shared/Components/{ビュー コンポーネント名}/{ビュー名}
  • /Pages/Shared/Components/{ビュー コンポーネント名}/{ビュー名}
  • /Areas/{Area Name}/Views/Shared/Components/{ビュー コンポーネント名}/{ビュー名}

この検索パスは、コントローラーとビューおよび Razor Pages を使うプロジェクト用です。

ビュー コンポーネントの既定のビュー名は、Default です。つまり、通常、ビュー ファイルは Default.cshtml という名前になるということです。 ビュー コンポーネントの結果を作成したり、View メソッドを呼び出したりするときに、別のビュー名を指定することができます。

ビュー ファイルに Default.cshtml という名前を付けて、"Views/Shared/Components/{ビュー コンポーネント名}/{ビュー名}" というパスを使用することをお勧めします。 このサンプルで使われる PriorityList ビュー コンポーネントでは、ビュー コンポーネント ビューとして Views/Shared/Components/PriorityList/Default.cshtml を使います。

ビューの検索パスをカスタマイズする

ビューの検索パスをカスタマイズするには、Razor の ViewLocationFormats コレクションを変更します。 たとえば、/Components/{View Component Name}/{View Name} というパス内のビューを検索するには、次のように新しい項目をコレクションに追加します。

using Microsoft.EntityFrameworkCore;
using ViewComponentSample.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews()
    .AddRazorOptions(options =>
    {
        options.ViewLocationFormats.Add("/{0}.cshtml");
    });

builder.Services.AddDbContext<ToDoContext>(options =>
        options.UseInMemoryDatabase("db"));

var app = builder.Build();

// Remaining code removed for brevity.

前のコードでは、プレースホルダー {0} はパス Components/{View Component Name}/{View Name} を表します。

ビュー コンポーネントを呼び出す

ビュー コンポーネントを使用するには、ビュー内で以下を呼び出します。

@await Component.InvokeAsync("Name of view component",
                             {Anonymous Type Containing Parameters})

パラメータは InvokeAsync メソッドに渡されます。 この記事で開発された PriorityList ビュー コンポーネントは、Views/ToDo/Index.cshtml ビュー ファイルから呼び出されます。 以下のコードでは、InvokeAsync メソッドは、2 つのパラメーターで呼び出されます。

</table>

<div>
    Maximum Priority: @ViewData["maxPriority"] <br />
    Is Complete:  @ViewData["isDone"]
    @await Component.InvokeAsync("PriorityList",
                     new { 
                         maxPriority =  ViewData["maxPriority"],
                         isDone = ViewData["isDone"]  }
                     )
</div>

タグ ヘルパーとしてビュー コンポーネントを呼び出す

ビュー コンポーネントは、タグ ヘルパーとして呼び出すことができます。

<div>
       Maxium Priority: @ViewData["maxPriority"] <br />
       Is Complete:  @ViewData["isDone"]
    @{
        int maxPriority = Convert.ToInt32(ViewData["maxPriority"]);
        bool isDone = Convert.ToBoolean(ViewData["isDone"]);
    }
    <vc:priority-list max-priority=maxPriority is-done=isDone>
    </vc:priority-list>
</div>

タグ ヘルパーのパスカル ケースのクラスとメソッドのパラメーターは、それぞれケバブ ケースに変換されます。 ビュー コンポーネントを呼び出すタグ ヘルパーでは、<vc></vc> 要素を使用します。 ビュー コンポーネントは、次のように指定されます。

<vc:[view-component-name]
  parameter1="parameter1 value"
  parameter2="parameter2 value">
</vc:[view-component-name]>

ビュー コンポーネントをタグ ヘルパーとして使用するには、@addTagHelper ディレクティブを使用して、ビュー コンポーネントを含むアセンブリを登録します。 ビュー コンポーネントが MyWebApp と呼ばれるアセンブリにある場合は、次のディレクティブを _ViewImports.cshtml ファイルに追加します。

@addTagHelper *, MyWebApp

ビュー コンポーネントは、そのビュー コンポーネントを参照する任意のファイルにタグ ヘルパーとして登録できます。 タグ ヘルパーを登録する方法の詳細については、「Managing Tag Helper Scope」 (タグ ヘルパーのスコープの管理) を参照してください。

このチュートリアルで使用される InvokeAsync メソッドは、次のとおりです。

@await Component.InvokeAsync("PriorityList",
                 new { 
                     maxPriority =  ViewData["maxPriority"],
                     isDone = ViewData["isDone"]  }
                 )

前述のマークアップでは、PriorityList ビュー コンポーネントは priority-list になります。 ビュー コンポーネントに対するパラメーターは、ケバブ ケースの属性として渡されます。

ビュー コンポーネントをコントローラーから直接呼び出す

通常、ビュー コンポーネントはビューから呼び出されますが、コントローラー メソッドから直接呼び出すことができます。 ビュー コンポーネントでコントローラーなどのエンドポイントを定義しないときに、ViewComponentResult のコンテンツを返すコントローラー アクションを実装できます。

次の例では、ビュー コンポーネントは、コントローラーから直接呼び出されます。

public IActionResult IndexVC(int maxPriority = 2, bool isDone = false)
{
    return ViewComponent("PriorityList",
        new { 
           maxPriority = maxPriority,
           isDone = isDone
        });
}

基本的なビュー コンポーネントを作成する

スタート コードをダウンロード、ビルド、およびテストします。 これは、[ToDo] 項目のリストを表示する ToDo コントローラーを備えた、基本的なプロジェクトです。

List of ToDos

優先度と完了状態を渡すようにコントローラーを更新する

優先度および完了状態のパラメーターを使用するように Index メソッドを更新します。

using Microsoft.AspNetCore.Mvc;
using ViewComponentSample.Models;

namespace ViewComponentSample.Controllers;
public class ToDoController : Controller
{
    private readonly ToDoContext _ToDoContext;

    public ToDoController(ToDoContext context)
    {
        _ToDoContext = context;
        _ToDoContext.Database.EnsureCreated();
    }

    public IActionResult Index(int maxPriority = 2, bool isDone = false)
    {
        var model = _ToDoContext!.ToDo!.ToList();
        ViewData["maxPriority"] = maxPriority;
        ViewData["isDone"] = isDone;
        return View(model);
    }

ViewComponent クラスの追加

ViewComponents/PriorityListViewComponent.cs に ViewComponent クラスを追加します。

using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using ViewComponentSample.Models;

namespace ViewComponentSample.ViewComponents;

public class PriorityListViewComponent : ViewComponent
{
    private readonly ToDoContext db;

    public PriorityListViewComponent(ToDoContext context) => db = context;

    public async Task<IViewComponentResult> InvokeAsync(
                                            int maxPriority, bool isDone)
    {
        var items = await GetItemsAsync(maxPriority, isDone);
        return View(items);
    }

    private Task<List<TodoItem>> GetItemsAsync(int maxPriority, bool isDone)
    {
        return db!.ToDo!.Where(x => x.IsDone == isDone &&
                             x.Priority <= maxPriority).ToListAsync();
    }
}

コードに関する注意事項

  • ビュー コンポーネント クラスは、プロジェクト内の任意のフォルダーに含めることができます。

  • クラス名 PriorityListViewComponent は、サフィックス ViewComponent で終わるため、ビューからクラス コンポーネントを参照するときに、ランタイムでは文字列 PriorityList を使用します。

  • [ViewComponent] 属性では、ビュー コンポーネントを参照するために使用する名前を変更できます。 たとえば、次の [ViewComponent] 属性を持つクラスに XYZ という名前を付けることができます。

    [ViewComponent(Name = "PriorityList")]
       public class XYZ : ViewComponent
    
  • 前のコードの [ViewComponent] 属性は、ビュー コンポーネント セレクターに次のものを使用するように指示します。

    • コンポーネントに関連付けられたビューを検索するときの名前 PriorityList
    • ビューからクラス コンポーネントを参照するときの文字列 "PriorityList"。
  • コンポーネントでは、依存性の注入を使用して、データ コンテキストを利用できるようにします。

  • InvokeAsync はビューから呼び出すことができるメソッドを公開しており、任意の数の引数を取ることができます。

  • InvokeAsync メソッドでは、isDonemaxPriority パラメーターを満たす ToDo 項目のセットを返します。

ビュー コンポーネント Razor ビューの作成

  • Views/Shared/Components フォルダーを作成します。 このフォルダーは、Components という名前にする必要があります

  • Views/Shared/Components/PriorityList フォルダーを作成します。 このフォルダー名は、ビュー コンポーネント クラスの名前、またはクラス名からサフィックスを除いた名前と一致する必要があります。 ViewComponent 属性が使用される場合、クラス名は属性の指定に一致する必要があります。

  • Views/Shared/Components/PriorityList/Default.cshtmlRazor ビューを作成します。

    @model IEnumerable<ViewComponentSample.Models.TodoItem>
    
    <h3>Priority Items</h3>
    <ul>
        @foreach (var todo in Model)
        {
            <li>@todo.Name</li>
        }
    </ul>
    

    Razor ビューでは、TodoItem のリストを取得してそれらを表示します。 ビュー コンポーネントの InvokeAsync メソッドでビューの名前を渡さない場合、Default が規則によってビュー名に使用されます。 特定のコントローラーの既定のスタイルをオーバーライドするには、コントローラーに固有のビュー フォルダー (例: Views/ToDo/Components/PriorityList/Default.cshtml) にビューを追加します。

    ビュー コンポーネントがコントローラー固有の場合は、コントローラー固有のフォルダーに追加できます。 たとえば、Views/ToDo/Components/PriorityList/Default.cshtml はコントローラー固有です。

  • 優先順位リスト コンポーネントの呼び出しを含む div を、Views/ToDo/index.cshtml ファイルの末尾に追加します。

    </table>
    
    <div>
        Maximum Priority: @ViewData["maxPriority"] <br />
        Is Complete:  @ViewData["isDone"]
        @await Component.InvokeAsync("PriorityList",
                         new { 
                             maxPriority =  ViewData["maxPriority"],
                             isDone = ViewData["isDone"]  }
                         )
    </div>
    

@await Component.InvokeAsync マークアップは、ビュー コンポーネントを呼び出すための構文を示します。 最初の引数は、呼び出す必要があるコンポーネントの名前です。 後続のパラメーターは、そのコンポーネントに渡されます。 InvokeAsync では、任意の数の引数を取得できます。

アプリをテストします。 次の画像は、[ToDo] リストと優先順位の項目を示します。

todo list and priority items

ビュー コンポーネントは、コントローラーから直接呼び出すことができます。

public IActionResult IndexVC(int maxPriority = 2, bool isDone = false)
{
    return ViewComponent("PriorityList",
        new { 
           maxPriority = maxPriority,
           isDone = isDone
        });
}

priority items from IndexVC action

ビュー コンポーネント名を指定する

複雑なビュー コンポーネントでは、いくつかの条件下で、既定以外のビューを指定する必要がある場合があります。 次のコードでは、InvokeAsync メソッドから "PVC" ビューを指定する方法を示しています。 PriorityListViewComponent クラスで InvokeAsync メソッドを更新します。

public async Task<IViewComponentResult> InvokeAsync(
                                           int maxPriority, bool isDone)
{
    string MyView = "Default";
    // If asking for all completed tasks, render with the "PVC" view.
    if (maxPriority > 3 && isDone == true)
    {
        MyView = "PVC";
    }
    var items = await GetItemsAsync(maxPriority, isDone);
    return View(MyView, items);
}

Views/Shared/Components/PriorityList/Default.cshtml ファイルを Views/Shared/Components/PriorityList/PVC.cshtml という名前のビューにコピーします。 PVC ビューが使用されていることを示すために、見出しを追加します。

@model IEnumerable<ViewComponentSample.Models.TodoItem>

<h2> PVC Named Priority Component View</h2>
<h4>@ViewBag.PriorityMessage</h4>
<ul>
    @foreach (var todo in Model)
    {
        <li>@todo.Name</li>
    }
</ul>

アプリを実行して、PVC ビューを確認します。

Priority View Component

PVC ビューがレンダリングされない場合は、優先度 4 以上のビュー コンポーネントが呼び出されていることを確認します。

ビューのパスを調べる

  • 優先順位ビューが返されないように、優先順位パラメーターを 3 以下に変更します。

  • Views/ToDo/Components/PriorityList/Default.cshtml の名前を一時的に 1Default.cshtml に変更します。

  • アプリをテストすると、次のエラーが発生します。

    An unhandled exception occurred while processing the request.
    InvalidOperationException: The view 'Components/PriorityList/Default' wasn't found. The following locations were searched:
    /Views/ToDo/Components/PriorityList/Default.cshtml
    /Views/Shared/Components/PriorityList/Default.cshtml
    
  • Views/ToDo/Components/PriorityList/1Default.cshtmlViews/Shared/Components/PriorityList/Default.cshtml にコピーします。

  • [Shared] の [ToDo] ビュー コンポーネントのビューにマークアップを追加して、そのビューが [Shared] フォルダーからのものであることを示します。

  • [Shared] コンポーネント ビューをテストします。

ToDo output with Shared component view

ハードコーディングされた文字列を避ける

コンパイル時の安全性を確保するために、ハードコーディングされたビュー コンポーネント名をクラス名に置き換えます。 "ViewComponent" サフィックスを使用しないように PriorityListViewComponent.cs ファイルを更新します。

using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using ViewComponentSample.Models;

namespace ViewComponentSample.ViewComponents;

public class PriorityList : ViewComponent
{
    private readonly ToDoContext db;

    public PriorityList(ToDoContext context)
    {
        db = context;
    }

    public async Task<IViewComponentResult> InvokeAsync(
                                               int maxPriority, bool isDone)
    {
        var items = await GetItemsAsync(maxPriority, isDone);
        return View(items);
    }

    private Task<List<TodoItem>> GetItemsAsync(int maxPriority, bool isDone)
    {
        return db!.ToDo!.Where(x => x.IsDone == isDone &&
                             x.Priority <= maxPriority).ToListAsync();
    }
}

ビュー ファイル:

</table>

<div>
    Testing nameof(PriorityList) <br />

    Maxium Priority: @ViewData["maxPriority"] <br />
    Is Complete:  @ViewData["isDone"]
    @await Component.InvokeAsync(nameof(PriorityList),
                     new { 
                         maxPriority =  ViewData["maxPriority"],
                         isDone = ViewData["isDone"]  }
                     )
</div>

CLR 型を受け取る Component.InvokeAsync メソッドのオーバーロードでは、typeof 演算子を使用します。

</table>

<div>
    Testing typeof(PriorityList) <br />

    Maxium Priority: @ViewData["maxPriority"] <br />
    Is Complete:  @ViewData["isDone"]
    @await Component.InvokeAsync(typeof(PriorityList),
                     new { 
                         maxPriority =  ViewData["maxPriority"],
                         isDone = ViewData["isDone"]  }
                     )
</div>

同期作業を実行する

非同期処理が不要な場合、フレームワークで同期 Invoke メソッドの呼び出しが処理されます。 次のメソッドでは同期 Invoke ビュー コンポーネントを作成します。

using Microsoft.AspNetCore.Mvc;
using ViewComponentSample.Models;

namespace ViewComponentSample.ViewComponents
{
    public class PriorityListSync : ViewComponent
    {
        private readonly ToDoContext db;

        public PriorityListSync(ToDoContext context)
        {
            db = context;
        }

        public IViewComponentResult Invoke(int maxPriority, bool isDone)
        {
 
            var x = db!.ToDo!.Where(x => x.IsDone == isDone &&
                                  x.Priority <= maxPriority).ToList();
            return View(x);
        }
    }
}

ビュー コンポーネントの Razor ファイル:

<div>
    Testing nameof(PriorityList) <br />

    Maxium Priority: @ViewData["maxPriority"] <br />
    Is Complete:  @ViewData["isDone"]
    @await Component.InvokeAsync(nameof(PriorityListSync),
                     new { 
                         maxPriority =  ViewData["maxPriority"],
                         isDone = ViewData["isDone"]  }
                     )
</div>

ビュー コンポーネントは、次のいずれかの方法を使用して、Razor ファイル (Views/Home/Index.cshtml など) 内で呼び出されます。

IViewComponentHelper の方法を使用するには、Component.InvokeAsync を呼び出します。

@await Component.InvokeAsync(nameof(PriorityList),
                             new { maxPriority = 4, isDone = true })

タグ ヘルパーを使用するには、@addTagHelper ディレクティブを使用して、ビュー コンポーネントを含むアセンブリを登録します (ビュー コンポーネントは、MyWebApp と呼ばれるアセンブリ内にあります)。

@addTagHelper *, MyWebApp

Razor マークアップ ファイルでビュー コンポーネントのタグ ヘルパーを使用します。

<vc:priority-list max-priority="999" is-done="false">
</vc:priority-list>

PriorityList.Invoke のメソッド署名は同期的ですが、Razor ではマークアップ ファイルで Component.InvokeAsync を使用してメソッドを見つけて呼び出します。

その他のリソース

ビュー コンポーネント

ビュー コンポーネントは部分ビューと似ていますが、はるかに強力なものです。 ビュー コンポーネントはモデル バインドを使用せず、ビュー コンポーネントの呼び出し時に渡されるデータに依存します。 この記事はコントローラーとビューを使用して作成されましたが、ビュー コンポーネントは Razor Pages で利用できます。

ビュー コンポーネントの特徴は次のとおりです。

  • 応答全体ではなく、チャンクをレンダリングします。
  • コントローラーとビューの間にあるのと同じ関心の分離とテストの容易性の利点があります。
  • パラメーターとビジネス ロジックを含めることができます。
  • 通常、レイアウト ページから呼び出されます。

ビュー コンポーネントは、次のような部分ビューには複雑すぎる、再利用可能なレンダリング ロジックを想定しています。

  • 動的なナビゲーション メニュー
  • タグ クラウド (データベースのクエリを実行する場所)
  • サインイン パネル
  • ショッピング カート
  • 新着情報の記事
  • ブログのサイドバーのコンテンツ
  • すべてのページでレンダリングされ、ユーザーのサインイン状態に応じて、サインアウトまたはサインするためのリンクを表示するサインイン パネル

ビュー コンポーネントは次の 2 つの部分で構成されています。

  • クラス (通常は ViewComponent から派生)
  • 返される結果 (通常はビュー)。

コントローラーと同様に、ビュー コンポーネントは POCO の場合がありますが、ほとんどの開発者は ViewComponent から派生させて、利用できるメソッドとプロパティを活用しています。

ビュー コンポーネントが、アプリの仕様を満たしているかどうかを検討する場合は、代わりに Razor コンポーネントを使用することを検討してください。 Razor コンポーネントでも、C# コードとマークアップを組み合わせて、再利用可能な UI ユニットを生成できます。 Razor コンポーネントは、クライアント側で UI ロジックと構成を提供する場合の開発者の生産性のために設計されています。 詳細については、「ASP.NET Core Razor コンポーネント」を参照してください。 MVC または Razor Pages アプリに Razor コンポーネントを組み込む方法については、「ASP.NET Core Razor コンポーネントのプリレンダリングと統合を行う」を参照してください。

ビュー コンポーネントを作成する

このセクションには、ビュー コンポーネントを作成するための高レベルの要件が含まれています。 記事の後半で、各ステップの詳細を検証し、ビュー コンポーネントを作成します。

ビュー コンポーネント クラス

ビュー コンポーネント クラスは、次のいずれかによって作成できます。

  • ViewComponent から派生させる
  • [ViewComponent] 属性でクラスを装飾するか、[ViewComponent] 属性でクラスから派生させる
  • 名前がサフィックス ViewComponent で終わるクラスを作成する

コントローラーと同様に、ビュー コンポーネントは、パブリック クラス、入れ子にされていないクラス、および非抽象クラスである必要があります。 ビュー コンポーネント名は、ViewComponent サフィックスを除いたクラス名です。 また、これは Name プロパティを使用して、明示的に指定することもできます。

ビュー コンポーネント クラスの特徴は次のとおりです。

  • コンストラクターの依存性の注入をサポートします
  • コントローラーのライフサイクルに関与しないため、フィルター はビュー コンポーネントで使用できません

大文字と小文字を区別しない ViewComponent サフィックスを持つクラスがビュー コンポーネントとして扱われないようにするには、[NonViewComponent] 属性を使用してクラスを修飾します。

using Microsoft.AspNetCore.Mvc;

[NonViewComponent]
public class ReviewComponent
{
    public string Status(string name) => JobStatus.GetCurrentStatus(name);
}

ビュー コンポーネント メソッド

ビュー コンポーネントは、そのロジックを次で定義します。

  • Task<IViewComponentResult> を返す InvokeAsync メソッド。
  • IViewComponentResult を返す Invoke 同期メソッド。

パラメーターは、モデル バインドではなく、ビュー コンポーネントから直接取得します。 ビュー コンポーネントが要求を直接処理することはありません。 通常、ビュー コンポーネントは、モデルを初期化し、View メソッドを呼び出してビューに渡します。 要約すると、ビュー コンポーネント メソッドの特徴は次のとおりです。

  • Task<IViewComponentResult> を返す InvokeAsync メソッドまたは IViewComponentResult を返す同期 Invoke メソッドを定義します。
  • 通常、モデルを初期化し、ViewComponent.View メソッドを呼び出してビューに渡します。
  • パラメーターは HTTP ではなく、呼び出し元のメソッドから取得されます。 モデル バインドはありません。
  • HTTP エンドポイントとして直接アクセスすることはできません。 これらは通常、ビューで呼び出されます。 ビュー コンポーネントでは要求が処理されません。
  • 現在の HTTP 要求からの詳細ではなく、シグネチャでオーバーロードされます。

ビューの検索パス

ランタイムでは、次のパスでビューを検索します。

  • /Views/{コントローラー名}/Components/{ビュー コンポーネント名}/{ビュー名}
  • /Views/Shared/Components/{ビュー コンポーネント名}/{ビュー名}
  • /Pages/Shared/Components/{ビュー コンポーネント名}/{ビュー名}
  • /Areas/{Area Name}/Views/Shared/Components/{ビュー コンポーネント名}/{ビュー名}

この検索パスは、コントローラーとビューおよび Razor Pages を使うプロジェクト用です。

ビュー コンポーネントの既定のビュー名は、Default です。つまり、通常、ビュー ファイルは Default.cshtml という名前になるということです。 ビュー コンポーネントの結果を作成したり、View メソッドを呼び出したりするときに、別のビュー名を指定することができます。

ビュー ファイルに Default.cshtml という名前を付けて、"Views/Shared/Components/{ビュー コンポーネント名}/{ビュー名}" というパスを使用することをお勧めします。 このサンプルで使われる PriorityList ビュー コンポーネントでは、ビュー コンポーネント ビューとして Views/Shared/Components/PriorityList/Default.cshtml を使います。

ビューの検索パスをカスタマイズする

ビューの検索パスをカスタマイズするには、Razor の ViewLocationFormats コレクションを変更します。 たとえば、/Components/{View Component Name}/{View Name} というパス内のビューを検索するには、次のように新しい項目をコレクションに追加します。

using Microsoft.EntityFrameworkCore;
using ViewComponentSample.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews()
    .AddRazorOptions(options =>
    {
        options.ViewLocationFormats.Add("/{0}.cshtml");
    });

builder.Services.AddDbContext<ToDoContext>(options =>
        options.UseInMemoryDatabase("db"));

var app = builder.Build();

// Remaining code removed for brevity.

前のコードでは、プレースホルダー {0} はパス Components/{View Component Name}/{View Name} を表します。

ビュー コンポーネントを呼び出す

ビュー コンポーネントを使用するには、ビュー内で以下を呼び出します。

@await Component.InvokeAsync("Name of view component",
                             {Anonymous Type Containing Parameters})

パラメータは InvokeAsync メソッドに渡されます。 この記事で開発された PriorityList ビュー コンポーネントは、Views/ToDo/Index.cshtml ビュー ファイルから呼び出されます。 以下のコードでは、InvokeAsync メソッドは、2 つのパラメーターで呼び出されます。

</table>

<div>
    Maximum Priority: @ViewData["maxPriority"] <br />
    Is Complete:  @ViewData["isDone"]
    @await Component.InvokeAsync("PriorityList",
                     new { 
                         maxPriority =  ViewData["maxPriority"],
                         isDone = ViewData["isDone"]  }
                     )
</div>

タグ ヘルパーとしてビュー コンポーネントを呼び出す

ビュー コンポーネントは、タグ ヘルパーとして呼び出すことができます。

<div>
       Maxium Priority: @ViewData["maxPriority"] <br />
       Is Complete:  @ViewData["isDone"]
    @{
        int maxPriority = Convert.ToInt32(ViewData["maxPriority"]);
        bool isDone = Convert.ToBoolean(ViewData["isDone"]);
    }
    <vc:priority-list max-priority=maxPriority is-done=isDone>
    </vc:priority-list>
</div>

タグ ヘルパーのパスカル ケースのクラスとメソッドのパラメーターは、それぞれケバブ ケースに変換されます。 ビュー コンポーネントを呼び出すタグ ヘルパーでは、<vc></vc> 要素を使用します。 ビュー コンポーネントは、次のように指定されます。

<vc:[view-component-name]
  parameter1="parameter1 value"
  parameter2="parameter2 value">
</vc:[view-component-name]>

ビュー コンポーネントをタグ ヘルパーとして使用するには、@addTagHelper ディレクティブを使用して、ビュー コンポーネントを含むアセンブリを登録します。 ビュー コンポーネントが MyWebApp と呼ばれるアセンブリにある場合は、次のディレクティブを _ViewImports.cshtml ファイルに追加します。

@addTagHelper *, MyWebApp

ビュー コンポーネントは、そのビュー コンポーネントを参照する任意のファイルにタグ ヘルパーとして登録できます。 タグ ヘルパーを登録する方法の詳細については、「Managing Tag Helper Scope」 (タグ ヘルパーのスコープの管理) を参照してください。

このチュートリアルで使用される InvokeAsync メソッドは、次のとおりです。

@await Component.InvokeAsync("PriorityList",
                 new { 
                     maxPriority =  ViewData["maxPriority"],
                     isDone = ViewData["isDone"]  }
                 )

前述のマークアップでは、PriorityList ビュー コンポーネントは priority-list になります。 ビュー コンポーネントに対するパラメーターは、ケバブ ケースの属性として渡されます。

ビュー コンポーネントをコントローラーから直接呼び出す

通常、ビュー コンポーネントはビューから呼び出されますが、コントローラー メソッドから直接呼び出すことができます。 ビュー コンポーネントでコントローラーなどのエンドポイントを定義しないときに、ViewComponentResult のコンテンツを返すコントローラー アクションを実装できます。

次の例では、ビュー コンポーネントは、コントローラーから直接呼び出されます。

public IActionResult IndexVC(int maxPriority = 2, bool isDone = false)
{
    return ViewComponent("PriorityList",
        new { 
           maxPriority = maxPriority,
           isDone = isDone
        });
}

基本的なビュー コンポーネントを作成する

スタート コードをダウンロード、ビルド、およびテストします。 これは、[ToDo] 項目のリストを表示する ToDo コントローラーを備えた、基本的なプロジェクトです。

List of ToDos

優先度と完了状態を渡すようにコントローラーを更新する

優先度および完了状態のパラメーターを使用するように Index メソッドを更新します。

using Microsoft.AspNetCore.Mvc;
using ViewComponentSample.Models;

namespace ViewComponentSample.Controllers;
public class ToDoController : Controller
{
    private readonly ToDoContext _ToDoContext;

    public ToDoController(ToDoContext context)
    {
        _ToDoContext = context;
        _ToDoContext.Database.EnsureCreated();
    }

    public IActionResult Index(int maxPriority = 2, bool isDone = false)
    {
        var model = _ToDoContext!.ToDo!.ToList();
        ViewData["maxPriority"] = maxPriority;
        ViewData["isDone"] = isDone;
        return View(model);
    }

ViewComponent クラスの追加

ViewComponents/PriorityListViewComponent.cs に ViewComponent クラスを追加します。

using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using ViewComponentSample.Models;

namespace ViewComponentSample.ViewComponents;

public class PriorityListViewComponent : ViewComponent
{
    private readonly ToDoContext db;

    public PriorityListViewComponent(ToDoContext context) => db = context;

    public async Task<IViewComponentResult> InvokeAsync(
                                            int maxPriority, bool isDone)
    {
        var items = await GetItemsAsync(maxPriority, isDone);
        return View(items);
    }

    private Task<List<TodoItem>> GetItemsAsync(int maxPriority, bool isDone)
    {
        return db!.ToDo!.Where(x => x.IsDone == isDone &&
                             x.Priority <= maxPriority).ToListAsync();
    }
}

コードに関する注意事項

  • ビュー コンポーネント クラスは、プロジェクト内の任意のフォルダーに含めることができます。

  • クラス名 PriorityListViewComponent は、サフィックス ViewComponent で終わるため、ビューからクラス コンポーネントを参照するときに、ランタイムでは文字列 PriorityList を使用します。

  • [ViewComponent] 属性では、ビュー コンポーネントを参照するために使用する名前を変更できます。 たとえば、次の [ViewComponent] 属性を持つクラスに XYZ という名前を付けることができます。

    [ViewComponent(Name = "PriorityList")]
       public class XYZ : ViewComponent
    
  • 前のコードの [ViewComponent] 属性は、ビュー コンポーネント セレクターに次のものを使用するように指示します。

    • コンポーネントに関連付けられたビューを検索するときの名前 PriorityList
    • ビューからクラス コンポーネントを参照するときの文字列 "PriorityList"。
  • コンポーネントでは、依存性の注入を使用して、データ コンテキストを利用できるようにします。

  • InvokeAsync はビューから呼び出すことができるメソッドを公開しており、任意の数の引数を取ることができます。

  • InvokeAsync メソッドでは、isDonemaxPriority パラメーターを満たす ToDo 項目のセットを返します。

ビュー コンポーネント Razor ビューの作成

  • Views/Shared/Components フォルダーを作成します。 このフォルダーは、Components という名前にする必要があります

  • Views/Shared/Components/PriorityList フォルダーを作成します。 このフォルダー名は、ビュー コンポーネント クラスの名前、またはクラス名からサフィックスを除いた名前と一致する必要があります。 ViewComponent 属性が使用される場合、クラス名は属性の指定に一致する必要があります。

  • Views/Shared/Components/PriorityList/Default.cshtmlRazor ビューを作成します。

    @model IEnumerable<ViewComponentSample.Models.TodoItem>
    
    <h3>Priority Items</h3>
    <ul>
        @foreach (var todo in Model)
        {
            <li>@todo.Name</li>
        }
    </ul>
    

    Razor ビューでは、TodoItem のリストを取得してそれらを表示します。 ビュー コンポーネントの InvokeAsync メソッドでビューの名前を渡さない場合、Default が規則によってビュー名に使用されます。 特定のコントローラーの既定のスタイルをオーバーライドするには、コントローラーに固有のビュー フォルダー (例: Views/ToDo/Components/PriorityList/Default.cshtml) にビューを追加します。

    ビュー コンポーネントがコントローラー固有の場合は、コントローラー固有のフォルダーに追加できます。 たとえば、Views/ToDo/Components/PriorityList/Default.cshtml はコントローラー固有です。

  • 優先順位リスト コンポーネントの呼び出しを含む div を、Views/ToDo/index.cshtml ファイルの末尾に追加します。

    </table>
    
    <div>
        Maximum Priority: @ViewData["maxPriority"] <br />
        Is Complete:  @ViewData["isDone"]
        @await Component.InvokeAsync("PriorityList",
                         new { 
                             maxPriority =  ViewData["maxPriority"],
                             isDone = ViewData["isDone"]  }
                         )
    </div>
    

@await Component.InvokeAsync マークアップは、ビュー コンポーネントを呼び出すための構文を示します。 最初の引数は、呼び出す必要があるコンポーネントの名前です。 後続のパラメーターは、そのコンポーネントに渡されます。 InvokeAsync では、任意の数の引数を取得できます。

アプリをテストします。 次の画像は、[ToDo] リストと優先順位の項目を示します。

todo list and priority items

ビュー コンポーネントは、コントローラーから直接呼び出すことができます。

public IActionResult IndexVC(int maxPriority = 2, bool isDone = false)
{
    return ViewComponent("PriorityList",
        new { 
           maxPriority = maxPriority,
           isDone = isDone
        });
}

priority items from IndexVC action

ビュー コンポーネント名を指定する

複雑なビュー コンポーネントでは、いくつかの条件下で、既定以外のビューを指定する必要がある場合があります。 次のコードでは、InvokeAsync メソッドから "PVC" ビューを指定する方法を示しています。 PriorityListViewComponent クラスで InvokeAsync メソッドを更新します。

public async Task<IViewComponentResult> InvokeAsync(
                                           int maxPriority, bool isDone)
{
    string MyView = "Default";
    // If asking for all completed tasks, render with the "PVC" view.
    if (maxPriority > 3 && isDone == true)
    {
        MyView = "PVC";
    }
    var items = await GetItemsAsync(maxPriority, isDone);
    return View(MyView, items);
}

Views/Shared/Components/PriorityList/Default.cshtml ファイルを Views/Shared/Components/PriorityList/PVC.cshtml という名前のビューにコピーします。 PVC ビューが使用されていることを示すために、見出しを追加します。

@model IEnumerable<ViewComponentSample.Models.TodoItem>

<h2> PVC Named Priority Component View</h2>
<h4>@ViewBag.PriorityMessage</h4>
<ul>
    @foreach (var todo in Model)
    {
        <li>@todo.Name</li>
    }
</ul>

アプリを実行して、PVC ビューを確認します。

Priority View Component

PVC ビューがレンダリングされない場合は、優先度 4 以上のビュー コンポーネントが呼び出されていることを確認します。

ビューのパスを調べる

  • 優先順位ビューが返されないように、優先順位パラメーターを 3 以下に変更します。

  • Views/ToDo/Components/PriorityList/Default.cshtml の名前を一時的に 1Default.cshtml に変更します。

  • アプリをテストすると、次のエラーが発生します。

    An unhandled exception occurred while processing the request.
    InvalidOperationException: The view 'Components/PriorityList/Default' wasn't found. The following locations were searched:
    /Views/ToDo/Components/PriorityList/Default.cshtml
    /Views/Shared/Components/PriorityList/Default.cshtml
    
  • Views/ToDo/Components/PriorityList/1Default.cshtmlViews/Shared/Components/PriorityList/Default.cshtml にコピーします。

  • [Shared] の [ToDo] ビュー コンポーネントのビューにマークアップを追加して、そのビューが [Shared] フォルダーからのものであることを示します。

  • [Shared] コンポーネント ビューをテストします。

ToDo output with Shared component view

ハードコーディングされた文字列を避ける

コンパイル時の安全性を確保するために、ハードコーディングされたビュー コンポーネント名をクラス名に置き換えます。 "ViewComponent" サフィックスを使用しないように PriorityListViewComponent.cs ファイルを更新します。

using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using ViewComponentSample.Models;

namespace ViewComponentSample.ViewComponents;

public class PriorityList : ViewComponent
{
    private readonly ToDoContext db;

    public PriorityList(ToDoContext context)
    {
        db = context;
    }

    public async Task<IViewComponentResult> InvokeAsync(
                                               int maxPriority, bool isDone)
    {
        var items = await GetItemsAsync(maxPriority, isDone);
        return View(items);
    }

    private Task<List<TodoItem>> GetItemsAsync(int maxPriority, bool isDone)
    {
        return db!.ToDo!.Where(x => x.IsDone == isDone &&
                             x.Priority <= maxPriority).ToListAsync();
    }
}

ビュー ファイル:

</table>

<div>
    Testing nameof(PriorityList) <br />

    Maxium Priority: @ViewData["maxPriority"] <br />
    Is Complete:  @ViewData["isDone"]
    @await Component.InvokeAsync(nameof(PriorityList),
                     new { 
                         maxPriority =  ViewData["maxPriority"],
                         isDone = ViewData["isDone"]  }
                     )
</div>

CLR 型を受け取る Component.InvokeAsync メソッドのオーバーロードでは、typeof 演算子を使用します。

</table>

<div>
    Testing typeof(PriorityList) <br />

    Maxium Priority: @ViewData["maxPriority"] <br />
    Is Complete:  @ViewData["isDone"]
    @await Component.InvokeAsync(typeof(PriorityList),
                     new { 
                         maxPriority =  ViewData["maxPriority"],
                         isDone = ViewData["isDone"]  }
                     )
</div>

同期作業を実行する

非同期処理が不要な場合、フレームワークで同期 Invoke メソッドの呼び出しが処理されます。 次のメソッドでは同期 Invoke ビュー コンポーネントを作成します。

using Microsoft.AspNetCore.Mvc;
using ViewComponentSample.Models;

namespace ViewComponentSample.ViewComponents
{
    public class PriorityListSync : ViewComponent
    {
        private readonly ToDoContext db;

        public PriorityListSync(ToDoContext context)
        {
            db = context;
        }

        public IViewComponentResult Invoke(int maxPriority, bool isDone)
        {
 
            var x = db!.ToDo!.Where(x => x.IsDone == isDone &&
                                  x.Priority <= maxPriority).ToList();
            return View(x);
        }
    }
}

ビュー コンポーネントの Razor ファイル:

<div>
    Testing nameof(PriorityList) <br />

    Maxium Priority: @ViewData["maxPriority"] <br />
    Is Complete:  @ViewData["isDone"]
    @await Component.InvokeAsync(nameof(PriorityListSync),
                     new { 
                         maxPriority =  ViewData["maxPriority"],
                         isDone = ViewData["isDone"]  }
                     )
</div>

ビュー コンポーネントは、次のいずれかの方法を使用して、Razor ファイル (Views/Home/Index.cshtml など) 内で呼び出されます。

IViewComponentHelper の方法を使用するには、Component.InvokeAsync を呼び出します。

@await Component.InvokeAsync(nameof(PriorityList),
                             new { maxPriority = 4, isDone = true })

タグ ヘルパーを使用するには、@addTagHelper ディレクティブを使用して、ビュー コンポーネントを含むアセンブリを登録します (ビュー コンポーネントは、MyWebApp と呼ばれるアセンブリ内にあります)。

@addTagHelper *, MyWebApp

Razor マークアップ ファイルでビュー コンポーネントのタグ ヘルパーを使用します。

<vc:priority-list max-priority="999" is-done="false">
</vc:priority-list>

PriorityList.Invoke のメソッド署名は同期的ですが、Razor ではマークアップ ファイルで Component.InvokeAsync を使用してメソッドを見つけて呼び出します。

その他のリソース

サンプル コードを表示またはダウンロードします (ダウンロード方法)。

ビュー コンポーネント

ビュー コンポーネントは部分ビューと似ていますが、はるかに強力なものです。 ビュー コンポーネントでは、モデル バインドを使用せず、呼び出すときに指定されたデータのみに依存します。 この記事はコントローラーとビューを使用して作成されましたが、ビュー コンポーネントは Razor Pages でも利用できます。

ビュー コンポーネントの特徴は次のとおりです。

  • 応答全体ではなく、チャンクをレンダリングします。
  • コントローラーとビューの間にあるのと同じ関心の分離とテストの容易性の利点があります。
  • パラメーターとビジネス ロジックを含めることができます。
  • 通常、レイアウト ページから呼び出されます。

ビュー コンポーネントは、次のような部分ビューには複雑すぎる、再利用可能なレンダリング ロジックをどこでも使用できるようにするためのものです。

  • 動的なナビゲーション メニュー
  • タグ クラウド (データベースをクエリする場所)
  • ログイン パネル
  • ショッピング カート
  • 新着情報の記事
  • 一般的なブログのサイドバーのコンテンツ
  • すべてのページでレンダリングされ、ユーザーの状態のログに応じて、ログアウトまたはログインのいずれかのリンクを示すログイン パネル

ビュー コンポーネントは、2 つの部分で構成されます: クラス (通常、ViewComponent から派生) と、クラスから返される結果 (通常はビュー)。 コントローラーと同様に、ビュー コンポーネントは POCO の場合がありますが、ほとんどの開発者は ViewComponent から派生させて、利用できるメソッドとプロパティを活用する必要があります。

ビュー コンポーネントが、アプリの仕様を満たしているかどうかを検討する場合は、代わりに Razor コンポーネントを使用することを検討してください。 Razor コンポーネントでも、C# コードとマークアップを組み合わせて、再利用可能な UI ユニットを生成できます。 Razor コンポーネントは、クライアント側で UI ロジックと構成を提供する場合の開発者の生産性のために設計されています。 詳細については、「ASP.NET Core Razor コンポーネント」を参照してください。 MVC または Razor Pages アプリに Razor コンポーネントを組み込む方法については、「ASP.NET Core Razor コンポーネントのプリレンダリングと統合を行う」を参照してください。

ビューのコンポーネントを作成する

このセクションには、ビュー コンポーネントを作成するための高レベルの要件が含まれています。 記事の後半で、各ステップの詳細を検証し、ビュー コンポーネントを作成します。

ビュー コンポーネント クラス

ビュー コンポーネント クラスは、次のいずれかによって作成できます。

  • ViewComponent から派生させる
  • [ViewComponent] 属性でクラスを装飾するか、[ViewComponent] 属性でクラスから派生させる
  • 名前がサフィックス ViewComponent で終わるクラスを作成する

コントローラーと同様に、ビュー コンポーネントは、パブリック クラス、入れ子にされていないクラス、および非抽象クラスである必要があります。 ビュー コンポーネント名は、"ViewComponent" サフィックスを除いたクラス名です。 また、これは ViewComponentAttribute.Name プロパティを使用して、明示的に指定することもできます。

ビュー コンポーネント クラスの特徴は次のとおりです。

  • コンストラクターの依存性の注入を完全にサポートします
  • コントローラーのライフサイクルに関わりません。つまり、ビュー コンポーネントでフィルターを使用できないということです

大文字と小文字を区別しない ViewComponent サフィックスを持つクラスがビュー コンポーネントとして扱われないようにするには、クラスを [NonViewComponent] 属性で装飾します。

[NonViewComponent]
public class ReviewComponent
{
    // ...

ビュー コンポーネント メソッド

ビュー コンポーネントでは、Task<IViewComponentResult> を返す InvokeAsync メソッドまたは IViewComponentResult を返す同期 Invoke メソッドでロジックを定義します。 パラメーターは、モデル バインドではなく、ビュー コンポーネントから直接取得します。 ビュー コンポーネントが要求を直接処理することはありません。 通常、ビュー コンポーネントは、モデルを初期化し、View メソッドを呼び出してビューに渡します。 要約すると、ビュー コンポーネント メソッドの特徴は次のとおりです。

  • Task<IViewComponentResult> を返す InvokeAsync メソッドまたは IViewComponentResult を返す同期 Invoke メソッドを定義します。
  • 通常、モデルを初期化し、ViewComponentView メソッドを呼び出してビューに渡します。
  • パラメーターは HTTP ではなく、呼び出し元のメソッドから取得されます。 モデル バインドはありません。
  • HTTP エンドポイントとして直接到達することはできません。 (通常はビュー内で) コードから呼び出されます。 ビュー コンポーネントでは要求が処理されません。
  • 現在の HTTP 要求からの詳細ではなく、シグネチャでオーバーロードされます。

ビューの検索パス

ランタイムでは、次のパスでビューを検索します。

  • /Views/{コントローラー名}/Components/{ビュー コンポーネント名}/{ビュー名}
  • /Views/Shared/Components/{ビュー コンポーネント名}/{ビュー名}
  • /Pages/Shared/Components/{ビュー コンポーネント名}/{ビュー名}
  • /Areas/{Area Name}/Views/Shared/Components/{ビュー コンポーネント名}/{ビュー名}

この検索パスは、コントローラーとビューおよび Razor Pages を使うプロジェクト用です。

ビュー コンポーネントの既定のビュー名は Default です。つまり、通常、ビュー ファイルの名前は Default.cshtml になります。 ビュー コンポーネントの結果を作成したり、View メソッドを呼び出したりするときに、別のビュー名を指定することができます。

ビュー ファイルの名前を Default.cshtml にして、"Views/Shared/Components/<ビュー コンポーネント名="">/<ビュー名>" というパスを使うことをお勧めします。 このサンプルで使われる PriorityList ビュー コンポーネントでは、ビュー コンポーネント ビューとして Views/Shared/Components/PriorityList/Default.cshtml を使います。

ビューの検索パスをカスタマイズする

ビューの検索パスをカスタマイズするには、Razor の ViewLocationFormats コレクションを変更します。 たとえば、"/Components/{ビュー コンポーネント名}/{ビュー名}" というパス内のビューを検索するには、次のように新しい項目をコレクションに追加します。

services.AddMvc()
    .AddRazorOptions(options =>
    {
        options.ViewLocationFormats.Add("/{0}.cshtml");
    })
    .SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

上記のコードでは、プレースホルダー "{0}" はパス "/Components/{ビュー コンポーネント名}/{ビュー名}" を表しています。

ビュー コンポーネントを呼び出す

ビュー コンポーネントを使用するには、ビュー内で以下を呼び出します。

@await Component.InvokeAsync("Name of view component", {Anonymous Type Containing Parameters})

パラメーターは、InvokeAsync メソッドに渡されます。 この記事で開発された PriorityList ビュー コンポーネントは、Views/ToDo/Index.cshtml ビュー ファイルから呼び出されます。 以下では、InvokeAsync メソッドは、2 つのパラメーターで呼び出されます。

@await Component.InvokeAsync("PriorityList", new { maxPriority = 4, isDone = true })

タグ ヘルパーとしてビュー コンポーネントを呼び出す

ASP.NET Core 1.1 以降の場合は、タグ ヘルパーとしてビュー コンポーネントを呼び出すことができます。

<vc:priority-list max-priority="2" is-done="false">
</vc:priority-list>

タグ ヘルパーのパスカル ケースのクラスとメソッドのパラメーターは、それぞれケバブ ケースに変換されます。 ビュー コンポーネントを呼び出すタグ ヘルパーでは、<vc></vc> 要素を使用します。 ビュー コンポーネントは、次のように指定されます。

<vc:[view-component-name]
  parameter1="parameter1 value"
  parameter2="parameter2 value">
</vc:[view-component-name]>

ビュー コンポーネントをタグ ヘルパーとして使用するには、@addTagHelper ディレクティブを使用して、ビュー コンポーネントを含むアセンブリを登録します。 ビュー コンポーネントが MyWebApp という名前のアセンブリにある場合は、次のディレクティブを _ViewImports.cshtml ファイルに追加します。

@addTagHelper *, MyWebApp

ビュー コンポーネントを参照する任意のファイルへのタグ ヘルパーとして、ビュー コンポーネントを登録できます。 タグ ヘルパーを登録する方法の詳細については、「Managing Tag Helper Scope」 (タグ ヘルパーのスコープの管理) を参照してください。

このチュートリアルで使用される InvokeAsync メソッドは、次のとおりです。

@await Component.InvokeAsync("PriorityList", new { maxPriority = 4, isDone = true })

タグ ヘルパーのマークアップでは、次のようになります。

<vc:priority-list max-priority="2" is-done="false">
</vc:priority-list>

上記のサンプルでは、PriorityList ビュー コンポーネントは priority-list になります。 ビュー コンポーネントに対するパラメーターは、ケバブ ケースの属性として渡されます。

ビュー コンポーネントをコントローラーから直接呼び出す

通常、ビュー コンポーネントはビューから呼び出されますが、コントローラー メソッドから直接呼び出すことができます。 ビュー コンポーネントでコントローラーなどのエンドポイントを定義しないときに、ViewComponentResult のコンテンツを返すコントローラー アクションを簡単に実装できます。

この例では、ビュー コンポーネントは、コントローラーから直接呼び出されます。

public IActionResult IndexVC()
{
    return ViewComponent("PriorityList", new { maxPriority = 3, isDone = false });
}

チュートリアル: 単純なビュー コンポーネントの作成

スタート コードをダウンロード、ビルド、およびテストします。 これは、[ToDo] 項目のリストを表示する ToDo コントローラーを備えた、単純なプロジェクトです。

List of ToDos

ViewComponent クラスの追加

ViewComponents フォルダーを作成して、次の PriorityListViewComponent クラスを追加します。

using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using ViewComponentSample.Models;

namespace ViewComponentSample.ViewComponents
{
    public class PriorityListViewComponent : ViewComponent
    {
        private readonly ToDoContext db;

        public PriorityListViewComponent(ToDoContext context)
        {
            db = context;
        }

        public async Task<IViewComponentResult> InvokeAsync(
        int maxPriority, bool isDone)
        {
            var items = await GetItemsAsync(maxPriority, isDone);
            return View(items);
        }
        private Task<List<TodoItem>> GetItemsAsync(int maxPriority, bool isDone)
        {
            return db.ToDo.Where(x => x.IsDone == isDone &&
                                 x.Priority <= maxPriority).ToListAsync();
        }
    }
}

コードに関する注意事項

  • ビュー コンポーネント クラスは、プロジェクト内の任意のフォルダーに含めることができます。

  • クラス名 PriorityListViewComponent は、サフィックス ViewComponent で終わるため、ビューからクラス コンポーネントを参照するときに、ランタイムでは文字列 PriorityList を使用します。

  • [ViewComponent] 属性では、ビュー コンポーネントを参照するために使用する名前を変更できます。 たとえば、ViewComponent 属性を持つクラスに XYZ という名前を付けることができます。

    [ViewComponent(Name = "PriorityList")]
       public class XYZ : ViewComponent
    
  • 前のコードの [ViewComponent] 属性は、ビュー コンポーネント セレクターに次のものを使用するように指示します。

    • コンポーネントに関連付けられたビューを検索するときの名前 PriorityList
    • ビューからクラス コンポーネントを参照するときの文字列 "PriorityList"。
  • コンポーネントでは、依存性の注入を使用して、データ コンテキストを利用できるようにします。

  • InvokeAsync ではビューから呼び出すことができるメソッドを表示し、任意の数の引数を取得できます。

  • InvokeAsync メソッドでは、isDonemaxPriority パラメーターを満たす ToDo 項目のセットを返します。

ビュー コンポーネント Razor ビューの作成

  • Views/Shared/Components フォルダーを作成します。 このフォルダーは、Components という名前にする"必要があります"。

  • Views/Shared/Components/PriorityList フォルダーを作成します。 このフォルダー名は、ビュー コンポーネント クラスの名前、または (規則に従い、クラス名に ViewComponent サフィックスを使用した場合は) サフィックスを差し引いたクラスの名前に一致する必要があります。 ViewComponent 属性を使用した場合は、クラス名は属性の指定に一致する必要があります。

  • Views/Shared/Components/PriorityList/Default.cshtmlRazor ビューを作成します。

    @model IEnumerable<ViewComponentSample.Models.TodoItem>
    
    <h3>Priority Items</h3>
    <ul>
        @foreach (var todo in Model)
        {
            <li>@todo.Name</li>
        }
    </ul>
    

    Razor ビューでは、TodoItem のリストを取得してそれらを表示します。 ビュー コンポーネントの InvokeAsync メソッドで (サンプルのように) ビューの名前を渡さない場合、Default が規則によってビュー名に使用されます。 このチュートリアルの後半で、ビューの名前を渡す方法について示します。 特定のコントローラーの既定のスタイルをオーバーライドするには、コントローラーに固有のビュー フォルダー (例: Views/ToDo/Components/PriorityList/Default.cshtml) にビューを追加します。

    ビュー コンポーネントがコントローラー固有の場合は、それをコントローラー固有のフォルダー (Views/ToDo/Components/PriorityList/Default.cshtml) に追加できます。

  • 優先順位リスト コンポーネントの呼び出しを含む div を、Views/ToDo/index.cshtml ファイルの末尾に追加します。

    </table>
    <div>
        @await Component.InvokeAsync("PriorityList", new { maxPriority = 2, isDone = false })
    </div>
    

@await Component.InvokeAsync マークアップは、ビュー コンポーネントを呼び出すための構文を示します。 最初の引数は、呼び出す必要があるコンポーネントの名前です。 後続のパラメーターは、そのコンポーネントに渡されます。 InvokeAsync では、任意の数の引数を取得できます。

アプリをテストします。 次の画像は、[ToDo] リストと優先順位の項目を示します。

todo list and priority items

また、コントローラーから直接ビュー コンポーネントを呼び出すこともできます。

public IActionResult IndexVC()
{
    return ViewComponent("PriorityList", new { maxPriority = 3, isDone = false });
}

priority items from IndexVC action

ビュー名を指定する

複雑なビュー コンポーネントでは、いくつかの条件下で、既定以外のビューを指定する必要がある場合があります。 次のコードでは、InvokeAsync メソッドから "PVC" ビューを指定する方法を示しています。 PriorityListViewComponent クラスで InvokeAsync メソッドを更新します。

public async Task<IViewComponentResult> InvokeAsync(
    int maxPriority, bool isDone)
{
    string MyView = "Default";
    // If asking for all completed tasks, render with the "PVC" view.
    if (maxPriority > 3 && isDone == true)
    {
        MyView = "PVC";
    }
    var items = await GetItemsAsync(maxPriority, isDone);
    return View(MyView, items);
}

Views/Shared/Components/PriorityList/Default.cshtml ファイルを Views/Shared/Components/PriorityList/PVC.cshtml という名前のビューにコピーします。 PVC ビューが使用されていることを示すために、見出しを追加します。

@model IEnumerable<ViewComponentSample.Models.TodoItem>

<h2> PVC Named Priority Component View</h2>
<h4>@ViewBag.PriorityMessage</h4>
<ul>
    @foreach (var todo in Model)
    {
        <li>@todo.Name</li>
    }
</ul>

以下を更新します。Views/ToDo/Index.cshtml:

@await Component.InvokeAsync("PriorityList", new { maxPriority = 4, isDone = true })

アプリを実行して、PVC ビューを確認します。

Priority View Component

PVC ビューがレンダリングされない場合は、4 以上の優先順位でビュー コンポーネントを呼び出していることを確認します。

ビューのパスを調べる

  • 優先順位ビューが返されないように、優先順位パラメーターを 3 以下に変更します。

  • Views/ToDo/Components/PriorityList/Default.cshtml の名前を一時的に 1Default.cshtml に変更します。

  • アプリをテストすると、次のエラーが表示されます。

    An unhandled exception occurred while processing the request.
    InvalidOperationException: The view 'Components/PriorityList/Default' wasn't found. The following locations were searched:
    /Views/ToDo/Components/PriorityList/Default.cshtml
    /Views/Shared/Components/PriorityList/Default.cshtml
    EnsureSuccessful
    
  • Views/ToDo/Components/PriorityList/1Default.cshtmlViews/Shared/Components/PriorityList/Default.cshtml にコピーします。

  • [Shared] の [ToDo] ビュー コンポーネントのビューにマークアップを追加して、そのビューが [Shared] フォルダーからのものであることを示します。

  • [Shared] コンポーネント ビューをテストします。

ToDo output with Shared component view

ハードコーディングされた文字列の回避

コンパイル時間の安全性を確保する必要がある場合は、ハードコーディングされたビュー コンポーネント名をクラス名に置き換えることができます。 "ViewComponent" サフィックスのないビュー コンポーネントを作成します。

using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using ViewComponentSample.Models;

namespace ViewComponentSample.ViewComponents
{
    public class PriorityList : ViewComponent
    {
        private readonly ToDoContext db;

        public PriorityList(ToDoContext context)
        {
            db = context;
        }

        public async Task<IViewComponentResult> InvokeAsync(
        int maxPriority, bool isDone)
        {
            var items = await GetItemsAsync(maxPriority, isDone);
            return View(items);
        }
        private Task<List<TodoItem>> GetItemsAsync(int maxPriority, bool isDone)
        {
            return db.ToDo.Where(x => x.IsDone == isDone &&
                                 x.Priority <= maxPriority).ToListAsync();
        }
    }
}

using ステートメントをお使いの Razor ビュー ファイルに追加して、nameof 演算子を使用します。

@using ViewComponentSample.Models
@using ViewComponentSample.ViewComponents
@model IEnumerable<TodoItem>

    <h2>ToDo nameof</h2>
    <!-- Markup removed for brevity.  -->

    <div>

        @*
            Note: 
            To use the below line, you need to #define no_suffix in ViewComponents/PriorityList.cs or it won't compile.
            By doing so it will cause a problem to index as there will be multiple viewcomponents 
            with the same name after the compiler removes the suffix "ViewComponent"
        *@

        @*@await Component.InvokeAsync(nameof(PriorityList), new { maxPriority = 4, isDone = true })*@
    </div>

CLR 型を受け取る Component.InvokeAsync メソッドのオーバーロードを使用できます。 この場合は、typeof 演算子を使用することを忘れないでください。

@using ViewComponentSample.Models
@using ViewComponentSample.ViewComponents
@model IEnumerable<TodoItem>

<h2>ToDo typeof</h2>
<!-- Markup removed for brevity.  -->

<div>
    @await Component.InvokeAsync(typeof(PriorityListViewComponent), new { maxPriority = 4, isDone = true })
</div>

同期作業を実行する

非同期作業を実行する必要がない場合は、フレームワークで同期 Invoke メソッドの呼び出しが処理されます。 次のメソッドでは同期 Invoke ビュー コンポーネントを作成します。

public class PriorityList : ViewComponent
{
    public IViewComponentResult Invoke(int maxPriority, bool isDone)
    {
        var items = new List<string> { $"maxPriority: {maxPriority}", $"isDone: {isDone}" };
        return View(items);
    }
}

ビュー コンポーネントの Razor ファイルには、Invoke メソッド (Views/Home/Components/PriorityList/Default.cshtml) に渡される文字列がリストされます。

@model List<string>

<h3>Priority Items</h3>
<ul>
    @foreach (var item in Model)
    {
        <li>@item</li>
    }
</ul>

ビュー コンポーネントは、次のいずれかの方法を使用して、Razor ファイル (Views/Home/Index.cshtml など) 内で呼び出されます。

IViewComponentHelper の方法を使用するには、Component.InvokeAsync を呼び出します。

@await Component.InvokeAsync(nameof(PriorityList), new { maxPriority = 4, isDone = true })

タグ ヘルパーを使用するには、@addTagHelper ディレクティブを使用して、ビュー コンポーネントを含むアセンブリを登録します (ビュー コンポーネントは、MyWebApp と呼ばれるアセンブリ内にあります)。

@addTagHelper *, MyWebApp

Razor マークアップ ファイルでビュー コンポーネントのタグ ヘルパーを使用します。

<vc:priority-list max-priority="999" is-done="false">
</vc:priority-list>

PriorityList.Invoke のメソッド署名は同期的ですが、Razor ではマークアップ ファイルで Component.InvokeAsync を使用してメソッドを見つけて呼び出します。

ビュー コンポーネントのすべてのパラメーターが必要

ビュー コンポーネントの各パラメーターは、必須の属性です。 こちらの GitHub のイシューを参照してください。 パラメーターを省略した場合は、次のようになります。

  • InvokeAsync メソッドのシグネチャが一致しないため、メソッドが実行されません。
  • ViewComponent がマークアップをレンダリングしません。
  • エラーがスローされません。

その他のリソース