WebSurfer's Home

トップ > Blog 1   |   Login
Filter by APML

更新操作中の例外の処置

by WebSurfer 16. August 2011 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月にこのブログを立ち上げました。その後 ブログ2 を追加し、ここは ASP.NET 関係のトピックス、ブログ2はそれ以外のトピックスに分けました。

Calendar

<<  June 2021  >>
MoTuWeThFrSaSu
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011

View posts in large calendar