WebSurfer's Home

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

Application_Start のデバッグ

by WebSurfer 2011年8月26日 22:08
Just in time debugger の起動

Global.asax の Application_Start メソッド内にブレークポイントを設定して、Visual Studio の F5(デバッグ開始)で実行しても、ブレークポイントでは停止しません。

その理由は、Application_Start のタイミングでは、まだデバッガが IIS のプロセスにアタッチされていないからだそうです。

ちなみに、開発サーバーの場合は Application_Start より前のタイミングでデバッガをアタッチするようで、 Application_Start 内に設定したブレークポイントで止まります。

IIS 上で Web アプリを動かしても、Application_Start をデバッグする方法はあるでしょうか?

調べてみたところ、任意のタイミングでデバッガをアタッチする方法があるようです。

それは、System.Diagnostics.Debugger.Break メソッド をコードに仕込んでおくことです。 Crtl + F5(デバッグなしで開始)で実行すると、デバッガがアタッチされていない場合はそこでダイアログ(上の画像参照)が表示され、デバッガを起動(下の画像参照)できます。

System.Diagnostics.Debugger.Break メソッドを使用して実行をブレーク

知ってましたか? 実は、自分は、Application_Start のタイミングではデバッガがアタッチされてないということも知らなかったです。(笑)

Tags: ,

ASP.NET

ViewStateUserKey

by WebSurfer 2011年8月19日 20:34

MSDN ライブラリの ASP.NET の組み込み機能を活用し、Web 攻撃を回避する で、ワンクリック攻撃を防ぐために、Page.ViewStateUserKey プロパティにセッション ID を設定することを推奨しています。

その場合、セッションが timeout(デフォルトで 20 分)すると ViewState の検証結果はどうなるでしょうか?

EnableViewStateMac が有効に設定してある場合(デフォルトで有効になっています)例外がスローされると思っていましたが、そうではなかったです。

timeout するとサーバーに保存されていたセッションデータは破棄されますが、セッション ID は書き換えられず、同じ ID が使用され続けます。即ち、Session["xxxxx"] に保存したデータは破棄されても、ブラウザとサーバ間のセッションは維持されるということのようです。

という訳で、ViewStateUserKey をセッション ID に設定してセッションが timeout しても、ViewState の検証結果は有効になります。

ちなみに、セッション ID が再発行されるのは、regenerateExpiredSessionId が有効で、かつ、cookieless モードが ture の場合のみだそうです。

知ってました? 自分は知らなかったです。(笑)

参考に、検証に使ったコードをアップしておきます。sessionState 要素の timeout 属性を短く設定して検証してみました。

<%@ 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">

  // timeout するとサーバーのメモリにあるデータは破棄されるが、
  // SessionID は書き換えらえず、同じ ID が使用され続ける。
  // 既定では、セッション ID は regenerateExpiredSessionId が
  // 有効な場合に cookieless モードに対してのみ再発行される。
  // したがって、timeout しても ViewState は有効。
  protected void Page_Init(object sender, EventArgs e)
  {
    ViewStateUserKey = Session.SessionID;
  }

  protected void Page_Load(object sender, EventArgs e)
  {
    if (!Page.IsPostBack)
    {
      Session["message"] = 
        "この文字列はセッションから取得しました。";
    }
    else
    {
      object obj = Session["message"];
      if (obj != null)
      {
        Label1.Text = (string)obj;
      }
      else
      {
        Label1.Text = "セッションが null です。";
      }
    }
  }
</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
  <title></title>
</head>
<body>
  <form id="form1" runat="server">
  <div>
    <asp:Button ID="Button1" runat="server" Text="Button" />
    <asp:Label ID="Label1" runat="server"></asp:Label>
  </div>
  </form>
</body>
</html>

-------- 2012/2/6 追記 --------

SessionState 情報を格納しない場合(Session["Data"] = xxxx; というようなコードが存在しない場合)、サーバーは SessionState 情報用のストレージを割り当てません。また、セッション Cookie も発行しません。(2014/7/26 追記:Global.asax に Session_Start ハンドラを追加すると話が違ってきます。詳しくは、Session_Start ハンドラの影響 を参照ください)

SessionID は SessionStateModule が生成しますが、Cookie が送られてこなければ SessionStateModule が生成する SessionID の値はリクエストのたび異なります。

なので、ViewStateUserKey = Session.SessionID; とすることにより、サーバーエラーになります。エラーメッセージは以下の通りです。

"System.Web.HttpException: viewstate MAC の検証フィールドです。このアプリケーションが Web Farm またはクラスタによってホストされている場合、<machineKey> 構成が同一の validationKey および検証アルゴリズムを指定していることを確認してください。AutoGenerate をクラスタで使用することはできません。"

Tags:

Validation

更新操作中の例外の処置

by WebSurfer 2011年8月16日 17:25

ユーザー入力を使ってデータベースの更新を行うケースで、ユーザー入力の検証結果が NG の場合、先にデータバインドコントロール(ここでは DetailsView を例にします)にユーザーが入力したデータを維持したままユーザーに再入力を促すにはどうすればよいかという話です。

ユーザーが入力したデータを維持したままユーザーに再入力を促す

ASP.NET には、ユーザー入力を検証するために RegularExpressionValidator, CustomValidator 等の検証のためのコントロールが用意されており、通常はこれらを組み合わせて使えば簡単に要件は満たせ��す。

ただし、検証するタイミングが更新の直前でなければならず、かつ DB ベースから値を取得して検証し、結果 NG ならロールバックしなければならないような場合は、上記の検証コントロールを使って目的を果たすのは難しいです。

その場合は、ObjectDataSource と型付 DataSet + TableAdapter を DetailsView と組み合わせた 3 層構造とし、検証やロールバック等の処理は TableAdapter を拡張したメソッドを作成して行うのが良いと思いますが、検証結果が NG の時 DetailsView にユーザーが入力したデータを維持するには工夫が必要です。

例えば、拡張したメソッドの戻り値などで検証結果 NG を判断して、DetailsView を挿入モードに保ってユーザーに再入力を促すことはできます。しかしながら、その前に DetailsView に DataBind が起こって、せっかくユーザーが入力したデータがすべて消えてしまいます。

それを避けるためには、拡張したメソッドで検証結果 NG と判定された場合は、そこで例外をスローすることです。ただし、スローしっぱなしではサーバーエラーで終わってしまうので、スローされた例外を処置しなければなりません。

処置と言っても、拡張したメソッド内で try - catch で行うのではありません。拡張したメソッドは、検証結果 NG の場合に特定の例外をスローするだけです。

処置は ItemInserted イベントのイベントハンドラに渡された DetailsViewInsertedEventArgs オブジェクトを使用して行います。

検証結果 NG でスローされた特定の例外を捕捉した場合のみ ExceptionHandled プロパティを true に設定することで例外は処置されたと見なされます。

以下に検証に使用した aspx ページのコードをアップしておきます。ArgumentNullException と NorthwindDataException のみを捕捉し、ExceptionHandled プロパティを true に設定しています。

<%@ Page Language="C#" %>
<%@ Import Namespace="ProductsDataSetTableAdapters" %>

<!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 Page_Load(object sender, EventArgs e)
  {
    MessageLabel.Text = String.Empty;
  }
    
  protected void DetailsView1_ItemInserted(object sender, 
    DetailsViewInsertedEventArgs e)
  {
    if (e.Exception == null && e.AffectedRows == 1)
    {
      MessageLabel.Text = "Insert に成功";
    }
    else
    {
      if (e.Exception != null)
      {
        Exception innerEx = e.Exception.InnerException;
        if (innerEx is ArgumentNullException ||
          innerEx is ProductsTableAdapter.NorthwindDataException)
        {
          MessageLabel.Text = innerEx.Message;
          e.ExceptionHandled = true;
        }
      }
      else
      {
        MessageLabel.Text = "例外の発生なし。";
      }            
      e.KeepInInsertMode = true;
    }
  }
    
  protected void DetailsView1_ItemUpdated(object sender, 
    DetailsViewUpdatedEventArgs e)
  {
    if (e.Exception == null && e.AffectedRows == 1)
    {
      MessageLabel.Text = "Update に成功。";
    }
    else
    {
      if (e.Exception != null)
      {
        Exception innerEx = e.Exception.InnerException;
        if (innerEx is ArgumentNullException ||
          innerEx is ProductsTableAdapter.NorthwindDataException)
        {
          MessageLabel.Text = innerEx.Message;
          e.ExceptionHandled = true;
        }
      }
      else
      {
        MessageLabel.Text = "例外の発生なし。";
      }       
      e.KeepInEditMode = true;
    }
  }

  protected void DetailsView1_DataBinding(object sender, 
    EventArgs e)
  {
    // 例外が出ない場合は DataBind がかかる。
    // 結果、ユーザー入力が消える。
    MessageLabel.Text += " DataBinding イベントが発生";
  }
</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
  <title></title>
</head>
<body>
  <form id="form1" runat="server">
  <div>
    <h2>挿入、更新操作中の例外の処置</h2>
    <asp:ObjectDataSource ID="ObjectDataSource1" 
      runat="server"
      SelectMethod="GetData" 
      DeleteMethod="Delete" 
      InsertMethod="ThrowExceptionBeforeInsert" 
      UpdateMethod="ThrowExceptionBeforeUpdate"
      OldValuesParameterFormatString="original_{0}" 
      TypeName="ProductsDataSetTableAdapters.ProductsTableAdapter">
      <DeleteParameters>
        <asp:Parameter Name="Original_ProductID" Type="Int32" />
      </DeleteParameters>
      <InsertParameters>
        <asp:Parameter Name="ProductName" Type="String" />
        <asp:Parameter Name="SupplierID" Type="Int32" />
        <asp:Parameter Name="CategoryID" Type="Int32" />
        <asp:Parameter Name="QuantityPerUnit" Type="String" />
        <asp:Parameter Name="UnitPrice" Type="Decimal" />
        <asp:Parameter Name="UnitsInStock" Type="Int16" />
        <asp:Parameter Name="UnitsOnOrder" Type="Int16" />
        <asp:Parameter Name="ReorderLevel" Type="Int16" />
        <asp:Parameter Name="Discontinued" Type="Boolean" />
      </InsertParameters>
      <UpdateParameters>
        <asp:Parameter Name="ProductName" Type="String" />
        <asp:Parameter Name="SupplierID" Type="Int32" />
        <asp:Parameter Name="CategoryID" Type="Int32" />
        <asp:Parameter Name="QuantityPerUnit" Type="String" />
        <asp:Parameter Name="UnitPrice" Type="Decimal" />
        <asp:Parameter Name="UnitsInStock" Type="Int16" />
        <asp:Parameter Name="UnitsOnOrder" Type="Int16" />
        <asp:Parameter Name="ReorderLevel" Type="Int16" />
        <asp:Parameter Name="Discontinued" Type="Boolean" />
        <asp:Parameter Name="Original_ProductID" Type="Int32" />
      </UpdateParameters>
    </asp:ObjectDataSource>
    <asp:DetailsView ID="DetailsView1" 
      runat="server" 
      AllowPaging="True" 
      AutoGenerateRows="False" 
      DataKeyNames="ProductID" 
      DataSourceID="ObjectDataSource1" 
      OnItemInserted="DetailsView1_ItemInserted" 
      OnItemUpdated="DetailsView1_ItemUpdated"
      OnDataBinding="DetailsView1_DataBinding">
      <Fields>
        <asp:BoundField DataField="ProductID" 
          HeaderText="ProductID" 
          InsertVisible="False" 
          ReadOnly="True" 
          SortExpression="ProductID" />
        <asp:BoundField DataField="ProductName" 
          HeaderText="ProductName" 
          SortExpression="ProductName" />
        <asp:BoundField DataField="SupplierID" 
          HeaderText="SupplierID" 
          SortExpression="SupplierID" />
        <asp:BoundField DataField="CategoryID" 
          HeaderText="CategoryID" 
          SortExpression="CategoryID" />
        <asp:BoundField DataField="QuantityPerUnit" 
          HeaderText="QuantityPerUnit" 
          SortExpression="QuantityPerUnit" />
        <asp:BoundField DataField="UnitPrice" 
          HeaderText="UnitPrice" 
          SortExpression="UnitPrice" />
        <asp:BoundField DataField="UnitsInStock" 
          HeaderText="UnitsInStock" 
          SortExpression="UnitsInStock" />
        <asp:BoundField DataField="UnitsOnOrder" 
          HeaderText="UnitsOnOrder" 
          SortExpression="UnitsOnOrder" />
        <asp:BoundField DataField="ReorderLevel" 
          HeaderText="ReorderLevel" 
          SortExpression="ReorderLevel" />
        <asp:CheckBoxField DataField="Discontinued" 
          HeaderText="Discontinued" 
          SortExpression="Discontinued" />
        <asp:CommandField ShowDeleteButton="True" 
          ShowEditButton="True" 
          ShowInsertButton="True" />
      </Fields>
    </asp:DetailsView>
    <asp:label id="MessageLabel"
      forecolor="Red"
      runat="server"/> 
  </div>
  </form>
</body>
</html>

TableAdapter を拡張したメソッドは以下の通りです。partial class として別ファイルにコードを書いて簡単に拡張できます。なお、以下のコードは検証用ですので、例外をスローするだけで、データーベースを更新するコードは含まれていません。

using System;
using System.Data;
using System.Configuration;
using System.Data.SqlClient;
using System.ComponentModel;

namespace ProductsDataSetTableAdapters
{
  public partial class ProductsTableAdapter
  {
    public virtual int ThrowExceptionBeforeInsert(
      string ProductName, 
      Nullable<int> SupplierID, 
      Nullable<int> CategoryID, 
      string QuantityPerUnit, 
      Nullable<decimal> UnitPrice, 
      Nullable<short> UnitsInStock, 
      Nullable<short> UnitsOnOrder, 
      Nullable<short> ReorderLevel, 
      bool Discontinued)
    {
      if (ProductName == null)
      {
        throw new ArgumentNullException("ProductName");
      }
      else if (ProductName == "xxx")
      {
        throw new ApplicationException("ハンドルされない例外");
      }
      else if (ProductName == "zzz")
      {
        return 0;
      }
      else
      {
        throw new NorthwindDataException("Insert で例外。");
      }
    }

    public virtual int ThrowExceptionBeforeUpdate(
      Nullable<int> Original_ProductID,
      string ProductName,
      Nullable<int> SupplierID,
      Nullable<int> CategoryID,
      string QuantityPerUnit,
      Nullable<decimal> UnitPrice,
      Nullable<short> UnitsInStock,
      Nullable<short> UnitsOnOrder,
      Nullable<short> ReorderLevel,
      bool Discontinued)
    {
      if (ProductName == null)
      {
        throw new ArgumentNullException("ProductName");
      }
      else if (ProductName == "xxx")
      {
        throw new ApplicationException("ハンドルされない例外");
      }
      else if (ProductName == "zzz")
      {
        return 0;
      }
      else
      {
        throw new NorthwindDataException("Update で例外。");
      }
    }

    public class NorthwindDataException : Exception
    {
        public NorthwindDataException(string msg) : base(msg) { }
    }

  }
}

Tags:

Exception Handling

About this blog

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

Calendar

<<  2024年3月  >>
252627282912
3456789
10111213141516
17181920212223
24252627282930
31123456

View posts in large calendar