WebSurfer's Home

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

aspx ページでのリソースの利用

by WebSurfer 2015年4月21日 21:22

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

リソースファイル

ほとんどのことは以下のページ(後者の方が詳しいですが前者のほうが文章が平易で分かりやすいかも)に書いてありますので、わざわざここで書く必要もないかもしれませんが、自分が気がついた注意点などを書いておきます。

Web サイトプロジェクトの場合、リソースファイルは以下の 3 つの場所に配置できます。(Web アプリケーションプロジェクトでは、それに加えてプロジェクト内の任意の場所に置くことができます)

  1. App_GlobalResources フォルダ
  2. App_LocalResources フォルダ
  3. 独立したリソースアセンブリ

上のそれぞれについて、以下に備忘録的なことを書いておきます。

(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" に設定するのを忘れると問題が出ると思います。

Tags: , ,

ASP.NET

ASP.NET MVC でトレース情報の表示

by WebSurfer 2015年4月6日 21:52

ブラウザからトレースビューア Trace.axd を要求したとき表示される[トレース情報]に、System.Diagnostics.Trace クラス(Page.Trace プロパティで取得できる TraceContext オブジェクトではない点に注意)を利用して書き込んだトレースメッセージを表示する方法を書きます。

トレースビューアの表示

この記事の例では、一つのソリューションの中に二つのプロジェクトがあって、一方が ASP.NET MVC4 の Web アプリケーション、他方がデータベースなどからデータなどを取得する中間ビジネス層のクラスライブラリを考えます。また、View に Razor 構文を使用することとします。

ASP.NET Web Forms アプリケーションの例は MSDN ライブラリの チュートリアル : ASP.NET トレースと System.Diagnostics トレースの統合 に詳しく書いてあります。(現時点の記事には、「Web フォームのトレース メッセージを書き込むには」のセクションのコード Trace.WriteLine が Trace.Write の間違い、WebPageTraceListener とコンパイラのバージョンが古いなどの問題があるので注意してください)

これと同様なことを MVC4 Razor 構文のアプリケーションで行ってみます。ちなみに、この記事の検証に使った環境は、Vista SP2, ASP.NET 4, VS2010 Professional, MVC4 インターネットアプリケーションテンプレートで作成、IIS7 統合パイプラインモード上で実行、ブラウザは IE9 です。

(1) クラスライブラリ(Model 相当)

チュートリアルにある AuthorClass ビジネスオブジェクトに相当するクラスライブラリは、以下のコードのとおり実装しました。

MVC アプリケーションの Model として利用しやすいように、Author クラスを定義し、List<Author> 型のオブジェクトを作成して渡すようにしています。チュートリアルとは違って、Authors.xml ファイルは使用していません。

トレースメッセージは、チュートリアルと同様に、AuthorClass オブジェクトの生成時点と AuthorClass.GetAuthors メソッドの呼び出し時点で Trace.Write メソッドを使って書き込みます。

加えて、非同期で呼び出したメソッドでトレースメッセージを書き込んだ場合にはどうなるかを検証するため、AsynchronousMethod を追加し、非同期で呼び出してみました。(結果は、上の画像の通りトレースビューアには表示されません)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace AuthorClassLibrary
{
  public class Author
  {
    public string au_id { get; set; }
    public string au_lname { get; set; }
    public string au_fname { get; set; }
    public string au_phone { get; set; }
  }

  public delegate Author WriteTraceMessage();
    
  public class AuthorClass
  {        
    public AuthorClass()
    {
        // トレースメッセージ書込み
        System.Diagnostics.Trace.Write(
            "AuthorClass is created.", "AUTHORCLASS TRACE");
    }

    public List<Author> GetAuthors()
    {
      // トレースメッセージ書込み
      System.Diagnostics.Trace.Write(
          "GetAuthors called.", "AUTHORCLASS TRACE");
            
      List<Author> authors = new List<Author>() {
        new Author { au_id = "172-32-1176", au_lname = "White",
            au_fname = "Gerry", au_phone = "408 496-7223" },
        new Author { au_id = "172-32-1176", au_lname = "Green",
            au_fname = "Marjorie", au_phone = "415 986-7020" }
      };

      // AsynchronousMethod を非同期呼び出し
      WriteTraceMessage asyncCall = 
          new WriteTraceMessage(AsynchronousMethod);
      IAsyncResult ar = asyncCall.BeginInvoke(null, null);
      Author author = asyncCall.EndInvoke(ar);
      authors.Add(author);

      return authors;
    }

    // 非同期で呼び出すメソッド
    static Author AsynchronousMethod()
    {
      // トレースメッセージ書込み(Trace.axd では表示されない)
      System.Diagnostics.Trace.Write(
          "AsynchronousMethod called.", "AUTHORCLASS TRACE");

      Author author = new Author() {
          au_id = "123-45-6789", au_lname = "太郎",
          au_fname = "日本", au_phone = "000 111-2222" };

      return author;
    }
  }
}

(2) Controller

MVC4 インターネットアプリケーションテンプレートで自動生成される HomeController に、以下のように Authors アクションメソッドを追加します。

ブラウザから Home/Authors が呼び出されたときに Trace.Write メソッドを使ってトレースメッセージを書き込むようにしています。

また、このメソッドの中で、上のクラスライブラリの AuthorClass コンストラクタと AuthorClass.GetAuthors メソッドが呼び出され、トレースメッセージが書き込まれることに注意してください。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace Mvc4App.Controllers
{
  public class HomeController : Controller
  {

    // ・・・中略・・・

    public ActionResult Authors()
    {
      System.Diagnostics.Trace.Write(
        "Home/Authors method called.", "CONTROLLER TRACE");
            
      ViewBag.Message = "System.Diagnostics Trace の統合。";

      AuthorClassLibrary.AuthorClass authors = 
                new AuthorClassLibrary.AuthorClass();
      return View(authors.GetAuthors());
    }
  }
}

(3) View

View においても、以下のコードのように、Trace.Write メソッドを使ってトレースメッセージを書き込むことができます。

@model IEnumerable<AuthorClassLibrary.Author>

@{
    ViewBag.Title = "ASP.NET MVC Trace";
    Layout = "~/Views/Shared/_Layout.cshtml";
    
    System.Diagnostics.Trace.Write(
        "Home/Authors.cshtml called.", "VIEW TRACE");
}

<hgroup class="title">
    <h1>@ViewBag.Title.</h1>
    <h2>@ViewBag.Message</h2>
</hgroup>

<table>
@foreach (var item in Model) {
    <tr>
        <td>
            @Html.DisplayFor(modelItem => item.au_id)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.au_lname)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.au_fname)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.au_phone)
        </td>        
    </tr>
}
</table>

Trace.Write メソッドで書き込んだトレースメッセージが出力されるようにするには、Visual Studio で当該プロジェクトの Property を開き、その[ビルド]タブの[TRACE 定数の定義(T)]にチェックマークを入れる必要があります。

[トレース定数の定義]の設定

View については更なる設定が web.config に必要です(上の画像の[トレース定数の定義]の設定だけでは View の Trace.Write メソッドは無視されます)。

具体的には、C:\Windows\Microsoft.NET\Framework\v4.0.30319\Config\web.config の中の complier 要素を、アプリケーションルート直下の web.config にコピーして、それに compilerOptions="/define:TRACE" を追加します。

詳しくは、MSDN ブログの記事 Tracing in ASP.NET MVC Razor Views を見てください。チュートリアルの「トレースを有効にしてアプリケーションを自動コンパイルするには」のセクションにも同様なことが書かれていますが、こちらはバージョンが古いので注意してください。

今回の検証で使った環境では以下のようになります。

<system.codedom>
    <compilers>
        <compiler 
            language="c#;cs;csharp" 
            extension=".cs" 
            warningLevel="4" 
            compilerOptions="/define:TRACE" 
            type="Microsoft.CSharp.CSharpCodeProvider, 
                System, 
                Version=4.0.0.0, 
                Culture=neutral, 
                PublicKeyToken=b77a5c561934e089">
            <providerOption 
                name="CompilerVersion" 
                value="v4.0"/>
            <providerOption 
                name="WarnAsError" 
                value="false"/>
        </compiler>
    </compilers>
</system.codedom>

さらに、System.Diagnostics.Trace クラスを使用して書き込んだトレースメッセージをルーティングして、ASP.NET トレース ビューア(Trace.axd)に表示されるようにするには、web.config で trace 要素を有効にし、WebPageTraceListener を追加する必要があります。

WebPageTraceListener の設定はチュートリアルの「構成でアプリケーションの WebPageTraceListener を追加するには」のセクションに書かれていますが、バージョンが古いようです。

WebPageTraceListener は System.Web.dll に含まれていますので、参照設定を見てそれと同じバージョンにするのがよさそうです。具体的には、今回の検証で使った環境では、以下の通りです。

<system.web>
    <trace enabled="true" pageOutput="true" 
        requestLimit="40" localOnly="false"/>
</system.web>

<system.diagnostics>
    <trace>
        <listeners>
            <add 
                name="WebPageTraceListener"
                type="System.Web.WebPageTraceListener, 
                System.Web, 
                Version=4.0.0.0, 
                Culture=neutral, 
                PublicKeyToken=b03f5f7f11d50a3a"/>
        </listeners>
    </trace>
</system.diagnostics>

上記の通り設定してブラウザから Home/Authors を呼び出すと以下のように表示されるはずです。

Home/Authors の表示

その後、ブラウザからトレースビューア Trace.axd を呼び出し、ブラウザに表示された Home/Authors 行の[詳細の表示]をクリックして[トレース情報]を見ると一番上の画像のようになっているはずです。

非同期呼び出しした AsynchronousMethod メソッドで書き込んだトレースメッセージ以外はトレースビューアに表示されているのがわかるでしょうか?

Tags: ,

MVC

ダウンロードの際に UpdateProgress を表示

by WebSurfer 2015年4月1日 16:30

ダウンロードするファイルをサーバー側で作成するのに時間がかかる場合、UpdatePanelUpdateProgress コントロールを使って、ファイル作成中であることをユーザーに知らせる方法を書きます。

UpdateProgress の表示

上の画像の一番上の行が表示された UpdateProgress です。この記事の例では、UpdateProgress が表示されるのはファイルをダウンロードしている時ではなく、その前のサーバー側でファイルを作成している時なのでご注意ください。

先の記事「ダウンロードは別ウィンドウで」に書いたような方法では、親ウィンドウの .aspx ページへの応答はすぐ帰ってくるものの、別ウィンドウを開いてそれから要求したダウンロード用 .aspx ページの応答はサーバー側でファイルの作成が完了するまで帰ってきません。

従って、ファイルの作成に時間がかかるとその間無反応になってしまい、ユーザーフレンドリーという面でどうかということになってしまいます。

少なくともサーバー側でファイルを作成している間その旨ユーザーに通知すれば、ユーザーのイライラも多少おさまるであろうということで、非同期要求と UpdateProgress を使った例を書いてみました。

どのような構成かを簡単に書くと、(1) .aspx ページに iframe と Button を含んだ UpdatePanel と UpdateProgress を配置、(2) Button クリックで非同期ポストバック、(3) サーバー側で Button クリックのハンドラでファイルを作成、(4) iframe の src 属性に作成したファイルを取得してダウンロードする HTTP ハンドラを設定・・・ということです。

そのようにすれば、非同期ポストバックをかけてから応答が帰ってくるまで UpdateProgress が表示され、応答が帰ってきて UpdatePanel 内が再描画されると、その中の iframe が src 属性に設定された HTTP ハンドラを要求し、HTTP ハンドラによってファイルがダウンロードされるという仕組みが作れます。

その .aspx ページのコード例は以下の通りです。

<%@ Page Language="C#" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<script runat="server">
    protected void Button_Click(object sender, EventArgs e)
    {
        // 時間のかかるファイルの作成
        System.Threading.Thread.Sleep(5000);
        
        // ファイルの作成が完了したら、UpdatePanel 内に配置し
        // た隠し iframe の src 属性に、作成したファイルを取
        // 得してダウンロードする HTTP ハンドラを設定する。
        // iframe には runat="server" 属性を付与してサーバー
        // コントロールにしている点に注意。非同期ポストバック
        // で UpdatePanel 内が再描画されると iframe から HTTP
        // ハンドラが要求されファイルがダウンロードされる。
        iframeDownload.Attributes["src"] = 
            "0104-TextFileDownloadHandler.ashx";
    }
</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
    <script type="text/javascript">
    //<![CDATA[
    var manager;

    function pageLoad(sender, args) {
        if (args.get_isPartialLoad() === false) {
            manager = 
              Sys.WebForms.PageRequestManager.getInstance();
            manager.add_beginRequest(OnBeginRequest);
            manager.add_endRequest(OnEndRequest);
        }
    }

    function OnBeginRequest(sender, args) {
        // 非同期要求の送信時に、アニメーションの表示などの
        // スクリプトを起動する場合はここに設定。
    }

    function OnEndRequest(sender, args) {
        // 完了時にスクリプトを起動する場合はここに設定。
    }

    // 実行中の非同期ポストバックを停止するスクリプト。
    // UpdateProgress に配置したボタンの onclick に設定。
    // サーバー側の処理まで停止されるわけではないので注意
    function AbortPostBack() {
        if (manager.get_isInAsyncPostBack()) {
            manager.abortPostBack();
        }
    }
    //]]>
    </script>
</head>
<body>
    <form id="form1" runat="server">
    <asp:ScriptManager ID="ScriptManager1" runat="server">
    </asp:ScriptManager>
    
    <asp:UpdateProgress ID="UpdateProgress1" runat="server" 
        AssociatedUpdatePanelID="UpdatePanel1">
        <ProgressTemplate>
            <asp:Image ID="Image1" runat="server" 
                ImageUrl="~/Images/grid-loading.gif" />
            しばらくお待ちください・・・
            <input type="button" onclick="AbortPostBack()" 
                value="Cancel" />
            <br /><br />
        </ProgressTemplate>
    </asp:UpdateProgress>
    
    <asp:UpdatePanel ID="UpdatePanel1" runat="server">
        <ContentTemplate>
            UpdatePanel
            <hr />            
            <%=DateTime.Now.ToString()%>
            <br />
            <asp:Button ID="Button1" runat="server" 
                Text="Download" OnClick="Button_Click" />
            <br />
            <%--ダウンロードに使う隠し iframe--%>
            <iframe id="iframeDownload" runat="server" 
                style="visibility:hidden" />
        </ContentTemplate>
    </asp:UpdatePanel>
    </form>
</body>
</html>

上のコード例には、実行中の非同期ポストバックを停止するためのスクリプトその他をオマケ(?)で加えておきました。UpdatePanel を使うと他にもいろいろできますので、興味がありましたら MSDN ライブラリの記事 PageRequestManager のイベントの処理 を見てください。

iframe の src 属性に設定する HTTP ハンドラのコード例は、先の記事「ダウンロードは HTTP ハンドラで」を見てください。

.aspx ページを使っても可能ですが、その記事に書きましたようにいろいろ問題がありますので、HTTP ハンドラを使ったほうがよさそうです。

Tags: ,

Upload Download

About this blog

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

Calendar

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

View posts in large calendar