先の記事「カスタム Tag ヘルパーで IUrlHelper を利用 (CORE)」のカスタム html ヘルパー版です。加えて、比較のために、同等な機能を部分ビューで実装する方法も書いてみました。
カスタム html ヘルパーは ASP.NET Core MVC では推奨されてないのか、Microsoft のドキュメントには作成方法の記事が見当たりませんでした。でも、使うことは可能なようですので、先の記事と同じく内部で IUrlHelper を利用する html ヘルパーを書いてみました。
.NET Framework 版の MVC5 アプリでは、カスタム html ヘルパーの中で以下のコードのようにして UrlHelper オブジェクトを取得できます。
using System.Web;
using System.Web.Mvc;
namespace Mvc5App.HtmlHelpers
{
public static class Mvc5AppHelpers
{
public static IHtmlString AchorTag(this HtmlHelper helper,
string contoller,
string action,
string text)
{
var urlHepler = new UrlHelper(helper.ViewContext.RequestContext);
var path = urlHepler.Action(action, contoller);
return MvcHtmlString.Create(
$"<a href=\"{path}\">{HttpUtility.HtmlEncode(text)}</a>");
}
}
}
Core 3.1 / 5.0 版の MVC アプリでは、先の記事「カスタム Tag ヘルパーで IUrlHelper を利用 (CORE)」に書きましたように、ASP.NET Core 組み込みの DI 機能を利用してコンストラクタ経由で IUrlHelperFactory と IActionContextAccessor を DI し、IUrlHelperFactory の GetUrlHelper(ActionContext) メソッドを使って IUrlHelper オブジェクトを取得しました。・・・よく調べてみると、LinkGenerator API を取得して利用する方が良さそうです。LinkGenerator を使ったカスタム html ヘルパーは下の「2021/4/18 追記」に書きます。
しかし、カスタム html ヘルパーで上のコード例のように拡張メソッドを使う場合は、静的クラス内に静的メソッドを配置することになりますので、コンストラクタ経由での DI ができません。
そこをどうするかですが、HttpContext の RequestServices プロパティ から IServiceProvider(サービスコンテナーへのアクセスを提供)を取得できますので、それを使って IUrlHelperFactory と IActionContextAccessor のインスタンスを取得できるようです。
HttpContent は IHtmlHelper.ViewContext プロパティから取得できる ViewContext の HttpContext プロパティで取得できます。
それらを利用したカスタム html ヘルパーを書いてみました。以下をコードを見てください。IServiceProvider から IUrlHelperFactory と IActionContextAccessor のインスタンスを取得し、それらを使って IUrlHelper オブジェクトを取得してその Action メソッドにより url パスの文字列を取得しています。(引数の AnchorTagData クラスの定義は先の記事を見てください)
using System;
using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Mvc.Rendering;
using System.Web;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using MvcCoreApp.Models;
namespace MvcCoreApp.HtmlHelpers
{
public static class MvcCoreAppHtmlHelpers
{
// 静的クラスなのでコンストラクタ経由での DI はできない。
// IServiceProvider を取得してそれから IServiceCollection に登録されている
// IUrlHelperFactory と IActionContextAccessor のインスタンスを取得
public static IHtmlContent AchorTag(this IHtmlHelper helper,
IEnumerable<AnchorTagData> Info)
{
IServiceProvider provider = helper.ViewContext.HttpContext.RequestServices;
var urlFactory = provider.GetRequiredService<IUrlHelperFactory>();
var actionAccessor = provider.GetRequiredService<IActionContextAccessor>();
var urlHelper = urlFactory.GetUrlHelper(actionAccessor.ActionContext);
var content = "";
foreach (var data in Info)
{
var path = urlHelper.Action(
action: data.Action,
controller: data.Controller);
content += "<li class=\"nav-item\">" +
$"<a class=\"nav-link text-dark\" href=\"{path}\">" +
$"{HttpUtility.HtmlEncode(data.Text)}</a>" +
"</li>\r\n";
}
var output = $"<ul class=\"navbar-nav flex-grow-1\">{content}</ul>";
return new HtmlString(output);
}
}
}
先のカスタム tag ヘルパーの記事と同様に、startup.cs での IActionContextAccessor のサービスへの登録は必要ですので忘れないようにしてください。具体的なコードは先の記事を見てください。
_Layout.cshtml に、上に定義した html ヘルパーが使えるように using 句を記述し、html ヘルパーに渡すモデルを初期化します。さらに、html ヘルパーを表示する場所に @Html.AchorTag(model) というコードを書きます。以下のような感じ。
@using MvcCoreApp.HtmlHelpers;
@{
IEnumerable<AnchorTagData> model =
new List<AnchorTagData> {
new AnchorTagData { Controller="Home", Action="Index", Text="Home" },
new AnchorTagData { Controller="Home", Action="Privacy", Text="Privacy" },
new AnchorTagData { Controller="People", Action="Index", Text="People" },
new AnchorTagData { Controller="Messages", Action="Index", Text="Messages" },
new AnchorTagData { Controller="Validation", Action="Create", Text="Validation" },
new AnchorTagData { Controller="Upload", Action="Index", Text="FileUpload" },
new AnchorTagData { Controller="Products", Action="Index", Text="Products" },
new AnchorTagData { Controller="Ajax", Action="Index", Text="Ajax" },
new AnchorTagData { Controller="IHttpClientFactory", Action="Index", Text="HttpClient" }};
}
// ・・・中略・・・
@Html.AchorTag(model)
// ・・・中略・・・
以上により、_Layout.cshtml に書いた @Html.AchorTag(model) というコードの部分に上の画像の赤枠で示したリンクが表示されます。
部分ビューで実装
次に、先の記事のカスタム tag ヘルパー、この記事のカスタム html ヘルパーと同等の機能を部分ビューを使って実装してみます。
ASP.NET Core の組み込みタグヘルパーのアンカータグヘルパーを以下のように部分ビュー _Navi.cshtml(名前は任意)に組み込みます。
@using MvcCoreApp.Models
@using System.Web;
@model IEnumerable<AnchorTagData>
<ul class="navbar-nav flex-grow-1">
@foreach (var data in Model)
{
<li class="nav-item">
<a class="nav-link text-dark"
asp-controller=@data.Controller
asp-action=@data.Action>
@HttpUtility.HtmlEncode(data.Text)
</a>
</li>
}
</ul>
上記部分ビューを、部分タグヘルパーを使って、以下のように _Layout.cshtml に配置します。
@{
IEnumerable<AnchorTagData> model = // 省略 (上と同じ)
}
// ���・・中略・・・
<partial name="_Navi.cshtml" model="model" />
// ・・・中略・・・
これだけで、先の記事のカスタム tag ヘルパー、この記事のカスタム html ヘルパーと同様に、上の画像の赤枠で示したリンクが表示されます。この記事で書いた程度のことを実装するなら部分ビューを使うのが一番シンプルでよさそうだと思いました
-------- 2021/4/18 追記 (LinkGenerator 利用) --------
上のカスタム html ヘルパーのコードは、IUrlHelperFactory と IActionContextAccessor をサービスコンテナーから取得し、 IUrlHelperFactory.GetUrlHelper(ActionContext) メソッドを使って IUrlHelper オブジェクトを取得して利用しています。
しかし、後でよく調べたら、Microsoft のドキュメント「URL 生成の概念」に書いてあるように LinkGenerator API を取得して利用する方が良さそうと思いました。
というわけで、LinkGenerator を使ったカスタム html ヘルパーのコードを以下に書きます。IActionContextAccessor のサービスへの登録は不要ですしコードも簡単になります。LinkGenerator はサービスコンテナに登録済みのようで、以下のようにするだけで取得できます。
using System;
using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Mvc.Rendering;
using System.Web;
using Microsoft.Extensions.DependencyInjection;
using System.Collections.Generic;
using MvcCoreApp.Models;
// LinkGenarator の利用
using Microsoft.AspNetCore.Routing;
namespace MvcCoreApp.HtmlHelpers
{
public static class MvcCoreAppHtmlHelpers
{
// 静的クラスなのでコンストラクタ経由での DI はできない。
// IServiceProvider を取得してそれから IServiceCollection に登録されている
// LinkGenarator のインスタンスを取得して利用する
public static IHtmlContent AchorTag(this IHtmlHelper helper,
IEnumerable<AnchorTagData> Info)
{
IServiceProvider provider = helper.ViewContext.HttpContext.RequestServices;
var linkGenerator = provider.GetRequiredService<LinkGenerator>();
var content = "";
foreach (var data in Info)
{
var path = linkGenerator.GetPathByAction(
action: data.Action,
controller: data.Controller);
content += "<li class=\"nav-item\">" +
$"<a class=\"nav-link text-dark\" href=\"{path}\">" +
$"{HttpUtility.HtmlEncode(data.Text)}</a>" +
"</li>\r\n";
}
var output = $"<ul class=\"navbar-nav flex-grow-1\">{content}</ul>";
return new HtmlString(output);
}
}
}