モダンなブラウザーではJPEG形式やPNG形式のファイルより軽量なWebP形式の画像を表示する事が可能です。
ただ、 Safari 14.0 より古いバージョンのブラウザー等を使っている場合などは、WebPだけを指定してしまうと画像が表示できなくなる為、<picture> タグなどで併記したり、スクリプトで制御したりが必要となります。
WebP 形式に対応したブラウザーで同名の WebP ファイルがサーバー上に存在する場合に、拡張子を置き換えて HTML を出力させ、 WebP ファイルが表示できない場合は src で指定されたファイルをそのまま表示させるタグヘルパーを作成します。
まず、プロジェクトに TagHelpers フォルダーを作り、 ReplaceWebPTagHelper.cs ファイルを作成し、リクエストヘッダーにアクセスが必要になるため、 UrlResolutionTagHelper を継承させます。
タグヘルパーは img タグを対象とし、 src 属性と replace-webp 属性を必要とさせます。
[HtmlTargetElement(TagName, Attributes = AttributesName, TagStructure = TagStructure.WithoutEndTag)]
public class ReplaceWebPTagHelper : UrlResolutionTagHelper
{
private const string TagName = "img";
private const string AttributesName = SrcAttributeName + "," + ReplaceWebpAttributeName;
private const string SrcAttributeName = "src";
private const string ReplaceWebpAttributeName = "replace-webp";
また、オーバーライドした Order を -1001 にする事で asp-append-version より前に実行させる様に設定しています。
public override int Order => -1001;
次にブラウザーがWebP形式に対応しているか判断する為、リクエストヘッダーの "accept" に "image/webp" が含まれているか、 "user-agent" に "AppleWebKit" と "Version/14." が含まれているのかをチェックします。
var f1 = false;
if (ViewContext.HttpContext.Request.Headers.TryGetValue("accept", out var sv1))
{
f1 = sv1.ToString().Contains("image/webp");
}
var f2 = false;
if (ViewContext.HttpContext.Request.Headers.TryGetValue("user-agent", out var sv2))
{
var ua = sv2.ToString();
f2 = ua.Contains("AppleWebKit") && ua.Contains("Version/14.");
}
このどちらかの条件に一致したら、 src で指定されたファイルの拡張子を .webp に置き換え、 wwwroot 配下の物理パスに変換し、同名の WebP 形式のファイルがサーバー上に存在するかどうかをチェックします。
if (f1 | f2)
{
var virtualPath = Path.ChangeExtension(Src, "webp");
var physicalPath = _env.MapWebRootPath(virtualPath);
if (File.Exists(physicalPath))
Src = virtualPath;
}
※仮想パスを物理パスに変換する _env.MapWebRootPath() は独自に実装した拡張メソッドになります。 HostEnvironmentExtension に関しては 以前の記事 を参照ください。
これらの条件に一致した場合に拡張子を書き換えて出力します。
以下が完成したタグヘルパーのコードになります。
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Razor.TagHelpers;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.AspNetCore.Mvc.TagHelpers;
using Microsoft.AspNetCore.Razor.TagHelpers;
using System;
using System.IO;
using System.Text.Encodings.Web;
namespace AspNetTips.MvcSite.TagHelpers
{
[HtmlTargetElement(TagName, Attributes = AttributesName, TagStructure = TagStructure.WithoutEndTag)]
public class ReplaceWebPTagHelper : UrlResolutionTagHelper
{
private const string TagName = "img";
private const string AttributesName = SrcAttributeName + "," + ReplaceWebpAttributeName;
private const string SrcAttributeName = "src";
private const string ReplaceWebpAttributeName = "replace-webp";
private readonly IWebHostEnvironment _env;
public ReplaceWebPTagHelper(
IWebHostEnvironment env,
IUrlHelperFactory urlHelperFactory,
HtmlEncoder htmlEncoder
) : base(urlHelperFactory, htmlEncoder)
{
_env = env;
}
/// <inheritdoc />
public override int Order => -1001;
[HtmlAttributeName(SrcAttributeName)]
public string Src { get; set; }
[HtmlAttributeName(ReplaceWebpAttributeName)]
public bool ReplaceWebp { get; set; }
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(SrcAttributeName, context);
if (ReplaceWebp)
{
Src = output.Attributes[SrcAttributeName].Value as string;
var f1 = false;
if (ViewContext.HttpContext.Request.Headers.TryGetValue("accept", out var sv1))
{
f1 = sv1.ToString().Contains("image/webp");
}
var f2 = false;
if (ViewContext.HttpContext.Request.Headers.TryGetValue("user-agent", out var sv2))
{
var ua = sv2.ToString();
f2 = ua.Contains("AppleWebKit") && ua.Contains("Version/14.");
}
if (f1 | f2)
{
var virtualPath = Path.ChangeExtension(Src, "webp");
var physicalPath = _env.MapWebRootPath(virtualPath);
if (File.Exists(physicalPath))
Src = virtualPath;
}
output.Attributes.SetAttribute(SrcAttributeName, Src);
}
}
}
}
タグヘルパーができたらビューで使用できる様に設定が必要です。
Views フォルダーにある _ViewImports.cshtml に @addTagHelper ディレクティブ を追加します。プロジェクトのアセンブリ名を指定して、 * で全てのタグヘルパーを使用可能にしています。
準備が整ったら、ビューで img タグに replace-webp="true" を付けるだけで使用でき、 asp-append-version と併用する事でブラウザーキャッシュ対策にも対応してハッシュを付与する事も可能です。
HTML出力時には、 "~/images/test1.png" → "/images/test1.webp?v=xxxxxxxxxx" と変換されます。
- Windows 10 (20H2)
- Visual Studio 2019 (v16.8.5)
- .NET SDK 5.0.103
- ASP.NET Core 5.0 / MVC