カスタムコントロールにおけるリソースの使い方は、先の記事 多言語対応カスタムコンロトール に書きました。今回は .aspx ページでリソースを使う場合について書きます。

ほとんどのことは以下のページ(後者の方が詳しいですが前者のほうが文章が平易で分かりやすいかも)に書いてありますので、わざわざここで書く必要もないかもしれませんが、自分が気がついた注意点などを書いておきます。
Web サイトプロジェクトの場合、リソースファイルは以下の 3 つの場所に配置できます。(Web アプリケーションプロジェクトでは、それに加えてプロジェクト内の任意の場所に置くことができます)
-
App_GlobalResources フォルダ
-
App_LocalResources フォルダ
-
独立したリソースアセンブリ
上のそれぞれについて、以下に備忘録的なことを書いておきます。
(1) App_GlobalResources フォルダ
Web アプリ全体で利用するためのリソースファイルは、アプリケーションルート直下に App_GlobalResources フォルダを作りその中に配置します。
例として、上の画像のように、MyResource.resx という名前のリソースファイルを配置し、それに Welcome という key 名で文字列を保存することを考えます。(上の画像には BookTitle というのもありますが)
Web アプリケーションプロジェクトでは App_GlobalResources フォルダにリソースファイルを追加すると、xxx.Designer.cs という名前(xxx は拡張子無しのリソースファイル名)の「厳密に型指定されたリソースクラス」が自動的に生成されますが、Web サイトプロジェクトの App_GlobalResources フォルダにリソースファイルを追加した場合はそうならない点に注意してください。
しかしながら、Visual Studio からは見えないものの、見えないところで Resources という名前空間に MyResource という「厳密に型指定されたリソースクラス」が定義されます。
従って、以下の画像のようにコードを入力する際にインテリセンスが働いて、定義済みの候補(画像の例では Resources 名前空間にある MyResource クラス)が表示されます。

key 名の Welcome はプロパティとして定義されているので、それに該当するリソースは Resources.MyResource.Welcome というようにして取得できます。
上記の方法の他に、How to: Retrieve Resource Values Programmatically に書かれていますように、GetGlobalResourceObject メソッドでもリソースを取得できます。
下の (2) に書いた HTTP ハンドラのように、一つの共通プログラムでリソース名と key 名を切り替えて画像を取得するような場合は、「厳密に型指定されたリソースクラス」より GetGlobalResourceObject メソッドを使うのがよさそうです。
さらに、上に紹介した URL の記事にあるとおり、.aspx ページに直接 $Resources 式を埋め込んで取得することもできます。文字列を取得するならそれが一番簡単で、本筋だと思われます。
(2) App_LocalResources フォルダ
特定のページで利用するリソースファイルは、そのページのあるフォルダ下に App_LocalResources フォルダを作ってその中に配置します。
リソースファイルの名前の付け方には制約があって、例えば、Default.aspx というページから利用するリソースファイルの名前は Default.aspx.resx となります。
(マイクロソフト公式解説書「プログラミング Microsoft ASP.NET 4」の p.315 によると リソースファイルの名前にフォルダ名をつけると、フォルダレベルで適用可能となるリソースファイルになるとのことです。ただし、自分が試した限りですが、$Resources 式や meta 属性を使って取得できないなど、あまり利用価値はなさそうなのでその話は割愛します)
上の名付けルールにより、特定のリソースファイルを特定の .aspx ページに関連付けているようです。例えば、Default.aspx ページで $Resources 式を使う場合、Text="<% $Resources:Welcome %>" とすれば Default.aspx.resx リソースファイルの中の Welcome という key 名のリソースを取得できます。
(逆に、Text="<% $Resources:Default.aspx,Welcome %>" のようにすると、グローバルリソースを探しに行くらしく、リソースが見つからないというエラーになります)
プログラムでリソースを取得することもできます。その場合、GetLocalResourceObject メソッドを使用します。
自分が試した限りですが、HttpContext クラスの HttpContext.GetLocalResourceObject メソッド を使うと、第 1 引数に .aspx ページの仮想パス(例えば下の画像のようなフォルダ構造で、Default.aspx.resx の中のリソースを取得したい場合は "~/TestFolder/Default.aspx")、第 2 引数にリソースの key 名を指定して、特定のページ以外のどこからでも、どこにあるローカルリソースでも取得できるようです。
ただ、ほとんどのケースでは .aspx ページから直接 $Resource 式や meta 属性を使って取得すれば済むはずで、どのようなケースでプログラムで取得するような必要があるかが疑問です。
自分が思いつくのはリソースが画像の場合ぐらいです。

例えば、上の画像のように Jpeg などの画像ファイルがリソースに含まれていて、それを ImageButton に表示するというような場合です。
その場合は $Resources 式を使って ImageUrl="<% $Resources:JpegSample %>" のようにしてもダメです。html にレンダリングされると src="System.Drawing.Bitmap" となってしまいます。
ImageUrl プロパティは html ソースでは src 属性になり、それに設定された url をブラウザが Web サーバーに要求に行くので、url 参照で取得できるようにしなければなりません。
それには HTTP ジェネリックハンドラを利用できます。上の画像にある ResourceImageHandler.ashx を以下のように定義して、それを ImageButton の ImageUrl に設定すれば OK です。
<%@ WebHandler Language="C#" Class="_ResourceImageHandler" %>
using System;
using System.Web;
using System.Drawing;
using System.Drawing.Imaging;
public class _ResourceImageHandler : IHttpHandler
{
public void ProcessRequest (HttpContext context)
{
string rsc = context.Request.QueryString["resource"];
string key = context.Request.QueryString["key"];
if (!String.IsNullOrEmpty(rsc) &&
!String.IsNullOrEmpty(key))
{
Bitmap bmp = (Bitmap)HttpContext.
GetLocalResourceObject("~/TestFolder/" + rsc, key);
if (bmp != null)
{
context.Response.
Cache.SetCacheability(HttpCacheability.Public);
context.Response.
Cache.SetExpires(DateTime.Now.AddYears(1));
context.Response.ContentType = "image/jpeg";
context.Response.Clear();
bmp.Save(context.Response.OutputStream,
ImageFormat.Jpeg);
}
}
}
public bool IsReusable
{
get
{
return false;
}
}
}
(3) 独立したリソースアセンブリ
この方法を取ることはまずないと思いますが・・・
Web アプリとは別にプロジェクトを追加し、その中にリソースファイルを配置することも可能です。
例として、ClassLibrary2 という名前で新しいプロジェクトをソリューションに追加し、そのプロジェクトに Resource1.resx という名前のアセンブリリソースファイルを追加することを考えます。
Visual Studio で Resource1.resx を開くと、画面の右上に[アクセス修飾子(I):]がドロップダウン式で選択できるようになっていますが、それを Public にするのを忘れないようにしてください(デフォルトは Internal)。
自動生成される Resource1.Designer.cs ファイルの中に、名前空間名 ClassLibrary2 下に厳密に型指定されたリソースクラス Resource1 が定義されます。
リソースファイルに文字列、テキストファイル、画像などを保存すると、Resource1 クラスに、保存したリソースを取得するためのプロパティが自動的に設定されます。
従って、Web アプリで ClassLibrary2 を参照設定しておけば(そうすると、ソリューションをビルドした時、Web アプリの Bin フォルダに ClassLibrary2.dll が自動的に配置されます)、Web アプリのコードからは ClassLibrary2.Resource1.<プロパティ名> でリソースファイルに保存したリソースを取得できます。
なお、自分の環境で検証した限りですが、GetGlobalResourceObject メソッドや $Resources 式ではリソースを取得できないので注意してください。
-------- 2016/6/17 追記 --------
サテライトアセンブリを追加して多言語対応する場合は Culture, UICulture を "auto" に設定するのを忘れないようにしてください。
Culture, UICulture を "auto" に設定すると、ASP.NET は、ブラウザから送信されてくる要求ヘッダに含まれる Accept-Language の設定を調べて、その要求を処理するスレッドのカルチャを Accept-Language に設定されているカルチャに書き換えます。
そして、リソースマネージャが実行時に、Thread.CurrentUICulture などで得られる CultureInfo(現在の要求を処理しているスレッドのカルチャ情報)を参照してローカライズされたリソースを検索し、UI に表示されるテキストを取得するという仕組みになっています。
Culture, UICulture を "auto" に設定するのを忘れるとブラウザの言語設定は無視されます。デフォルトではシステムのロケールに該当するカルチャがスレッドに設定されますので、例えば日本語 OS で xxx.ja-JP.resx というリソースがあれば、常にそれから UI に表示されるテキストを取得します。
Web サイトが日本語専用でサーバーも日本にあれば忘れても問題ないかもしれませんが、ホスティングサービス(Azure も含む)でサーバーが外国にある場合は Culture, UICulture を "auto" に設定するのを忘れると問題が出ると思います。