WebSurfer's Home

トップ > Blog 1   |   ログイン
APMLフィルター

React で日本語を使うとコンパイルエラー / 文字化け

by WebSurfer 2022年4月10日 14:26

Visual Studio 2019 / 2022 の「React.js での ASP.NET Core」のテンプレートで作ったプロジェクトで、自動生成された Home.js とか Counter.js などに以下のように <p>日本語</p> という日本語を書き加えると Unexpected character '�' というエラーメッセージが出てコンパイルエラーになります。文字によってはコンパイルは通りますが文字化けします。

Home.js に日本語追加

フレームワークを .NET Core 3.1 としてプロジェクトを作って同様なことをすると、ブラウザに空白画面が表示されるだけなので何が起こったのか分からないと思いますが、.NET 6.0 とするとコマンドラインに以下のように表示されます。(コンパイルエラーにならない場合は文字化けしてブラウザに表示されるので分かります)

コマンドラインのエラーメッセージ

ブラウザにも以下のようにエラーメッセージが表示されます。

ブラウザのエラーメッセージ

文字によってはコンパイルは通り、ブラウザには結果が表示されますが、日本語の部分は文字化けします。 (コンパイルが通るもの / 通らないものの違いは不明です)

原因は Visual Studio で編集して保存する時ファイルの文字コードが Shift_JIS になってしまうからです。下の画像は Home.js をバイナリエディタで表示したものですが「日本語」の部分が 93 FA 96 78 8C EA と Shift_JIS になっているのが分かるでしょうか。

バイナリエディタで表示

何故そんなことになってしまうかというと、Visual Studio の設定がそうなっているからです。下の画像を見てください。Home.js の「保存オプションの詳細設定」ですが、そこで[エンコード(E)]が「日本語 (シフト JIS) - コードページ 932」になっているのに注目してください。

保存オプションの詳細設定

このため日本語を書き込んで保存すると Shift_JIS になってしまい、UTF-8 を期待している React ではコンパイルエラーまたは文字化けするということです。Home.js 以外にも ClientApp/src フォルダのほとんどのファイルがその設定になっていますので注意してください。

解決策は、下の画像のように[エンコード(E)]を「Unicode (UTF-8 シグネチャなし) - コードページ 65001」として上書きすることです。

保存オプションの詳細設定

または、メモ帳で問題の .js ファイルを開いて[文字コード(E)]を BOM なし UTF-8 に設定して上書きすることでも OK です。一旦メモ帳で BOM なし UTF-8 で保存してしまえば、その後は Visual Studio で .js ファイルを開いて日本語を書き込んでも UTF-8 で保存されるのでこの問題は起こりません。(詳しい仕組みは分かりませんが、Visual Studio の「保存オプションの詳細設定」設定に反映されるようです)

なお、BOM 付き UTF-8 にすると下の画像のように Unexpected Unicode BOM (Byte Order Mark) という警告が出ますので注意してください。(「保存オプションの詳細設定」でシグネチャ付きというのは BOM 付きのことですので注意)

BOM による警告

BOM 付きでもコンパイルエラーにはならず文字化けもしませんが、そこは警告に従って BOM なし UTF-8 にするのが良さそうです。

Visual Studio を操作して[追加(D)]⇒[新しい項目(W)...]で「JavaScript ファイル」を選んで作成すると BOM 付き UTF-8 になることに注意してください。コンパイルは通りますが、やはり Unexpected Unicode BOM (Byte Order Mark) という警告が出ます。

React プロジェクトでは ClientApp/src フォルダの .js ファイルだけでなく、Program.cs や Startup.cs でも保存オプションはデフォルトで「日本語 (シフト JIS) - コードページ 932」になっています。そこは ASP.NET Core プロジェクトでも同じです。

Program.cs や Startup.cs は Visual Studio が読んで処理を行いますので正しく文字コードが解釈され Shift_JIS でも問題は出ないはずです。ただ、ちょっと特殊な話ですが問題事例はありました。興味がありましたらTeratail のスレッド「asp.net.coreでの文字化けを解決したい。」を見てください。これはブラウザに送信する際は UTF-8 になっていたが、応答ヘッダやメタタグで文字コードの指定をしてなかったので、ブラウザが Shif_JIS として処理して文字化けを起こしたというもので、開発者のミスに近いものですが。

この記事の React の .js ファイルの話は、昔々 .NET Core 1 時代の MVC アプリで経験した「ASP.NET Core で文字化け」の .cshtml ファイルの件と似た話です。Microsoft も文字コードの問題はなかなか気が付かないのでしょうね。

Tags: , , , ,

React

SmtpClient でメール送信

by WebSurfer 2010年9月7日 14:25

ユーザー登録の際のパスワードの連絡、ブログにコメントが書き込まれた時の通知などのために、Web アプリケーションにメールの自動送信機能を実装するケースは多いと思います。

自分の HP にも、メールの自動送信の機能を実装しています。もっとも、ユーザー登録するような人はいないし、コメントを書き込む人もいないので、ほとんど役に立っていませんが。(苦笑)

メール送信に利用しているのは .NET Framework のライブラリ SmtpClient クラスです。これを利用すると簡単にメール送信ができます。

ただし、日本語対応に問題があり注意が必要です。以下に、文字コードとしてデフォルトの UTF-8 を使った場合と、日本で広く使われている iso-2022-jp (JIS) を使った場合の注意事項を書いておきます。

(1) UTF-8 を使う場合

UTF-8 の場合は、ほとんど文字化けに悩むことはありませんが、アドレスの表示名に日本語を使う場合のみ注意が必要です。

表示名は MailAddress コンストラクタの第 2 引数で設定しますが、日本語の文字列をそのまま代入すると Q エンコードされてしまいます。

Q エンコードで問題なければいいのですが、受信するメーラによっては B エンコードでないと文字化けするかもしれません。(Outlook Express, Windows メールは問題ありませんでしたが)

B エンコードにするには、以下のサンプルコードにある EncodeMailHeader ようなメソッドを作って日本語の表示名を変換し、それを第 2 引数に代入して���ださい。

件名、本文、添付ファイル名は、以下のサンプルのように日本語をそのまま代入して問題ありません。件名、添付ファイル名は、自動的に UTF-8 の B エンコードに変換されて、ヘッダーに設定されます。本文は、UTF-8 の base64 エンコードとなります。

private string EncodeMailHeader(string str, Encoding enc)
{
  string ret = 
    System.Convert.ToBase64String(enc.GetBytes(str));
  ret = string.Format("=?{0}?B?{1}?=", enc.BodyName, ret);
  return ret;
} 

Encoding enc = Encoding.GetEncoding("utf-8");

MailAddress to = 
  new MailAddress("surfer@mail.abc.co.jp", 
    EncodeMailHeader("日本語の名前", enc));
MailAddress From = 
  new MailAddress("surfer@mail.abc.co.jp", 
    EncodeMailHeader("日本語の名前", enc));

MailMessage mailMessage = new MailMessage(from, to);

mailMessage.Subject = "日本語の件名";
mailMessage.Body = "日本語の本文";

Attachment data = 
  new Attachment("添付ファイル名", 
    System.Net.Mime.MediaTypeNames.Application.Octet);
ContentDisposition disposition = data.ContentDisposition;
disposition.FileName = 
  EncodeMailHeader(System.IO.Path.GetFileName(file), enc);
disposition.CreationDate = 
  System.IO.File.GetCreationTime(file);
disposition.ModificationDate = 
  System.IO.File.GetLastWriteTime(file);
disposition.ReadDate = 
  System.IO.File.GetLastAccessTime(file);

mailMessage.Attachments.Add(data);

SmtpClient smtpClient = new SmtpClient();
smtpClient.Host = "mail.abc.co.jp";
smtpClient.Port = 25;
smtpClient.Send(mailMessage);

(2) iso-2022-jp を使う場合

Encoding を iso-2022-jp にする以外は、メールアドレスを設定するところまでは上記の UTF-8 の場合と同じです。

件名は、直接 Subject プロパティに日本語を代入すると Q エンコードになってしまいます。以下のサンプルのように、EncodeMailHeader メソッドを使って変換してから Subject プロパティに代入しなければなりません。(注:.NET Framework 4.5 で件名のエンコードが変わったそうです。詳しくは下の 2013/5/10 追記を見てください)

一番の問題は本文で、Body を使用すると Content-Transfer-Encoding は 8bit エンコードになってしまいます。これを 7bit にする方法はないそうです。代わりに、AlternateView を使って 7bit エンコードにする方法があります。以下にそのサンプルを書いておきます。

添付ファイル名は、Content-Type の name は、どのよう設定にしても UTF-8 の B エンコードになってしまいます。一方、Content-Disposition の filename は、以下のように設定すれば、iso-2022-jp の B エンコードになります。

以上のように、iso-2022-jp を使うのは、特に本文を AlternateView にするところが、かなり無理やりっぽい感じです。自分的には決してお勧めではないです。

Encoding enc = Encoding.GetEncoding("iso-2022-jp");

private string EncodeMailHeader(string str, Encoding enc)
{
  string ret = 
    System.Convert.ToBase64String(enc.GetBytes(str));
  ret = string.Format("=?{0}?B?{1}?=", enc.BodyName, ret);
  return ret;
}

MailAddress to = 
  new MailAddress("surfer@mail.abc.co.jp", 
    EncodeMailHeader("日本語の名前", enc));
MailAddress From = 
  new MailAddress("surfer@mail.abc.co.jp", 
    EncodeMailHeader("日本語の名前", enc));

MailMessage mailMessage = new MailMessage(from, to);

mailMessage.Subject = EncodeMailHeader("日本語の件名", enc);

// プレーンテキストのAlternateViewを作成
AlternateView htmlView = 
  AlternateView.CreateAlternateViewFromString(
    "日本語の本文",
    enc,
    System.Net.Mime.MediaTypeNames.Text.Plain);

// TransferEncoding.SevenBitを指定
htmlView.TransferEncoding = 
  System.Net.Mime.TransferEncoding.SevenBit;

mailMessage.AlternateViews.Add(htmlView);

Attachment data = 
  new Attachment("添付ファイル名", 
    System.Net.Mime.MediaTypeNames.Application.Octet);
ContentDisposition disposition = data.ContentDisposition;
disposition.FileName = 
  EncodeMailHeader(System.IO.Path.GetFileName(file), enc);
disposition.CreationDate = 
  System.IO.File.GetCreationTime(file);
disposition.ModificationDate = 
  System.IO.File.GetLastWriteTime(file);
disposition.ReadDate = 
  System.IO.File.GetLastAccessTime(file);

mailMessage.Attachments.Add(data);

SmtpClient smtpClient = new SmtpClient();
smtpClient.Host = "mail.abc.co.jp";
smtpClient.Port = 25;
smtpClient.Send(mailMessage);

ということで、結局自分はどうしているかというと、デフォルトの UTF-8 を使っています。UTF-8 が使えないメーラーなんか相手にしない・・・と言いたいところですが、自分宛のメール送信ぐらいにしか使ってないから、何だっていいのです。(笑)

----- 2013/5/10 追記 -----

.NET Framework 4.5 では件名 (Subject) の実装が変わって、上記の「(2) iso-2022-jp を使う場合」に書いたサンプルコードでは Q エンコードになってしまうそうです。その理由など、詳しくは以下のページを参照してください。

.NET Framework 4.5 の System.Net.Mail で日本語の件名を ISO-2022-JP の Base64 でエンコードして送信する方法

二重にエンコードすることにより B エンコードになるそうです。検証していませんが、上記のサンプルコードでは以下のように変更すればよいはずです。

.NET Framework 4 まで(上記サンプルコードの通り)

mailMessage.Subject = EncodeMailHeader("日本語の件名", enc);

.NET Framework 4.5 では二重エンコード

mailMessage.Subject = 
  EncodeMailHeader(EncodeMailHeader("日本語の件名", enc), enc);

Tags: , ,

.NET Framework

日本語の文字化けの問題(3)

by WebSurfer 2010年6月8日 16:49

Tag cloud の中で日本語の Tag をクリックすると、Tag に関連するポストは正しく表示されるものの、ブラウザのタイトル表示が文字化けします。

例えば、Tag cloud の中の "湘南" をクリックすると、タイトルは All posts tagged '湘南' となるべきところ、All posts tagged 'e6b998e58d97' となってしまいます。

タイトルは、アプリケーションルート直下にある Default.aspx.cs の DisplayTags メソッドの中で、以下のように設定されます。

base.Title = " All posts tagged '" + 
    Request.QueryString["tag"].Substring(1) + "'";

ところが、このクエリ文字列は、UrlEncode してから '%' を除去しているので、上記では UrlDecode されません。(UrlEncode と '%' の除去は、widgets\Tag cloud\widget.ascx.cs の中で Utils.RemoveIllegalCharacters メソッドを使って行っています)

これは DisplayTags メソッドを以下のように直して対応しました。

private void DisplayTags()
{
  if (!string.IsNullOrEmpty(Request.QueryString["tag"]))
  {
    PostList1.ContentBy = ServingContentBy.Tag;
    List<Post> posts = 
      Post.GetPostsByTag(Request.QueryString["tag"].Substring(1));
    PostList1.Posts = 
      posts.ConvertAll(new Converter<Post, IPublishable>(p => p as IPublishable));
    foreach (string t in posts[0].Tags)
    {
      if (Utils.RemoveIllegalCharacters(t).Equals(
         Request.QueryString["tag"].Substring(1),
         StringComparison.OrdinalIgnoreCase))
      {
        base.Title = " All posts tagged '" + t + "'";
        break;
      }
    }
    base.AddMetaTag("description", 
      Server.HtmlEncode(BlogSettings.Instance.Description));
  }
}

ところが、ポスト欄の左下に「Tag: XXXX」というリンクがありますが、この URL のクエリ文字列は UrlEncode がされたのみになっています。('%' は除去されていない。例えば、"湘南" は "%e6%b9%98%e5%8d%97" となります)

これをクリックすると、Tag cloud の中のリンクをクリックしたのと同様に、Default.aspx.cs の DisplayTags メソッドの中で処置されます。そこで、GetPostsByTag メソッドで再度 RemoveIllegalCharacters が引数の文字列に適用され、'%' は除去されるので、正しく Tag に関連する posts が得られますが、foreach の中で一致する t がない(if 文の条件が true にならない)のでタイトルが設定されません。

その結果、ブラウザのタイトルは、All posts tagged 'XXXX' としたいところ、 http://surferonwww.info/BlogEngine... のようになってしまいます。

これは、BlogEngine.Core の中の Web\Controls\PostViewBase.cs の TagLinks メソッドを以下のように直して対応しました。

protected virtual string TagLinks(string separator)
{
  if (Post.Tags.Count == 0)
    return null;

  string[] tags = new string[Post.Tags.Count];
  string link = "<a href=\"{0}/{1}\" rel=\"tag\">{2}</a>";
  string path = Utils.RelativeWebRoot + "?tag=";
  for (int i = 0; i < Post.Tags.Count; i++)
  {
    string tag = Post.Tags[i];
    // 以下で、オリジナルの HttpUtility.UrlEncode(tag) を変更。
    tags[i] = string.Format(CultureInfo.InvariantCulture, 
      link, path, BlogEngine.Core.Utils.RemoveIllegalCharacters(tag), 
      HttpUtility.HtmlEncode(tag));
  }
  return string.Join(separator, tags);
}

クエリ文字列は UrlEncode だけでよさそうな気がしましたが、わざわざ Widget の Tag cloud の方で RemoveIllegalCharacters を使うように変更したのは何か理由がありそうなので、それに合わせることにしました。

Tags: ,

BlogEngine.NET

About this blog

2010年5月にこのブログを立ち上げました。主に ASP.NET Web アプリ関係の記事です。

Calendar

<<  2024年3月  >>
252627282912
3456789
10111213141516
17181920212223
24252627282930
31123456

View posts in large calendar