ASP.NET Core 2.1 では、日本語がHTMLエンティティで表示される問題(解決)


0から始めるASP.NET Core で発生した問題について

ASP.NET Core 2.1 で、Razorを使ってASP.NETを組み立てるという練習を始めた途端に出てきた問題として、「日本語」を含む2byte文字以上(HTMLで有害となる文字)以外をRazorを通して出力すると、HTMLの出力結果がHTMLエンティティされる問題について、環境としての仕様というコメントをいただき、コードを追加したところ解決したのでメモとして残しておきます。


発生した現象

文字列(string型) のデータを、view(またはpage)において、@変数 でデータを表示させるとき、それが日本語を含む2byte以上の文字列(1byteでもHTMLとして有害となるコードを除く)である場合(厳密のは全角文字)、HTMLエンティティとして出力されたこと

例えば

string hello = "こんにちは";

<div>
@hello
</div>

としたコードを書いた場合、ブラウザ上での出力は

こんにちは

と表示されますが、HTMLソースコード(応答結果)は、

<div>

&#x3053;&#x3093;&#x306B;&#x3061;&#x306F;
</div>

と表示されます。


発生を確認した環境


  • Visual Studio 2017 Community より、直接コンパイルした場合

  • Windowsでコマンドより、dotnet run で実行させた場合

  • Linuxのasp.net core(.net core 2.1)を使った場合

  • mac osでのasp.net core(.net core 2.1)を使った場合


問題点

ブラウザでは、読める形で出力されるために閲覧としては問題はありません。しかし、HTMLエンティティとして表示されている関係上、出力されるバイト数が生の状態よりも2倍近く増えること(UTF8の場合、日本語は3byteのため。なお、一部例外あり)と、HTMLエンティティされていることにより、デコードが余分にかかってしまうということ。特に、ブログなどの記事の場合はよろしくない状態かと思われます。


影響範囲

現在、検証できている範囲は、ハードコーティングしたデータを出力させる、PostおよびGetリクエストで送受信したデータにおいて発生することがわかっています。データベースから呼び出したデータを呼びしたりした場合でも、変数を通す関係上同様の現象が発生します。

以下は、RazorページでのPostデータでのテストを行うために作った簡易コードです。

[Pages/Index.cshtml]

@page

@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@model aspnet_zero.Pages.IndexModel
@{
Layout = null;
}

<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Index</title>
</head>
<body>
<form method="post">
<input asp-for="Msg.Title" placeholder="題名" />
<input asp-for="Msg.Msg" placeholder="内容" />
<button type="submit">はろー</button>
</form>
<div>
<p>@Html.Raw(Model.Msg.Title)</p>
<p>@HModel.Msg.Msg</p>
</div>
</body>
</html>

[Pages/Index.cshtml.cs]

public class IndexModel : PageModel

{
public Message Msg { set; get; }

public void OnGet()
{
Msg = new Message();
}

public IActionResult OnPost(Message msg)
{
if(!ModelState.IsValid)
{
return Page();
}

Msg = msg;
return Page();
}
}

[Models/Message.cs]

public class Message

{
public string Title { set; get; }
public string Msg { set; get; }
}

なお、@Html.Raw() メソッドを通している場合は、HTMLエンティティされません。が、当然のごとく不等号< > も無効化されてしまうため、サニタイジングが解除されてしまいます。


解決策

コメントにて、解決法をいただいたので、そのコードを記録として残しておきます

// UTF8文字コードがHTMLエンティティされる問題を解決するおまじない

services.Configure<Microsoft.Extensions.WebEncoders.WebEncoderOptions>(options =>
{
options.TextEncoderSettings = new System.Text.Encodings.Web.TextEncoderSettings(System.Text.Unicode.UnicodeRanges.All);
});

設定としてエンコード範囲にUnicodeも追加するということでしょうか。それにしても、今や世界中で使われる環境であり、今時UTF-8などのUnicode系以外の文字コード環境を使うことは稀かと思うので、できれば標準で対応しておいてほしいなとも思うのですが...。


まとめ

環境による制約というか、仕様のよう、コードを追加することで対応可能なのが分かりホッとしました。とはいえ、MSDNでも記述が見つからず(検索のワードが悪いのか、見つけにくいだけなのか)、参考になるような文献も見つからず、結構インターネットが普及したからと言って簡単に情報を探し出し、解決できるとは限らない状況ですね。

フレームワークによってカプセル化されることによる制約は今まで何度も経験していますが、それを解決する手段を得るのは、なかなか難しいなという教訓を得た感じもします。