WebSurfer's Home

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

更新操作中の例外の処置

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

SQL Server Express 用 Profiler

by WebSurfer 2011年8月8日 23:26

SQL Server を利用したアプリケーションの開発時に、アプリケーションから SQL Server にどのようなクエリが発行されているかを確認したいことがあると思います。

SQL Server Standard Edition 以上には SQL Server Profiler というツールが付属していて、GUI 環境で SQL Server のインスタンスで発生するイベントを収集して表示させることができるそうです。

しかし、残念ながら Express Edition には SQL Server Profiler は付属していません。

ググって調べてみると、AnjLab というロシアのソフト会社が無償で提供している SQL Server 2005/2008 Express Profiler というツールを見つけましたので使ってみました。

インストールは、上記のサイトからダウロードした zip ファイルに SqlExpressProfiler.msi という Windows インストーラーパッケージが含まれているので、それを実行するだけです。

設定方法が書いてあるドキュメントが見つからず、正しいかどうかイマイチ不安ですが、以下のように設定しました。なお、SQL Server は 2008 Express Edition で、開発マシンにローカルにインストールしてあります。認証方式は Windows 認証です。

(1) メニューバーで File --> New Trace を選択。表示される Trace Properties ダイアログの General タブで以下のように設定します。

SQL Profiler の Trace Properties ダイアログの General タブ設定

(2) Events タブの設定がよく分かりませんでしたが、開発中のアプリケーションから実行されるクエリの内容を確認したい場合は、SQLStmtStarting と SPStmtStarting にチェックを入れればよさそうです。

SQL Profiler の Trace Properties ダイアログの Events タブ設定

(3) Trace Properties ダイアログの Run ボタンをクリックするとダイアログが閉じて、トレース状況が表示されます。下の画像は、先の記事 SQL キャッシュ依存関係 で紹介したアプリケーションが、SQL Server にポーリングをかけているところです。

SQL Profiler のトレース状況

というわけで、アプリケーションが発行したクエリを確認するだけにしか使っていませんが、その目的であれば問題なく使えました。

Tags:

DevelopmentTools

Forms 認証チケットの受渡しに URI を使用

by WebSurfer 2011年8月6日 14:59

今回は URI に認証チケットを追加して送る場合の Forms 認証のログインとログアウト動作について調べてみました。クッキーを使用した場合については、先の記事 Forms 認証のログイン・ログオフ動作 を参照してください。

認証チケットの入れ物に URI を使った場合の Forms 認証のログイン・ログオフ動作

web.config で authentication の forms 要素 の cookieless 属性を AutoDetect に設定し、Firefox 5 で「サイトから送られてきた Cookie を保存する」のチェックを外してテストしました。

URI を使用する場合の問題は、上の画像に示したように、ブラウザのアドレスバーに認証チケットが表示されるので、認証チケットが一般ユーザーの目に直接さらられてしまうことです。というわけで、できればクッキーのみを使うようにしたほうが無難だと思われます。

***** ログイン *****

  1. 匿名アクセスが許可されているページ default.aspx で、ログインしていない状態から LoginStatus をクリックする。
  2. JavaScrip が起動してポストバック(default.aspx への POST 要求)がかかる。
  3. サーバーからリダイレクト指示(HTTP/1.1 302 Found)が返ってくる。応答ヘッダーに含まれる Location(リダイレクト先)は web.config で指定したログインページ login.aspx となる。
  4. ブラウザはリダイレクト先のページ login.aspx を GET 要求する。・・・ここまではクッキーを使った場合と同じ。
  5. 再び、サーバーからリダイレクト指示(HTTP/1.1 302 Found)が返ってくる。応答ヘッダーに含まれる Location は同じく login.aspx だが、クエリ文字列に AspxAutoDetectCookieSupport=1 が追加されている。
  6. 再び、ブラウザはリダイレクト先のページ login.aspx を GET 要求する。前回の要求の時と違うのは、クエリ文字列に AspxAutoDetectCookieSupport=1 が追加されていること。
  7. サーバーから応答(login.aspx)が返ってくる。
  8. ユーザーが ID とパスワードを入力して[ログイン]ボタンをクリックする。
  9. [ログイン]ボタンのクリックで login.aspx にポストバックがかかる。ただし、form 要素の action 属性に (X(1))/ が追加されているので、POST 先は login.aspx ではなく (X(1))/login.aspx となる。クエリ文字列 にも AspxAutoDetectCookieSupport=1 が追加されている。
  10. サーバーからリダイレクト指示(HTTP/1.1 302 Found)が返ってくる。応答ヘッダーに含まれる Location は (X(1)F(mfZdwXpqsmZ ... 2Qg2g1))/default.aspx となり、mfZdwXpqsmZ ... 2Qg2g1 という認証チケットが追加されている。
  11. リダイレクト指示を受けて、ブラウザは (X(1)F(mfZdwXpqsmZ ... 2Qg2g1))/default.aspx を GET 要求する。
  12. サーバーから応答(default.aspx)が返ってくる。認証チケットが URI に含まれているのでユーザーは認証済みとなる。

***** ログアウト *****

  1. ログインしている状態のページ (X(1)F(mfZdwXpqsmZ ... 2Qg2g1))/default.aspx で LoginStatus をクリック。
  2. JavaScrip でポストバックがかかる。
  3. サーバーからリダイレクト指示(HTTP/1.1 302 Found)が返ってくる。Location は (X(1))/default.aspx となる。この時、URI から認証チケットは削除されている。
  4. ブラウザは (X(1))/default.aspx を GET 要求する。
  5. サーバーから応答 (X(1))/default.aspx が返ってくる(ログアウトした後も URI に (X(1)) は含まれる)。ユーザーはログアウト状態となる。

Tags: ,

Authentication

About this blog

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

Calendar

<<  2024年5月  >>
2829301234
567891011
12131415161718
19202122232425
2627282930311
2345678

View posts in large calendar