WebSurfer's Home

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

ASP.NET Core で文字化け

by WebSurfer 2017年6月16日 20:37

ASP.NET Core のプロジェクトで、スキャフォールディング機能を利用して生成した View に日本語を書き込むと、文字化けするという話を書きます。元の話は Teratail の記事「ASP.NET Core でスキャフォールディングすると文字化けする」です。(注: VS2015 ASP.NET Core v1 の話です。VS2019 ASP.NET Core v3.1 では直っていました)

ASP.NET Core での文字化け

上の画像で赤枠で囲った部分が問題の文字化けです。どのような理由でそうなるかを以下に説明します。

(注:何行にもわたって書き込むとサーバーエラーになり、"An error occurred during the compilation of a resource required to process this request. Please review the following specific error details and modify your source code appropriately. "�" is not valid at the start of a code block. Only identifiers, keywords, comments, "(" and "{" are valid." というエラーメッセージが出ることもあります)

検証に使った環境は以下の通りです。Visual Studio 2017 でも同じ問題が出るとのことです。

  • Visual Studio 2015 Community 2015 Update 3
  • ASP.NET Core Web Application (.NET Core) のテンプレート使用
  • Windows 10 Professional 64-bit
  • IIS 10.0 Express

そもそもの原因は、スキャフォールディング機能を利用して生成した View の文字コードが Shift_JIS になってしまうことです(日本語 OS の場合)。.NET Framework ベースの MVC プロジェクトの View はデフォルトでは BOM 付きの UTF-8 になります。それを Shift_JIS に変換しても文字化けの問題は出ません。何故か、Core では Shift_JIS では対応できず、上の画像のように文字化けします。

Fiddler を使って応答をキャプチャし、問題の文字 "�" のバイト列を調べてみたら EF BF BD となっていました。

MSDN ライブラリ「.NET で文字エンコーディング クラスを使用する方法」によると "Unicode デコーダーでは、デコードできない 2 バイトのシーケンスが REPLACEMENT_CHARACTER (U+FFFD) に置き換えられます" とのことです。U+FFFD は UTF-8 のバイト列では EF BF BD となります。

ということは、Shift_JIS の日本語の文字列は(多分、他の非 ASCII 文字も)、ASP.NET Core ではその文字列を処理する際デコードできず、REPLACEMENT_CHARACTER に置き換えられ、その UTF-8 のバイト列 EF BF BD がサーバーから送られてきたということのようです。

何故 Core ベースのアプリでは View の文字コードが Shift_JIS になってしまうのか、何故 Shift_JIS ではデコードできないのかは不明ですが、解決するには View の文字コードを UTF-8 にするだけで OK です。

ただし、Visual Studio のオプション設定などで自動的に UTF-8 で保存する方が見つかりません。なので、プリミティブな方法ですが、メモ帳で当該 View を開いて UTF-8 で保存し直すことでとりあえず解決しました。

最後に、上の画像を表示した View をどのように作ったかを書いておきます。

まず、以下のような Controller と Model を作ります。(Controller と Model が一緒になっているのは単に分けるのが面倒だったからです)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;

namespace AspNetCore.Controllers
{
  public class SampleModel
  {
    public string FirstName { set; get; }
    public string LastName { set; get; }
  }

  public class HomeController : Controller
  {
    public IActionResult Sample()
    {
      ViewData["SubTitle"] = "日本語";

      var model = new List<SampleModel>
      {
        new SampleModel() {FirstName="山田",LastName="太郎"},
        new SampleModel() {FirstName="日本",LastName="花子"}
      };

      return View(model);
    }
  }
}

上のコードにある SampleModel をベースにスキャフォールディング機能を利用して View を自動生成します。具体的には、以下の画像のように、Visual Studio のソリューションエクスプローラで、View のフォルダを右クリック ⇒[追加(D)]⇒[ビュー(V)...]で「ビューの追加」ダイアログを開き、必要な情報を入力して[追加]ボタンをクリックします。

「ビューの追加」ダイアログ

生成される View のコードは以下のようになります。文字コードは Shift_JIS になりますが、この時点では日本語が含まれていませんので問題は出ないです。

@model IEnumerable<AspNetCore.Controllers.SampleModel>

@{
    ViewData["Title"] = "Sample";
}

<h2>Sample</h2>

<p>
  <a asp-action="Create">Create New</a>
</p>
<table class="table">
  <thead>
    <tr>
      <th>
        @Html.DisplayNameFor(model => model.FirstName)
      </th>
      <th>
         @Html.DisplayNameFor(model => model.LastName)
      </th>
      <th></th>
    </tr>
  </thead>
  <tbody>
@foreach (var item in Model) {
    <tr>
      <td>
        @Html.DisplayFor(modelItem => item.FirstName)
      </td>
      <td>
        @Html.DisplayFor(modelItem => item.LastName)
      </td>
            
      ・・・中略・・・

    </tr>
}
  </tbody>
</table>

Visual Studio のエディタから上のコードの一部を、以下のように日本語にします。

@model IEnumerable<AspNetCore.Controllers.SampleModel>

@{
    ViewData["Title"] = "日本語";
}

<h2>@ViewData["SubTitle"]</h2>

<p>日本語</p>

<p>
  <a asp-action="Create">Create New</a>
</p>

・・・以下略・・・

これを Visual Studio のバイナリエディタで開いたのが以下の画像です。"日本語" の文字列が 93 FA 96 7B 8C EA と Shift_JIS のバイト列になっているのが分かるでしょうか。

バイナリエディタで表示

この状態の View を表示させたのが一番上の画像です。

文字化けしているのは ViewData["Title"] = "日本語"; として _Layout.cshtml に渡し、それから title タグに設定した文字列(一番上の画像で上の赤枠)と、<p>日本語</p> とした部分(下の赤枠)です。

Controller で ViewData["SubTitle"] = "日本語"; としたり、Model から DisplayFor 経由で View に渡した日本語の文字列は問題ありません。

一般的にまだ枯れてないフレームワークにはいろいろ不具合があると思いますが、今回の問題もそういった不具合の一つということでしょうか。

ちなみに、ビューのフォルダを右クリック⇒[追加(D)]をクリック ⇒[新しい項目(W)...]をクリック ⇒[MVC ビューページ]を選択して名前を付けて[追加(A)]をクリック・・・で生成される空の View ファイルは UTF-8 になります。なので、やはり本来は UTF-8 になるべきなのであろうと思います。

ググって調べてもこの問題は日本語の記事には見つからなかったのですが、英語の stackoverflow には同様な問題の報告がありました。

Special characters in Razor template not being encoded correctly

Asp net core MVC View creating view default encoding VS 2017

前者の記事のコメントに known issue だとして Encoding in scaffolded Items へのリンクが張ってあって、その記事によると "closed this on May 6 2016" となっていますが、直ってません。どういうことかは不明です。

Tags: , ,

CORE

EDM にデータアノテーション属性を付与

by WebSurfer 2017年5月21日 15:10

Microsoft のチュートリアル「10 行でズバリ!! ASP.NET MVC を構成する各コンポーネントとネーミング ルール (C#)」のように、既存の SQL Server データベースから Entity Data Model (EDM) を生成して ASP.NET MVC アプリで使用する場合、ユーザー入力を検証するためのデータアノテーション属性をどのように付与できるかを書きます。

Visual Studio で生成した EDM

上の画像は、SQL Server 2008 Express のデータベース AdventureWorksLT をベースに、Visual Studio 2015 + EF6 で生成した EDM (Model1.edmx) です。(注:Visual Studio 2010 + EF4 で生成した EDM は内容が異なりますが、データアノテーション属性を付与する方法は同じです)

生成した EDM をベースに、上に紹介した「10 行でズバリ!!」チュートリアルと同様に、Visual Studio のスキャフォールディング機能を利用して、Address テーブルを表示・編集する ASP.NET MVC アプリの Controller と View を一式自動生成する場合を例に考えます。

自動生成された EDM で Address テーブルを表す Model のコードは、上の画像で示すように Address.cs にあります。そのコードは以下の通りです(説明に不要な部分は省略しています)。

namespace AdventureWorksLT
{
    using System;
    using System.Collections.Generic;
    
    public partial class Address
    {
        //・・・コンストラクタ(省略)・・・
        
        public int AddressID { get; set; }
        public string AddressLine1 { get; set; }
        public string AddressLine2 { get; set; }
        public string City { get; set; }
        public string StateProvince { get; set; }
        public string CountryRegion { get; set; }
        public string PostalCode { get; set; }
        public System.Guid rowguid { get; set; }
        public System.DateTime ModifiedDate { get; set; }
    
        //・・・ナビゲーションプロパティ(省略)・・・
    }
}

これに手を加えればよさそうに思えますが、それは NG です。何故なら、コードのコメントに書いてあるように、"このファイルを手動で変更すると、アプリケーションで予期しない動作が発生する可能性があります。このファイルに対する手動の変更は、コードが再生成されると上書きされます" ということだからです。

ではどうするかですが、Microsoft の文書「Validation with the Data Annotation Validators (C#)」の「Using Data Annotation Validators with the Entity Framework」のセクションに書いてある方法を取るのがよさそうです。(日本語版は機械翻訳なので英文を読むことをお勧めします)

TechNet の記事を読めばこれ以降の説明は不要かもしれませんが、記事がリンク切れになったりすると困るので、上の Address クラスの場合を例に方法を書いておきます。

具体的には以下のようなコードを EDM とは別の場所にクラスファイルを追加してそれに含めます。上の画像のソリューションエクスプローラの AddressMetaData.cs がそのクラスファイルです。

using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

namespace AdventureWorksLT
{
    [MetadataType(typeof(AddressMetaData))]
    public partial class Address
    {

    }

    public class AddressMetaData
    {
        [Display(Name = "住所1")]
        [Required(ErrorMessage = "{0} は必須入力です")]
        public object AddressLine1 { get; set; }

        [Display(Name = "住所2")]
        public object AddressLine2 { get; set; }

        [Display(Name = "街")]
        [Required(ErrorMessage = "{0} は必須入力です")]
        [StringLength(10, ErrorMessage = "{0} は {1} 以内。")]
        public object City { get; set; }

        [Display(Name = "州")]
        [Required(ErrorMessage = "{0} は必須入力です")]
        public object StateProvince { get; set; }

        [Display(Name = "国名")]
        [Required(ErrorMessage = "{0} は必須入力です")]
        public object CountryRegion { get; set; }

        [Display(Name = "郵便番号")]
        [Required(ErrorMessage = "{0} は必須入力です")]
        public object PostalCode { get; set; }

        [Display(Name = "GUID")]
        [Required(ErrorMessage = "{0} は必須入力です")]
        public object rowguid { get; set; }

        [Display(Name = "更新日")]
        [Required(ErrorMessage = "{0} は必須入力です")]
        public object ModifiedDate { get; set; }
    }
}

上のコードで AddressMetaData が TechNet の記事で言うメタデータクラスです。それにプロキシプロパティを定義して必要なデータアノテーション属性を付与しています。

TechNet の記事にも書いてありますが、プロキシプロパティの型は自動生成された Address クラスのプロパティの型と同じである必要はないそうです(同じでも OK ですが)。上のコードでは TechNet の記事にならってプロキシプロパティの型は Object 型にしています。

自動生成された Address クラスは partial として定義されているところに注目してください。partial なので、上のコードのように別のクラスファイルに Address クラスを定義してそれに MetadataTypeAttribute Classを付与することができます。(自動生成された Address クラスに手を加える必要はありません)

MetadataType 属性の引数に typeof(AddressMetaData) を設定することにより、メタデータクラス AddressMetaData を Address クラスに関連付けています。

以上の設定により、データアノテーション属性による検証が、クライアント側とサーバー側の両方で行われるようになります。

さらに、Display 属性も追加していますので、表示名が Display 属性の Name プロパティで設定した通りになります。

Tags: ,

Validation

LocalDB で Northwind と Pubs を利用

by WebSurfer 2017年5月16日 20:43

Microsoft が提供するサンプルデータベース Northwind と Pubs を LocalDB にアタッチして使うにはどうすればいいかということを書きます。

VS2015 から Pubs に接続

今年の 1 月に買った Windows 10 Pro 64-bit のノート PC に Visual Studio 2015 Communuty Update 3(以下、VS2015 と書きます)をインストールして使い始めました。

自分の環境では、VS2015 をインストールした際 SQL Server 2016 Express LocalDB(以下、LocalDB と書きます)も同時にインストールされたので、特に何もしていないのですが LocalDB が使えます。

VS2015 のテンプレートを使ってフォーム認証用の ASP.NET プロジェクトを作ると、ASP.NET Identity 用のデータベース(.mdf, .ldf ファイル)が EF Code First と LocalDB を使って App_Data フォルダに自動生成されるということで、LocalDB を使うのが VS2015 でのデフォルトのようです。

というわけで、少なくとも開発の初期段階では LocalDB を使うのが便利だと思って自分も使い始めました。

でも、実は最近になって、SQL Server Express が使えれば、LocalDB を使う必要はなさそうだと思い始めているのですが。LocalDB の使い方を勉強する時間がもったいないかも。(笑)

(上に書いた EF Code First で DB を生成するのは SQL Server Express でもできます。プロジェクトが生成された直後に web.config の接続文字列を SQL Server Express を使うように変更すれば、DB を生成して SQL Server Express の既定のインスタンス(または名前付きインスタンス)にアタッチしてくれます)

(もう一つ、ローカル IIS を使って開発を行う場合、LocalDB は使えないという問題があります。詳しくは MSDN Blog の記事「Using LocalDB with Full IIS, Part 1: User Profile」を見てください。その記事のPart 2「Using LocalDB with Full IIS, Part 2: Instance Ownership」に書いてあるような解決方法はあるそうですが、無理に LocalDB を使うより SQL Server Express を使った方がよさそうだと思います)

SQL Server Express が使えれば、LocalDB で Northwind と Pubs を使う必要はなさそうです。でも、せっかく考えたので書いておきます。この先 LocalDB しか使えないという環境もあるかもしれませんし。

さて、Northwind と Pubs ですが、それらは Microsoft が無償で提供している SQL Server 2000 用のサンプルデータベースです。MSDN ライブラリなどのチュートリアルでよく利用されており、自分もお世話になっています。

ファイルは Microsoft のダウンロードサイト Northwind and pubs Sample Databases for SQL Server 2000 から SQL2000SampleDb.msi というインストーラー形式で入手できます。

インストーラーを実行すると C:\SQL Server 2000 Sample Databases というフォルダ下に .mdf ファイル、.ldf ファイル、.sql ファイルが生成されます。

VS2015 から LocalDB を利用する場合、MSSQLLocalDB という名前の自動インスタンス(他に名前付きインスタンスというのもあるそうです)に特定の .mdf ファイル名を接続文字列の AttachDbFileName に指定してアタッチし、それに接続するのが一般的だと思います。

しかしながら、ダウンロードした NORTHWND.MDF や PUBS.MDF をアタッチしようとすると、SQL Server 2000 の .mdf ファイルなので SQL Server 2016 と互換性がないため、以下のようなエラーとなって失敗します。(この画像の前に「データベースを今すぐアップグレードしますか?」と出るので、そこで[はい]をクリックした結果です)

エラーメッセージ

ダウンロードした NORTHWND.MDF や PUBS.MDF を SQL Server 2016 と互換性を持つようにアップグレードできないかいろいろ調べましたが、自分が調べた限りでは方法は見つかりませんでした。

上の画像のエラーメッセージ "You must re-create the database" の通りデーターベースを作り直す他方法はなさそうです。

NORTHWND.MDF や PUBS.MDF と一緒にダウンロードされたスクリプトファイル instnwnd.sql(Northwind 用)と instpubs.sql(Pubs 用)を使えば作り直すことが可能です。問題はどのようにスクリプトを実行するかです。

実は知らなかったのですが、SQL Server Management Studio(以下、SSMS と書きます)を LocalDB に接続できるのでした。SSMS を利用すれば容易にスクリプトを実行できます。

下の画像は SQL Server 2008 の SSMS ですが、LocalDB に接続し、赤丸で囲った[ファイルを開���]アイコンをクリックして instnwnd.sql を開いたところです。

SSMS を LocalDB に接続

スクリプトには SQL Server 2016 では使用できないストアドプロシージャ sp_dboption が使われていますので、実行する前にその部分の修正が必要です(instnwnd.sql に 2 行、instpubs.sql に 1 行あります)。上の画像の赤の四角で囲った部分が instnwnd.sql の場合の訂正箇所です。

ストアドプロシージャの説明は MSDN ライブラリの sp_dboption (Transact-SQL) を見てください。チュートリアルに使うだけならコメントアウトするだけでもいいと思います。

同等の設定にするなら、alter database <データベース名> set recovery simple に書き換えればよさそうです。

訂正してから[実行(X)]をクリックするとスクリプトが走ってデーターベースが生成され、SSMS でデータベースを開いて操作できるようになります。

データベースが生成されると VS2015 からも接続できるようになります。下の画像は VS2015 のサーバーエクスプローラーからデータ接続の追加で、LocalDB 上に生成してアタッチされた Pubs に接続しているところです。

VS2015 を LocalDB に接続

上の画像で[データソース(S)]が既定のインスタンス(または名前付きインスタンス)に接続するためのものになっており、[サーバー名(E)]が LocalDB の MSSQLLocalDB という名前の自動インスタンスになっている点に注意してください。

それらが正しく選択されていれば[データベース名の選択または入力(D)]に上の画像のように接続可能な候補一覧が表示されるはずです。ここで Pubs を選択して[OK]をクリックした結果が一番上の画像です。

もちろんアプリケーションからも接続できます。既定のインスタンス(または名前付きインスタンス)に接続する時と同様な接続文字列で、Data Source を (LocalDB)\MSSQLLocalDB にすれば OK です。

Tags: , ,

DevelopmentTools

About this blog

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

Calendar

<<  2024年4月  >>
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011

View posts in large calendar