WebSurfer's Home

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

CustomValidator で jQuery.ajax 利用

by WebSurfer 2016年10月20日 23:18

ASP.NET Web Forms アプリで、ユーザー入力の検証に CustomValidator と jQuery.ajax を用い、クライアント側での検証を行うというコードを書いたときに、無知な自分がハマったことを忘れないように備忘録として書いておきます。

CustomValidator

シナリオは、ユーザー入力が有効かどうかサーバー側でなければ確認できない場合、クライアント側から jQuery.ajax を使ってサーバーに問合せて検証し、検証結果が NG であればエラーメッセージを表示してユーザーに再入力を促すというものです。

CustomValidator は検証用のコードを自力で書かなければなりませんが、その分自由度が高く、クライアント側での検証用スクリプトを自分で書いて、jQuery.ajax を使ってサーバー側に問い合わせ、その結果で検証するようにできます。

(ちなみに、RequiredFieldValidator とか RegularExpressionValidator などは、クライアント側での検証用スクリプトとサーバー側での検証用コードの両方とも ASP.NET が自動的に生成してくれます)

また、ASP.NET Web Forms 用に多々用意されている検証コントロールの一つなので、他の検証コントロールと合わせて、全体的に整合が取れたユーザー入力の検証を行うことができます。

というわけで、今回のシナリオには CustomValidator を使うのがよさそうなので、CustomValidator と jQuery.ajax を組み合わせて使ったサンプルコードを書いてみました。下にアップしたのがそれです。

Microsoft が提供するサンプルデータベース Northwind の Customers テーブルを利用し、ユーザーが TextBox に入力した文字列が Customers テーブルの CustomerID フィールドに存在しない場合は検証 NG としています。他に、未入力および入力形式(アルファベット 5 文字)の検証もしています。

で、自分がハマったことですが、以下の 1 と 2 です。3 以降は自分的に備忘録として残しておいた方がよさそうだと思って書きました。下のサンプルコードを参照しながら読んでください。

  1. jQuery.ajax の設定 の async オプションを false に設定する。
    デフォルトは true で「非同期」になります。非同期では、検証用スクリプトの ClientValidate 関数は、$.ajax({ ... }); が実行されると、success, fail オプションに設定されたコールバックが実行されないまま即完了してしまいます。結果、ClientValidate 関数では args.IsValid には何も設定されません(初期値の true のままになる)。
    なので async:false に設定する必要があります。API Documentation の説明によると "Note that synchronous requests may temporarily lock the browser, disabling any actions while the request is active." とのことなので、あまりお勧めはできないようですが、他に手段が見つからないのでやむを得ません。
    なお、async:false とした場合は、jqXHR.done() は使わないで、success, error, complete にコールバックを書けと言うことですのでそうしてあります。
  2. jQuery.ajax によるサーバーへの要求が 2 回出てしまうケースがある。
    TextBox にアルファベット 5 文字を入力してから(正規表現による検証はパスさせてから)Button をクリックすると 2 回要求が出てしまいます(1 回無駄)。理由は、Button クリックで TextBox からフォーカスが外れて検証がかかり、Button クリックでもう一度検証かかかるからです。
    もともとそのようなケースでは検証が 2 回かかってしまうのが仕様(?)のようですが、今回のようにサーバーのデータベースへの問合せが行われており、それが 1 回余分になるのは何とかした方がよさそうな気がします。回避方法は調査中です。(見つからないかも)
  3. ユーザーが未入力の場合の検証は RequiredFieldValidator を利用しています。
    先の記事「CustomValidator と RequiredFieldValidator」で書きましたが、CustomValidator で未入力の検証も可能です。しかし、検証コントロールのエラーメッセージは固定的なので、CustomValidator 一つで済ませる場合、エラーメッセージは "未入力もしくはデータベースに存在しません" というようにせざるを得ません。
    それより、RequiredFieldValidator と CustomValidator を併用して、未入力の時は "未入力です" というエラーメッセージを、入力はあるが無効な場合は "データベースに存在しません" とした方がユーザーフレンドリーだと思います。
  4. 上で「検証コントロールのエラーメッセージは固定的」と書きましたが、サーバー側での検証メソッドでは検証内容に応じてエラーメッセージを書き換えることができます。
    しかしながら、先の記事「FileUpload と CustomValidator」でもいろいろ考えましたが、クライアント側でのエラーメッセージを書き換える方法が分かりません。それも RequiredFieldValidator と CustomValidator を併用した理由です。
  5. RegularExpressionValidator と併用すると表示が 2 重になる。
    最初、RequiredFieldValidator で未入力の検証、RegularExpressionValidator でアルファベット 5 文字であることの検証、CustomValidator でデータベースにデータがあるかの検証を行うつもりでした。
    しかしユーザー入力があって RequiredFieldValidator での検証をパスした場合、RegularExpressionValidator と CustomValidator の両方の検証がかかり、両方のエラーメッセージ(例えば、「アルファベット 5 文字としてください」と「データベースに存在しません」)が出てしまいます。
    それが、以下のサンプルコードで、CustomValidator でアルファベット 5 文字とデータベースの有無の両方の検証をすることにした理由です。
  6. 上の画像のエラーメッセージ "CustomValidator" は、CustomeValidator をドラッグ&ドロップしたときのデフォルトです。
    ユーザー入力 abcde はデータベースに存在しないので、クライアント側での検証結果が NG となり、ErrorMessage プロパティに設定されたエラーメッセージ "CustomValidator" が赤文字で表示されたところです。
    もちろんこれは初期設定で変更できます。サーバー側ではエラーメッセージを動的に書き換えることもできます。下のコードで、クライアント側での検証が働かないようにして(ClientValidate 関数の中のコードを全てコメントアウトするなどして)サーバー側だけで検証をかけると、ServerValidate メソッドで書き換えられたエラーメッセージが表示されます。
    ただし、上にも書きましたが、クライアント側でのエラーメッセージは固定的になります。
<%@ Page Language="C#" %>
<%@ Import Namespace="System.Text.RegularExpressions " %>
<%@ Import Namespace="System.Web.Configuration" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<%@ Import Namespace="System.Web.Services" %>

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

<script runat="server">

  // jQuery.ajax で POST された文字列を受け、DB に存在するか
  // をチェックするためのページ内の静的メソッド。
  // public static にして WebMethodAttribute 属性を付与
  [WebMethod]
  public static string ValidateId(string id)
  {
    // Northwind サンプルデータベース Customers テーブルの
    // CustomerID フィールドにユーザーが入力した文字列と同じ
    // データが存在するかを調べ、存在すればその CustomerID
    // データを、無ければ null を返す        
    string query = "SELECT CustomerID FROM Customers " + 
                   "WHERE CustomerID = @CustomerID";
    string connString = WebConfigurationManager.
        ConnectionStrings["NORTHWINDConnectionString"].
        ConnectionString;

    using (SqlConnection conn = new SqlConnection(connString))
    {
      using (SqlCommand cmd = new SqlCommand(query, conn))
      {
        cmd.Parameters.Add("@CustomerID", SqlDbType.NChar);
        cmd.Parameters["@CustomerID"].Value = id;
        conn.Open();
        return (string)cmd.ExecuteScalar();
      }
    }
  }
    
  // CustomValidator のサーバー側での検証用メソッド
  protected void ServerValidate(object source, 
      ServerValidateEventArgs args)
  {
    // CustomerID はアルファベット 5 文字なので、まず
    // 正規表現でアルファベット 5 文字にマッチするかを
    // チェック
    string pattern = "^[a-zA-Z]{5}$";

    if (Regex.IsMatch(args.Value, pattern))
    {
      // 上のヘルパメソッドを流用。DB にデータが
      // 存在しない場合 null が id に代入される
      string id = ValidateId(args.Value);

      if (id != null)
      {
        args.IsValid = true;
      }
      else
      {
        // サーバー側でならエラーメッセージを書き換
        // えることが可能
        ((CustomValidator)source).ErrorMessage = 
                    "データベースに存在しません";
        args.IsValid = false;
      }
    }
    else
    {
      ((CustomValidator)source).ErrorMessage = 
              "アルファベット 5 文字としてください";
      args.IsValid = false;
    }
  }   

  protected void Button1_Click(object sender, EventArgs e)
  {
    // IsValid で検証結果を調べて処置を行うのが原則
    if (Page.IsValid)
    {
      Label1.Text = "OK";
    }
    else
    {
      Label1.Text = "NG";
    }
  }
</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
  <title></title>
  <script src="/Scripts/jquery-1.11.1.js" type="text/javascript">
  </script>
  <script type="text/javascript">
  //<![CDATA[
    // async: false を設定しないとコールバックは非同期で実行
    // され args.IsValid が設定できないので注意
    // TextBox に入力してから Button をクリックすると 2 回要
    // 求が出でしまう。理由は、Button クリックする際 TextBox
    // からフォーカスが外れて検証がかかり、Button クリックで
    // もう一度検証かかかるから(デフォルトでそういう仕様)
    function ClientValidate(sender, args) {
      // 正規表現パターン(アルファベット 5 文字)            
      if (args.Value.match(/^[a-zA-Z]{5}$/)) {
        $.ajax({
          type: "POST",
          async: false,
          url: "0180-CustomValidatorAjax.aspx/ValidateId",
          data: '{"id":"' + args.Value + '"}',
          contentType: "application/json; charset=utf-8",

          success: function (data) {
            // .NET 3.5 で追加された d パラメータの処置
            if (data.hasOwnProperty('d')) {
              data = data.d;
            }
            if (data) {
              args.IsValid = true;
            } else {
              args.IsValid = false;
            }
          },

          error: function (jqXHR, textStatus, errorThrown) {
            args.IsValid = false;
          }
        });
        } else {
          args.IsValid = false;
        }
      }
  //]]>
  </script>
</head>
<body>
  <form id="form1" runat="server">
    
  <asp:TextBox ID="TextBox1" runat="server">
  </asp:TextBox>
    
  <asp:RequiredFieldValidator 
    ID="RequiredFieldValidator1" 
    runat="server" 
    ErrorMessage="必須入力です" 
    Display="Dynamic"
    ControlToValidate="TextBox1" 
    ForeColor="Red">
  </asp:RequiredFieldValidator>

  <asp:CustomValidator 
    ID="CustomValidator1" 
    runat="server" 
    ErrorMessage="CustomValidator" 
    ControlToValidate="TextBox1" 
    Display="Dynamic"
    ForeColor="Red" 
    OnServerValidate="ServerValidate" 
    ClientValidationFunction="ClientValidate">
  </asp:CustomValidator>

  <br />
  <asp:Button ID="Button1" runat="server" 
    Text="送信" OnClick="Button1_Click" />
  <br />
  <asp:Label ID="Label1" runat="server"></asp:Label>
  </form>
</body>
</html>

Tags: ,

Validation

GridView, ListView で特定の行をハイライト

by WebSurfer 2016年10月13日 23:35

GridView, ListView でデータを一覧表示する際、それにデータバインドされたデータソースに含まれる各行の特定の列のデータの値を調べ、その値に応じて当該行をハイライトする方法について書きます。

元は MSDN フォーラムのスレッド「ListViewでの一覧表示で、取得したデータに応じて背景色を変えたい」での話です。

そのスレッドで自分が回答したことなのですが、忘れかけていたので自分のブログに備忘録として書いておくことにしました。また、ついでに GridView での実装例も書いておきます。

このブログの別の記事「GridView, ListView に合計表示」のコードをベースにしていますので、そちらも見てください。

その記事では、Microsoft が提供するサンプルデータベース Northwind の Orders というテーブルのレコードのうち、[CustomerID] が ALFKI という顧客の注文の [OrderID], [OrderDate], [Freight] フィールド一覧を表示しています。

Orders テーブルには出荷日を示す [ShippedDate] というフィールドが含まれています。ここでは、[ShippedDate] を調べて、ある日付より後に出荷されたレコードの行の背景色をハイライトしてみます。なお、ListView / GridView には [ShippedDate] フィールドは表示しません。

ListView での例

以下の画像は 1998/1/1 より後に出荷された注文のレコードをハイライトしたものです。

ListView

紹介した MSDN フォーラムのスレッドで書いたとおりですが、上のように表示するための具体的な方法を以下に書きます。

  1. SqlDataSource の SELECT クエリに [ShippedDate] を追加します。
  2. ListView の ItemTemplate 内の tr 要素をサーバーコントロールに変更して id を付与します。具体的には runat="server" id="datarow" という 2 つの属性を追加します(id 名は任意)。
  3. 例えば、1998/1/1 より後に出荷されたレコードを黄色でハイライトする場合、[ShippedDate] が 1998/1/1 より後のアイテムの中から FindControl メソッドで上記 2 でサーバーコントロールにした tr 要素を探し、それに style 属性を追加します。コードは以下のようになります。
protected void ListView1_ItemDataBound(object sender, 
        ListViewItemEventArgs e)
{
  if (e.Item.ItemType == ListViewItemType.DataItem)
  {
    ListViewDataItem lvdi = (ListViewDataItem)e.Item;
    DataRowView drv = (DataRowView)lvdi.DataItem;
    total = total + (decimal)drv["Freight"];

    // 以下が追加した部分
    DateTime date = new DateTime(1998, 1, 1, 0, 0, 0);
    if ((DateTime)drv["ShippedDate"] > date)
    {
      HtmlControl tr = (HtmlControl)lvdi.FindControl("datarow");
      if (tr != null)
      {
        tr.Attributes.Add("style", "background-color:yellow;");
      }
    }
  }
}

GridView での例

上の ListView の場合と同様に、1998/1/1 より後に出荷された注文のレコードをハイライトしたものです。

GridView

上のように表示するための具体的な方法は以下の通りです。

  1. SqlDataSource の SELECT クエリに [ShippedDate] を追加します。(ListView の場合と同様)
  2. ListView の場合とは異なり、GridView のコードには手を加える必要はありません。GridView の RowDataBound イベントで GridViewRow を取得し、それの CssClass プロパティにスタイルを設定できますので。
  3. 上の ListView の場合と同様に、1998/1/1 より後に出荷されたレコードを黄色でハイライトする場合、[ShippedDate] を調べて 1998/1/1 より後であれば、当該行の GridViewRow オブジェクトを取得し、その CssClass プロパティを設定します。コードは以下のようになります。
protected void GridView1_RowDataBound(object sender, 
        GridViewRowEventArgs e)
{        
  if (e.Row.RowType == DataControlRowType.DataRow)
  {
    DataRowView drv = (DataRowView)e.Row.DataItem;
    total = total + (decimal)drv["Freight"];

    // 以下が追加した部分
    DateTime date = new DateTime(1998, 1, 1, 0, 0, 0);
    if ((DateTime)drv["ShippedDate"] > date)
    {
      e.Row.CssClass = "style1";
    }
    // ここまで
  }
  else if (e.Row.RowType == DataControlRowType.Footer)
  {
    e.Row.Cells[1].Text = "Freight Total";
    e.Row.Cells[2].Text = String.Format("${0:N2}", total);

    e.Row.Cells[1].ColumnSpan = 2;
    e.Row.Cells.RemoveAt(0);            
  }        
}

e.Row.CssClass = "style1"; のところの style1 は別途定義してソースに追加してください。インラインで <title> 要素の下に書くなら以下のようにします。

<style type="text/css">
    .style1
    {
        background-color: yellow;
    }
</style>

Tags: , ,

ASP.NET

MVC アプリでのデータの編集・更新

by WebSurfer 2016年9月18日 13:48

ASP.NET MVC アプリケーションで Entity Framework を利用してデータベースの編集・更新を行う方法について調べる機会があったのですが、その過程でいろいろ新発見があったので備忘録として残しておきます。

(1) EntityState.Modified の設定

データベースを更新 (UPDATE) するには、SaveChanges メソッドをコンテキストに適用する前に、当該エンティティの State を EntityState.Modified(以下、Modified と書きます)に設定する必要があります。

その方法には、自分が知る限りですが、以下の 2 つがあります:

(1-1) コードで明示的に設定

編集したオブジェクトをコンテキストにアタッチし、そのエンティティの State を Modified に設定します。

具体的な方法は、Microsoft の記事「Entity states and SaveChanges」の Attaching an existing but modified entity to the context のセクションの説明とサンプルコードが参考になると思いますので、そちらを見てください。

なお、上に紹介した記事のコードは DbContext クラスがベースのコンテキストの場合ですので注意してください。(EF6 Code First の場合は DbContext クラスが使われます)

VS2010 の EF4 を使って DB First で作った EDM などには、DbContext クラスではなくて ObjectContext クラスが使われますが、それには Entry メソッドは定義されておらず、上に紹介した記事のコードのようにはできないので注意してください。具体的には以下のようにします。

[HttpPost]
public ActionResult Edit(Address address)
{
  if (ModelState.IsValid)
  {
    db.Address.Attach(address);
    db.ObjectStateManager.
      ChangeObjectState(address, EntityState.Modified);
    db.SaveChanges();
    return RedirectToAction("Index");
  }
  return View(address);
}

上のコードは、Microsoft が提供するサンプルデータベース AdventureWorksLT の Address テーブルから、VS2010 + EF4 を使って DB First で EDM を作成し、それをベースにスキャフォールディング機能を使って自動生成させたものです。

(1-2) エンティティのプロパティを書換

コンテキストから更新するエンティティを取得し、そのプロパティを使って内容を書き換えると、前の値から変更された場合は自動的にそのエンティティに Modified マークが付けられます。前の値と同じ値を設定した場合は Unchanged のままとなります。

言葉だけでは分かりにくいと思いますので、以下にコードを書いて説明します。

先の記事「EF でレコードの削除」で紹介した通りに Code First でデータベースを生成したとします。

Code First で生成した Blogs テーブルと Posts テーブルに、以下のコードでデータを INSERT します。

// DB に以下のデータを INSERT
Blog b = new Blog { Name = "プログラミング" };
b.Posts = new List<Post>();
b.Posts.Add(new Post { Title = "ASP", Content = "作り方" });
b.Posts.Add(new Post { Title = "WCF", Content = "書き方" });
b.Posts.Add(new Post { Title = "WPF", Content = "内容" });
db.Blogs.Add(b);
db.SaveChanges();

それを以下のようなコードで各エンティティのプロパティを使用して書き換えるとします。

// DB からエンティティを取得し、一部のプロパティを書換
BloggingContext db = new BloggingContext();
Blog b = db.Blogs.Single(i => i.BlogId == 1);
b.Name = "プログラミング";     // 変更なし
b.Posts[0].Title = "MVC";      // ASP ⇒ MVC
b.Posts[0].Content = "訂正";   // 作り方 ⇒ 訂正
b.Posts[1].Title = "NET";      // WCF ⇒ NET
b.Posts[1].Content = "書き方"; // 変更なし
b.Posts[2].Title = "WPF";      // 変更なし
b.Posts[2].Content = "内容";   // 変更なし

// 各エンティティの State を調べると:
EntityState state = db.Entry(b).State;  // Unchanged
foreach (Post p in b.Posts)
{
    state = db.Entry(p).State;
    // 順に、Modified, Modified, Unchanged
}

各エンティティの State を調べると、全てのプロパティに現在の値と同じ値を設定した場合は(即ち変更しない場合は)そのエンティティの State は Unchanged のままになり、一つでもプロパティが前の値から変更された場合はエンティティの State が自動的に Modified に変更されます。

注:Visual Studio 2010 に標準で備わっている EF4 を使って DB First で EDM を生成した場合はプロパティに現在の値と同じ値を設定しても Modified に変わるので注意してください。上の話は NuGet で EF6 を適用し、Code First でデータベースを生成した場合の話です。(EF4 / EF6 のバージョンの差によるものか、ObjectContext / DbContext のコンテキストのベースが違うためかは分かりません)


(2) MVC アプリで Modified マークをつける方法

エンティティに Modified マークをつけてから、そのエンティティをトラックしているコンテキストに SaveChanges メソッドを適用すればデータベースは更新 (UPDATE) されます。

ASP.NET MVC アプリでエンティティに Modified マークをつけるには、編集画面から送信されてきたユーザー入力情報を利用することになりますが、基本的には以下のような方法があると思います。

(2-1) UpdateModel メソッドを使う

Controller.UpdateModel メソッドは、フォーム、クエリ文字列、ルート、クッキーなどに含まれるクライアントから送信されてきたデータから、このメソッドの引数に設定されたエンティティのプロパティ名と一致するものを探して、それでエンティティの内容を書き換えます。

書き換えられると、上の (1-2) で書いたように、State が自動的に Unchanged から Modified に変わりますので、SaveChanges メソッドでデーターベースが更新 (UPDATE) されます。

UpdateModel メソッドを使う場合の問題は、フォーム、クエリ文字列、ルート、クッキーなどを使って送信されてきたデータのどれを使ってエンティティを書き換えているのかよく見えないところにあると思います。UpdateModel には多数のオーバーロードがあり、送信されたパラメータを指定 / 除外するためのオプションがあって、それらを使えばある程度コントロールできるとは思いますが・・・

そのあたりを配慮するとしても、先の記事「親子関係のあるデータの編集・削除」で書いたようなケースが問題です。

その記事に書いてありますが、UpdateModel メソッドを使うと、SaveChanges で子テーブルの関連するレコードの外部キーフィールドを NULL に書き換え、ポストされたデータでレコードを新たに作り INSERT するという動きになります。外部キーが NULL 不可に設定されている場合は特に問題で、当然制約違反でエラーになってしまいます。

これには、自分が考えた限りですが、対応不可でした。(何故そういう結果になるのか、メカニズムが解明できていません)

そもそも、普通はモデルバインディング+データアノテーション検証の機能を利用している場合がほとんどでしょうから、そういうケースでは UpdateModel メソッドは忘れてもよさそうです。

モデルバインディング+データアノテーション検証の機能を利用すると、アクションメソッドのパラメータ(引数)が指すオブジェクトにクライアントから送信されてきたデータがバインドされると同時に、サーバー側で検証が行われます。

コントローラーで ModelState.IsValid が ture であればアクションメソッドの引数が指すオブジェクトにバインドされたデータは検証結果 OK ということになります。

従って、モデルバインディング+データアノテーション検証の機能を利用しているのであれば、アクションメソッドの引数を使った方が簡単&確実だと思います。その方法は下の (2-2), (2-3) を見てください。

(2-2) バインディング結果をアタッチして Modified マーク

モデルバインディング+データアノテーション検証の機能を利用している場合、アクションメソッドの引数が指すオブジェクトに更新後のデータはバインドされており、コントローラーで ModelState.IsValid が ture であればデータは検証結果 OK ということになります。

従って、アクションメソッドの引数が指すオブジェクトを、上の (1-1) で述べた方法でコンテキストにアタッチし、そのエンティティの State を Modified に設定してやることで目的が果たせます。

UpdateModel メソッドでは (2-1) に書いた問題があった親子関係のあるデータ(子はコレクション)の場合も、親子を別々にコンテキストにアタッチし、State を Modified に設定してやれば問題は解決できます。(具体例は、先の記事「親子関係のあるデータの編集・削除」の「Edit アクションメソッド」のコードを見てください)

(2-3) バインドされたデータでエンティティを書き換え

上の (2-2) の方法では無条件に State が Midified に設定されますので、ユーザーがデータを変更していなくても SaveChanges でデータベースに UPDATE がかかってしまいます。

先の記事「親子関係のあるデータの編集・削除」ではそのようにコーディングしましたが、子のデータのほんの一部のみ更新するケースも多々あるでしょうから考え直した方がよさそうです。

また、本来更新は不要でユーザーが送信する必要のないデータも送信する必要があるという面倒なこともあります。特に外部キーのデータは送信しないと State を Midified に設定する際参照整合性制約違反でエラーとなります。

上の (1-2) で書きましたが、コンテキストから更新するエンティティを取得し、そのプロパティを使って内容を書き換えると、前の値から変更された場合のみ Modified マークが付けられ、前の値と同じ値を設定した場合は Unchanged のままとなります。

アクションメソッドの引数が指すオブジェクトにモデルバインドされたデータがあるのですから、自力でコードを書いて更新する項目のみそれらのデータをエンティティに反映させた方がよさそうです。すべてを自分のコントロール下で設定できるので安心・安全ということもあるかもしれません。

上記のことを考慮して、先の記事「親子関係のあるデータの編集・削除」を書き直すと、以下のようになるでしょうか。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Data.Entity;
using Mvc4App3.Models;

namespace Mvc4App3.Controllers
{
  public class ParentChildController : Controller
  {
    private ParentChildContext db = new ParentChildContext();
        
    // ・・・中略・・・

    // GET: /ParentChild/Edit/5
    public ActionResult Edit(int id)
    {
      Parent parent = db.Parents.Find(id);
      if (parent == null)
      {
        return HttpNotFound();
      }
      return View(parent);
    }

    // POST: /ParentChild/Edit/5
    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Edit(Parent postedParent)
    {
      if (ModelState.IsValid)
      {
        // 編集画面から送信された親の Id で、コンテキストから
        // 該当する親エンティティを取得
        Parent parent = db.Parents.Find(postedParent.Id);

        if (parent == null)
        {
          // 見つからなかったときの処理
        }

        // 編集画面から送信されたデータで、上で取得した親エン
        // ティティの中身を書き換え
        parent.Name = postedParent.Name;

        foreach (Child postedChild in postedParent.Children)
        {
          // 編集画面から送信された子の Id で、コンテキスト
          // から該当する子エンティティを取得
          Child child = db.Children.Find(postedChild.Id);
                    
          if (child == null)
          {
            // 見つからなかったときの処理
          }
                    
          // 編集画面から送信されたデータで、上で取得した
          // 子エンティティの中身を書き換え
          child.Name = postedChild.Name;
        }

        db.SaveChanges();

        return RedirectToAction("Index");
      }
      return View(postedParent);
    }

    // ・・・中略・・・

  }
}

上のコードのコメントの「見つからなかったときの処理」ですが、編集画面を表示する際に取得した Id を隠しフィールドで保持しており、更新するときそれをそのまま POST しているので、普通なら見つからないということはあり得ません。見つからないとすれば、編集中に誰かが DB の当該レコードを削除してしまったとか、不正なデータが送信されたなどの異常事態と考えた方がよいかもしれません。

Tags: ,

MVC

About this blog

2010年5月にこのブログを立ち上げました。その後 ブログ2 を追加し、ここは ASP.NET 関係のトピックス、ブログ2はそれ以外のトピックスに分けました。

Calendar

<<  2017年3月  >>
2627281234
567891011
12131415161718
19202122232425
2627282930311
2345678

View posts in large calendar