ASP.NET MVC アプリケーションをデバッグ実行したときに「CultureNotFoundException」が発生する現象を回避する

環境

  • Visual Studio 2013
  • .NET Framework 4
  • ASP.NET MVC 3 (or 4)

現象

いつのころからか、ASP.NET MVC アプリケーションをデバッグ実行しようとすると図のような「CultureNotFoundException」の例外が発生する様になりました。

1

このまま続行して動かしても全く問題ないのですが、デバッグ実行するたびに出てくるので (100% ではないが7~8割) 結構煩わしいです。

海外サイトを調べてみると「C:WindowsMicrosoft.NETFrameworkv4.0.30319Temporary ASP.NET Files」フォルダを削除すると直るような記述はあったのですが、それっぽいフォルダが削除してみましたが直りませんでした。(フォルダ名が若干違う)

現状例外を直接発生しないようにする方法が見つからなかったので、ここでは例外が発生してもそのまま無視できる方法について記載しています。この方法を試す場合は、例外を無視しても問題ない場合に行ってください。

手順

Visual Studio を起動したらメニューの「デバッグ」から「例外」を選択します。

2

右の「追加」ボタンをクリックします。

3

「新しい例外」ダイアログが表示されたら、型から「Common Language Runtime Exceptions」を選択し、例外名に「System.Globalization.CultureNotFoundException」と入力して OK ボタンをクリックします。

4

一覧に「System.Globalization.CultureNotFoundException」が表示されていることを確認し、例外が発生しても一時停止されないように「スローされるとき」のチェックを外します。OK ボタンをクリックし、実行しても例外で一時停止されないか確認してみてください。

5

]]>

ホームページを多言語対応しました

ホームページ「ソーサリーフォース.net」を多言語化対応してみました。対応している言語は列挙するのがちょっと大変なので書きませんが大体40か国語ぐらいに対応しています。主要な言語については大体サポートしているつもりです。

1年前に英語には対応していましたが、なんとなくやってみた程度だったので、半分ぐらいは翻訳されていない状態でした。最近プログラムの方をさわる機会ができたので今回一気に多言語対応しました。ただ、ページによってはそもそも多言語化する必要のないところもありますので、そこはそのままにしてあります。

多言語化の仕組みなんですが、sorceryforce.net ではサーバープログラムに ASP.NET MVC を採用しています。ASP.NET MVC にはローカライズの仕組みがあり普通に多言語化する分にはこれを使って作ります(作ると思います)。

ただ、調べてみると言語が増えるたびにファイルが増えて行くというなんとも嫌らしい構成になるらしく、数言語ぐらいならまだしも全世界対応ってなると相当な数になるみたいです。ビューを言語ごとに作成するとなると、レイアウトとか変更するたびに全ファイル修正しなければならなくなり、ビューの修正が億劫になってしまいます。私も Windows From でちょろっと多言語化やってみたのですが、同じように Windows Form のデザインを言語ごとに作る必要があり、とてもめんどくさそうなイメージがありました。まあ、ASP.NET MVC ならビューの継承、Windows Form ならフォームの継承を使ってある程度はごまかせるのですが、テキストが右⇒左方向だったり、翻訳語のテキストが長くなったり、文字の大きさが変わったりするので結局は微調整に時間を取られそうな感じがしますね。Windows Form は問題が顕著になりますが、Web ならレイアウトはある程度ブラウザが補間してくれるのでなんとかなりそうな気がしますが。

で、そんなこんな考慮してみて、最終的には多言語対応はすべて自前で実装しました。Visual Studio のリソース入力もめんどそうだったので、テキストはすべて Excel で管理し、日本語さえ入力すれば後は半自動的に他の言語に翻訳してくれるようにしました。半自動っていうのは、テキストを入力した後に頻繁に翻訳されても困るので、入力した後にバッチ叩けばプログラムにまで自動反映されるよー、っていう感じにしてあります。

実際にビューに出力する方法としては、テキストはすべて ID 管理されているため、.cshtml とかで @Model.GetMessage(なんちゃらID) のようにしておけば、後はブラウザの言語を判定して対応する言語で表示されるようになっています。ID は列挙体なので、ID 名でどんなテキストかわかんなくてもマウスカーソルを合わせれば summary で元の日本語が表示されるのでとても便利です。

あと、この仕組みはプラットフォームに依存しないようにしてあるので、ASP.NET でなくても、.NET Framework であれば他のアプリケーションにも適用できるようになっています。クライアントツールとかゲームとか Windows ストアアプリとか。まあ、まだホームページにしか使ってないので、機会があれば他のソフトも多言語対応してみたいですね。

ちょっといろいろ長くなってしまい、また内部的な話になっていしましたが、要はホームページを多言語化対応しました、という話でした。

]]>

[ASP.NET MVC] FilePathResult でファイルを返すと IE8 以前のブラウザで意図しないファイル名でダウンロードされる

アクションメソッド内で File メソッド (FilePathResult) を使用してファイルを返している処理があったのですが、IE でファイルをダウンロードした際にすべて同じファイル名でダウンロードされてしまうという報告を受けたので調べてみました。

理由については「ASP.NET MVC2 の FileContentResult で日本語ファイル名で返すと IE で日本語ファイル名にならない」のリンク先を見てもらえればそのままなのですが、IE8 以前だと、File メソッドの fileDownloadName 引数にファイル名を指定してもアクション名でファイル名が返ってしまうみたいです。

対応策はやはりリンク先のようにクラスを派生させて対応できました。私の場合はローカルにあるファイルからダウンロードさせるようにしていたので「FilePathResult」クラスから派生させています。コードは下のような感じです。(クラス名は適当なので気にしないでください)

/// <summary>
/// IE 8 以前でファイル名を正常に返せない不具合に対応した FilePathResult。
/// </summary>
public class FilePathResultEx : FilePathResult
{
    public FilePathResultEx(string fileName,
                            string contentType,
                            string fileDownloadName)
        : base(fileName, contentType)
    {
        base.FileDownloadName = fileDownloadName;
    }
    public override void ExecuteResult(ControllerContext context)
    {
        // IE 以外では基底の処理に任せる(RFC2231)
        if (context.HttpContext.Request.Browser.Browser != "IE")
            base.ExecuteResult(context);
        else
        {
            var fileName = this.FileDownloadName;
            fileName = HttpUtility.UrlEncode(fileName).Replace("+", "%20");
            var response = context.HttpContext.Response;
            response.ContentType = this.ContentType;
            response.AddHeader("content-disposition", "attachment; filename=" + fileName);
            this.WriteFile(response);
        }
    }
}

 

アクションメソッドでは下のように変更して返します。

string localFilePath = @"<ローカルのファイルパス>";
string contentType = "<ファイルのコンテンツタイプ>";
string downloadFileName = "<ダウンロードさせるファイル名>";
// 下のメソッドはやめる
//return File(localFilePath, contentType, downloadFileName);
// ヘッダーを変更したレスポンスを返す FilePathResul 拡張クラスで対応
return new FilePathResultEx(localFilePath, contentType, FileName);

]]>

[ASP.NET MVC] Release ビルドでコントローラークラスが消える…?

実はさっきまで sorceryforce.net のルートページを開こうとしてもエラーになっていました。原因ははっきりとはしていないのですが、HomeController クラスで何らかの処理をさせようとすると NullRefenenceException で落ちるというエラーが発生していました。単純に Index メソッド内で「int a = 0;」とさせるだけでも Null 参照で落ちます。おそらく HomeController のインスタンス自体が null になっているような感じでした。(C++言語なんかだと自分自身のインスタンスを delete するなんて裏ワザもあったりするんですけどね。インスタンス要素にアクセスしなければ処理は継続できるという)

私が想定する原因としては Release ビルドを行う際にコードの最適化が実行されるのですが、HomeController クラスに処理を入れた場合でも、HomeController クラスは実際にはどこからも参照されていないクラスなので最適化した際にごっそり削られたのではないかと思っています。ためしに「コードの最適化」のチェックを外して Web サイトにアップロードすると問題なく動作します。なので今は適当なクラスで HomeController クラスのインスタンスを生成するコードを入れておいてビルドするようにしています。こうするとコードの最適化が実行されても HomeController クラスは正常に動作します。でも他のコントローラークラスは正常に動作するんですよね…。

]]>

[ASP.NET MVC] Redirect メソッドと RedirectPermanent メソッドの違い

Redirect メソッドと RedirectPermanent メソッドはどちらも同じ別ページに遷移するためのメソッドですが、クライアントに返すステータスが異なります。Redirect メソッドが「302 (Moved Temporarily)」を返し、RedirectPermanent メソッドが「301 (Moved Permanently)」を返します。

これはサイトを閲覧しているユーザーのためというよりは検索エンジンのクローラーに対してのメッセージとして使われます。302 は一時的に生成したページに遷移するという意味を持ち、クローラーはそのリダイレクト先の情報を収集しようとはしません。301 は古いページから新しいページに移動させるためのリダイレクトとして解釈され、クローラーは新しいリダイレクト先の情報を収集します。(収集しない、すると明言して書いていますが、実際の挙動はクローラーに依存します)

ですので、ユーザー登録のための画面や一時的なダウンロードページなど、クローラーに収集されたくないページへリダイレクトする場合は「Redirect メソッド」、遷移先のページをクロールされてもいい場合は「RedirectPermanent メソッド」のように使い分けをします。

]]>

ASP.NET MVC で AsyncOperationManager.CreateOperation メソッドを呼び出すと ActionResult が効かなくなる

理由はわかりませんがクライアントから何らかのリクエストを受け、アクション処理実行中に AsyncOperationManager.CreateOperation メソッドを呼び出すと ViewResult や FileContentResult を返してもクライアントでは何も反応しなくなります。エラーや例外が発生するわけではないのでなかなか原因の発生元がわかりづらいので注意です。

]]>