WebSurfer's Home

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

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

識別子名に日本語

by WebSurfer 2015年3月17日 17:23

変数、メソッド、プロパティなどの識別子名に、見易さや保守性を考えて日本語を使うという話を時々聞きますが、言語仕様上許されているからと言って安易に使うと思わぬ副作用がありそうという話を書きます。

自分が気がついた例に限っての話ですが、それは ASP.NET MVC のモデルのプロパティ名、コントローラーのアクションメソッドの引数名です。

モデルのプロパティ名は html ソースでは name 属性に設定され、クライアント側でのユーザー入力の検証やサーバー側でのモデルバインディングに使われます。

具体的には次の通りです。

先の記事 コレクションのデータアノテーション検証 で紹介したモデル / ビュー / コントローラーのコードを見てください。そのモデルには CountryList と Name というプロパティ名が使われています。それを元に以下のようなビューを書くと、

@Html.LabelFor(m => m.CountryList[i].Name)
@Html.EditorFor(m => m.CountryList[i].Name)
@Html.ValidationMessageFor(m => m.CountryList[i].Name)

生成される html ソースは以下のようになります。

<label for="CountryList_0__Name">国名</label>
<input class="text-box single-line" 
  data-val="true" 
  data-val-length="国名 は 15 文字以内" 
  data-val-length-max="15"
  data-val-required="国名 は必須" 
  id="CountryList_0__Name"
  name="CountryList[0].Name" 
  type="text"
  value="Italy" />
<span class="field-validation-valid" 
  data-valmsg-for="CountryList[0].Name" 
  data-valmsg-replace="true">
</span>

上記の CountryList と Name が日本語になった場合、jQuery ライブラリを使ったクライアント側での検証がうまく動くのか、ブラウザで form が submit されると き name 属性がどうなるか、サーバー側できちんとモデルバインディングされるかが気がかりです。

モデルのプロパティ以外で思いつくものとしてはアクションメソッドの引数名があります。これもサーバー側でのモデルバインディングに関係します。

他にも予期できない副作用があるかもしれません。十二分に検証すれば済むかもしれませんが、そもそも余計な気苦労と不要な手間です。そこまでして日本語を使う理由はなさそうです。

クライアント側に影響がありそうな ASP.NET MVC アプリの識別子名で、日本語を使っても問題なさそうなのはコントローラーのアクションメソッド名ぐらいでしょうか?

例えば、以下のようにアクションメソッド名は日本語でも、ActionNameAttribute で英語名 Countries を付与すればブラウザからは Countries で呼べます。

[ActionName("Countries")]
public ActionResult 国リスト取得()
{
  return View();
}

でも、やっぱり日本語を使うのは個人的にはお勧めではないです。例えば、Countries でなら呼べますが Countries では 404 エラーです。違いが分かりますか?

答は、名前の最後の s が半角 / 全角の違いということです。

そんなエラーはすぐ気がつくと思われるかもしれませんが、たとえ 1 分で気がついたとしても時間と労力の無駄には変わりないということで、やはり日本語は使用しない方がよいと自分は思います。

Tags: ,

.NET Framework

About this blog

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

Calendar

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

View posts in large calendar