ASP.NET Tips #005 - ブラウザーキャッシュに対応した aタグ・sourceタグ 用の独自タグヘルパーを実装する

ASP.NET Core で wwwroot 配下にある静的ファイル へのアクセスに対して HTTP ヘッダーに "Cache-Control" を設定する事ができます。
ブラウザー側でのキャッシュ期間を設定するとリクエストを軽減させる事ができますが、同名のファイルをアップロードしなおしても パスが一緒のためキャッシュが優先 されてしまいます。

そこで ASP.NET Core では、imgタグ、linkタグ、scriptタグに asp-append-version="true" を付ける事で自動的にファイル名の後ろにハッシュを付与してくれます。これによってデータが置き換わるとハッシュも変わるのでキャッシュしてあるファイルとは別物として最新版を読み込んでくれます。

しかし、 a タグで PDF や ZIP のダウンロードであったり、 jQuery 等の画像を拡大表示するプラグインで a タグに画像を指定する必要がある時、picture タグで source を指定してレスポンシブでブレイクポイントに応じて画像を差し替えたり、 WebP 形式など異なるファイルにも対応したい時、 video タグで source 指定する際なども… wwwroot 配下にデータがある場合はキャシュを参照されてしまうと困ります。

なので、 a タグの hrefsource タグの srcset でも asp-append-version="true" を付ける事でハッシュを付与する様に独自のタグヘルパーを作成します。

基本的には GitHub で公開されている img タグ向けの ImageTagHelper のソースコードを参考に書き換えます。


プロジェクトを開いたら、「TagHelpers」フォルダーを作成し、「AnchorAppendVersionTagHelper.cs」と「SourceAppendVersionTagHelper.cs」のクラスを追加します。

ソリューション エクスプローラー

AnchorAppendVersionTagHelper.cs に ImageTagHelper のソースコードにある、「img」を「a」に、「src」を「href」に置き換えます。

~/TagHelpers/AnchorAppendVersionTagHelper.cs
using Microsoft.AspNetCore.Mvc.Razor.TagHelpers;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.AspNetCore.Mvc.TagHelpers;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Text.Encodings.Web;

namespace AspNetTips.MvcSite.TagHelpers
{
	/// <summary>
	/// aタグのhrefで静的ファイルのパスにバージョンのハッシュを付加する
	/// </summary>
	[HtmlTargetElement(
		"a",
		Attributes = AppendVersionAttributeName + "," + HrefAttributeName,
		TagStructure = TagStructure.WithoutEndTag
	)]
	public class AnchorAppendVersionTagHelper : UrlResolutionTagHelper
	{
		private const string AppendVersionAttributeName = "asp-append-version";
		private const string HrefAttributeName = "href";

		/// <summary>
		/// タグヘルパーの初期化
		/// </summary>
		/// <param name="fileVersionProvider"><see cref="IFileVersionProvider" /></param>
		/// <param name="htmlEncoder"><<see cref="HtmlEncoder"/></param>
		/// <param name="urlHelperFactory"><see cref="IUrlHelperFactory"/></param>
		[ActivatorUtilitiesConstructor]
		public AnchorAppendVersionTagHelper(
			IFileVersionProvider fileVersionProvider,
			HtmlEncoder htmlEncoder,
			IUrlHelperFactory urlHelperFactory
		) : base(urlHelperFactory, htmlEncoder)
		{
			FileVersionProvider = fileVersionProvider;
		}

		/// <inheritdoc />
		public override int Order => -1000;

		/// <summary>
		/// 対象となる静的ファイルのパス
		/// </summary>
		[HtmlAttributeName(HrefAttributeName)]
		public string Href { get; set; }

		/// <summary>
		/// ファイルのバージョンをクエリ文字列として付加するかどうかのフラグ
		/// </summary>
		/// <remarks>
		/// 値が true の時、ファイルのパスにクエリ文字列「v」としてハッシュが付加されます
		/// </remarks>
		[HtmlAttributeName(AppendVersionAttributeName)]
		public bool AppendVersion { get; set; }

		internal IFileVersionProvider FileVersionProvider { get; private set; }

		/// <inheritdoc />
		public override void Process(TagHelperContext context, TagHelperOutput output)
		{
			if (context == null)
				throw new ArgumentNullException(nameof(context));

			if (output == null)
				throw new ArgumentNullException(nameof(output));

			output.CopyHtmlAttribute(HrefAttributeName, context);
			ProcessUrlAttribute(HrefAttributeName, output);

			if (AppendVersion)
			{
				EnsureFileVersionProvider();
				Href = output.Attributes[HrefAttributeName].Value as string;
				output.Attributes.SetAttribute(HrefAttributeName, FileVersionProvider.AddFileVersionToPath(ViewContext.HttpContext.Request.PathBase, Href));
			}
		}

		private void EnsureFileVersionProvider()
		{
			if (FileVersionProvider == null)
			{
				FileVersionProvider = ViewContext.HttpContext.RequestServices.GetRequiredService<IFileVersionProvider>();
			}
		}
	}
}

同じ様に SourceAppendVersionTagHelper.cs では「img」→「source」に、「src」→「srcset」に置き換えます。
※a タグも source タグも置換する箇所は基本的にどちらも一緒になります。

タグヘルパーができたらビューで使用できる様に設定が必要です。
Views フォルダーにある _ViewImports.cshtml@addTagHelper ディレクティブ を追加します。

ソリューション エクスプローラー
~/Views/_ViewImports.cshtml

2番目のパラメーターで タグヘルパーの含まれるアセンブリ名 を指定し、1番目のパラメーター「*」によってアセンブリ内の全てのタグヘルパーを使用可能にします。

プロジェクトのプロパティ ※アセンブリ名の確認

ビューで使用する際、 a タグの記述に asp-append-version="true" を付けるとタグヘルパーに反応して href が太字で色が変わります。

~/Views/Home/Index.cshtml ※aタグの使用例

source タグの記述に asp-append-version="true" を付けるとタグヘルパーに反応して srcset が太字で色が変わります。

~/Views/Home/Index.cshtml ※picture(source)タグの使用例

これでタグヘルパーによるキャッシュ対応は完成です。
デバッグ実行してソースを表示して、ファイル名の後ろに ?v=xxxxx とハッシュが付加されているのを確認します。

ページのソースを表示 ※aタグの場合
ページのソースを表示 ※picture(source)タグの場合
試作環境
  • Windows 10 (20H2)
  • Visual Studio 2019 (v16.8.4)
  • .NET SDK 5.0.102
  • ASP.NET Core 5.0 / MVC