WebSurfer's Home

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

GridView 内の隠しボタンでポストバック

by WebSurfer 2014年3月26日 17:34

GridView の各データ行に隠し Button を配置し、クライアントスクリプトを使って行クリックで当該行の隠しボタンをクリックすることによりポストバックがかかり、サーバー側の Button.Click イベントでクリックされた行のレコードの ID を取得するサンプルの紹介です。

GirdView 内の隠しボタンでポストバック

先の記事 __doPostBack を使ってはいけません では、GridView の行クリックでポストバックをかけるために GetPostBackEventReference メソッド を使用しました。

この記事のサンプルでは、GetPostBackEventReference メソッドを使う代わりに、隠し Button をクリックすることによりポストバックをかけます。

隠し Button なのでユーザーは直接それをクリックすることはできません。なので、GridView から生成されるデータ行の tr 要素の click イベントに JavaScript のリスナーをアタッチし、そのリスナーによって隠し Button をクリックします。

リスナーの作成および click イベントへのアタッチは、以下のコードのように jQuery を使うと比較的簡単にできます。

気をつけなければならないことは DOM イベントには「バブリング」というものがあることです。(バブリングの詳細については、先の記事 キャプチャリングとバブリング を見てください)

つまり、(1) tr 要素をクリック ⇒ (2) リスナーがクリックイベントを捕捉 ⇒ (3) リスナー内のメソッドが当該 tr 要素内の Button をクリック ⇒ (4) Button の click イベント発生 ⇒ (5) バブリングで tr 要素にイベントが伝播 ⇒ (6) ステップ (2) に戻る・・・と言うように無限ループになってしまうと言うことです。

この記事の例では、無限ループにならないように event.stopPropagation メソッドによりバブリングを止めました。その部分を含めたページ全体のソースコードをこの記事の下の方に記載しています。

また、実際に動かして試すことができるよう 実験室 にアップしました。興味のある方は試してみてください。

<%@ Page Language="C#" %>
<%@ Import Namespace="System.Data" %>

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

<script runat="server">

  // データソース用の DataTable を作成
  protected DataTable CreateDataTable()
  {
    DataTable dt = new DataTable();

    dt.Columns.Add(new DataColumn("ID", typeof(Int32)));
    dt.Columns.Add(new DataColumn("Name", typeof(string)));
    dt.Columns.Add(new DataColumn("Price", typeof(Int32)));
    dt.Columns.Add(new DataColumn("Qty", typeof(Int32)));
    dt.Columns.Add(new DataColumn("Amount", typeof(Int32)));
    dt.Columns.Add(new DataColumn("Remarks", typeof(string)));

    for (int i = 1; i < 6; i++)
    {
      DataRow dr = dt.NewRow();
      dr["ID"] = i;
      dr["Name"] = "Item " + i.ToString();
      dr["Price"] = 123000 * i;
      dr["Qty"] = i;
      dr["Amount"] = 123000 * i * i;
      dr["Remarks"] = "Remarks " + i.ToString();
      dt.Rows.Add(dr);
    }
    return dt;
  }

  protected void Page_Load(object sender, EventArgs e)
  {
    if (!Page.IsPostBack)
    {
      GridView1.DataSource = CreateDataTable();
      GridView1.DataBind();
    }
  }

  protected void Button1_Click(object sender, EventArgs e)
  {
    Label2.Text = 
      "Selected ID: " + ((Button)sender).CommandArgument;
  }
</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
  <title>WebSurfer's Page - 実験室</title>
  <script src="/scripts/jquery.js" type="text/javascript">
  </script>
  <script type="text/javascript">
  //<![CDATA[
    $(document).ready(function () {
      $("#<%=GridView1.ClientID%> tr.dataRow")
        .click(function () {

        var hiddenbutton = $(this).find("input:submit");

        // バブリングを止めないと無限ループになる。
        hiddenbutton.click(function (event) {
          event.stopPropagation();
        });

        hiddenbutton.click();
      });

      // カーソルを指型にする。
      $("#<%=GridView1.ClientID%> tr.dataRow")
        .css('cursor', 'pointer');
    });
  //]]>
  </script>
  <style type="text/css">
    .style1
    {
      display: none;
    }
    .style2
    {
      cursor: pointer;
    }
  </style>
</head>
<body>
  <form id="form1" runat="server">
  <div>
    <asp:GridView ID="GridView1" 
      runat="server" 
      AutoGenerateColumns="False" 
      RowStyle-CssClass="dataRow">
      <Columns>
        <asp:TemplateField HeaderText="ID">
          <ItemTemplate>
            <asp:Label ID="Label1" 
              runat="server" 
              Text='<%#Eval("ID")%>'>
            </asp:Label>
            <asp:Button ID="Button1" 
              runat="server" 
              Text="Button" 
              OnClick="Button1_Click" 
              CommandArgument='<%#Eval("ID")%>' 
              CssClass="style1" />
          </ItemTemplate>
        </asp:TemplateField>
        <asp:BoundField DataField="Name" HeaderText="Name" />
        <asp:BoundField DataField="Price" HeaderText="Price" />
        <asp:BoundField DataField="Qty" HeaderText="Qty" />
        <asp:BoundField DataField="Amount" HeaderText="Amount" />
        <asp:BoundField DataField="Remarks" HeaderText="Remarks" />
      </Columns>
    </asp:GridView>
    <asp:Label ID="Label2" runat="server"></asp:Label>
  </div>
  </form>
</body>
</html>

Tags: ,

ASP.NET

ブラウザ定義ファイル

by WebSurfer 2014年3月4日 13:32

ASP.NET には「ブラウザ定義ファイル」というものがあります。(下の画像は ASP.NET 4 用のブラウザ定義ファイルです。これとは別に ASP.NET 2.0, 3.0, 3.5 用のブラウザ定義ファイルが v2.0.50727 サブフォルダ下にあります)

ASP.NET のブラウザ判定はこれらのファイルに書かれた定義が基になるはずなのですが、実際の判定結果はファイルの内容と違うことがある・・・即ち、HttpBrowserCapabilities オブジェクトを調べないと分からない・・・という話を書きます。

ブラウザ定義ファイル

ASP.NET は、実行時に、ブラウザから送られてくる User Agent と、.browser ファイルに書かれている定義を基にブラウザの機能を判定し、ブラウザに送信する html, Javascript などの内容を調整します。詳しくは MSDN ライブラリの ブラウザー定義ファイルのスキーマ (browsers 要素) を見てください。

IE10 がリリースされた直後に、IE10 で ASP.NET ページにアクセスすると __doPostBack() などが定義されてないというスクリプトエラーが出たということがありました。この原因は、IE10 のリリース直後にはまだ ASP.NET に IE10 のブラウザ定義がなかったからです。その後の Windows Update で IE10 のブラウザ定義が追加され、問題が解消されたという経緯があります。

先日 IE11 がリリースされ、その User Agent が大幅に変更された("MSIE" という文字列が含まれなくなりました。詳しくは MSDN ライブラリの User-agent string changes を参照ください)ということで、それに伴いブラウザ定義ファイルがどう変わったかを調べてみました。

ところが、ASP.NET 2.0, 3.0, 3.5 用のブラウザ定義ファイルの内容と、ASP.NET のブラウザ判定結果(HttpRequest.Browser プロパティで取得できる HttpBrowserCapabilities オブジェクト)が、IE11 はおろか IE10 でも一致しません。(ASP.NET 4 の場合は一致しました。)

例えば、ASP.NET 3.5 のサイトで、HttpBrowserCapabilities.Version プロパティで正しく IE10, IE11 のバージョン(それぞれ 10.0, 11.0)が取得できましたが、ブラウザ定義ファイルにはどこにもそのような定義は見当たりません。

おかげで理由を調べるのに半日ぐらいハマってしまいました。(汗)

そんな時に見つけたのが MSDN Blog の記事 ASP.NET の IE10 対応について です。

詳しくはその記事を読んでいただきたいのですが、要するに、ASP.NET は「ブラウザ定義ファイル」使っておらず、System.Web.dll の中に持っているブラウザ判定ルーチンを使っているのだそうです。

また、Windows Update による更新では Sysytem.Web.dll を更新して既定のルーチンは置き換えるものの、必ずしもブラウザ定義ファイルは更新されないようです。

なんのこっちゃ・・・という感じです。(笑)

Tags:

ASP.NET

Windows Forms アプリで Membership を利用

by WebSurfer 2014年2月11日 17:47

ASP.NET の Membership クラスRoles クラス を Windows Forms アプリケーションでも利用できるという話です。

Windows Forms アプリで Membership と Roles を利用

Membership オブジェクトおよび Roles オブジェクトは、ASP.NET が web.config の情報を基に Membership プロバイダおよび Role プロバイダ(ユーザー情報のストアが SQL Server の場合は SqlMembershipProvider クラス および SqlRoleProvider クラス)を利用して自動的に生成します。

プログラマがコードを書いて Membership オブジェクト、Roles オブジェクトを作る手段は提供されていません。なので、ASP.NET Web アプリケーションでしか使用できないと思っていましたが、実はそうではなかったです。(汗)

そのことは、MSDN フォーラムで Using the ASP.Net membership provider in a Windows forms application part 1. という記事を紹介されて、初めて知りました。

その記事によると、Windows コンソールアプリでも(ASP.NET でなくても)、app.config から(web.config からでなくても)設定値を取得して、Membership オブジェクトや Roles オブジェクトを自動的に生成してくれるということでした。

自分でも、以下のコードの Windows Forms アプリを作って試してみましたが、確かにその通りであることが確認できました。上の画像が以下のコードを実行したときのものです。

Form1.cs

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Web.Security;

namespace ASPNETMembershipWindowsForms
{
  public partial class Form1 : Form
  {
    public Form1()
    {
      InitializeComponent();
            
      this.listBox2.DataSource = Roles.GetAllRoles();

      List<string> userNames = new List<string>();
      foreach (MembershipUser user in Membership.GetAllUsers())
      {
        userNames.Add(user.UserName);
      }
      this.listBox1.DataSource = userNames;
    }

    private void button1_Click(object sender, EventArgs e)
    {
      if (Membership.ValidateUser(this.textBox1.Text, 
            this.textBox2.Text))
      {
        this.label3.Text = "Result: User validated.";
      }
      else
      {
        this.label3.Text = "Result: User invalid!";
      }            
    }

    private void button2_Click(object sender, EventArgs e)
    {
      if (this.listBox1.SelectedItem == null)
      {
        this.label6.Text = "Please select a user.";
        return;
      }

      if (this.listBox2.SelectedItem == null)
      {
        this.label6.Text = "Please select a role.";
        return;
      }

      string user = this.listBox1.SelectedItem.ToString();
      string role = this.listBox2.SelectedItem.ToString();

      try
      {
        if (Roles.IsUserInRole(user, role))
        {
          this.label6.Text = "Yes";
        }
        else
        {
          this.label6.Text = "No";
        }
      }
      catch (Exception ex)
      {
        this.label6.Text = ex.Message;
      }
    }
  }
}

app.config

<?xml version="1.0"?>
<configuration>
  <configSections>
  </configSections>
  <connectionStrings>
    <add name="MemberShip" 
      connectionString="..." 
      providerName="System.Data.SqlClient"/>
  </connectionStrings>

  <system.web>
    <machineKey validationKey="..." 
      decryptionKey="..." 
      validation="SHA1" 
      decryption="AES"/>

    <membership defaultProvider="SqlProvider">
      <providers>
        <remove name="SqlProvider"/>
        <add name="SqlProvider" 
          type="System.Web.Security.SqlMembershipProvider" 
          connectionStringName="MemberShip" 
          applicationName="MyHomePage" 
          enablePasswordRetrieval="false" 
          enablePasswordReset="true" 
          requiresQuestionAndAnswer="true" 
          requiresUniqueEmail="false" 
          passwordFormat="Hashed" 
          maxInvalidPasswordAttempts="5" 
          minRequiredPasswordLength="7" 
          minRequiredNonalphanumericCharacters="0" 
          passwordAttemptWindow="10"/>
      </providers>
    </membership>
        
    <roleManager enabled="true" defaultProvider="RoleProvider">
      <providers>
        <clear/>
        <add name="RoleProvider" 
          type="System.Web.Security.SqlRoleProvider" 
          connectionStringName="MemberShip" 
          applicationName="MyHomePage"/>
      </providers>
    </roleManager>
  </system.web>
  <startup>
    <supportedRuntime version="v2.0.50727"/>
  </startup>
</configuration>

今回の検証では、既存の ASP.NET Web アプリケーションの Forms 認証用の ASPNETDB.mdf を利用ました。

なので、上記 app.config で既存の ASPNETDB.mdf に接続できるように接続文字列を設定し、machineKey 要素、membership 要素、roleManager 要素の設定は既存の Web アプリの web.config と同じにしています。

machineKey 要素の設定まで同じにしたのは、MSDN ライブラリの How To: ASP.NET 2.0 で MachineKey を構成する方法 の「メンバシップ」のセクションに "暗号化パスワード フォーマットを選択すると、データの暗号化と暗号化解除のときに <machineKey> 設定が使用されます" という記述があったからです。

ただし、これは passwordFormat が "Encrypted" に設定されている場合のようで、デフォルトの "Hashed" の場合は関係なさそうです。

それから、ランタイムバージョンが .NET 2.0 になっているのは、.NET 4 では何故か MembershipUser が使えないからです。

オブジェクトブラウザを見ても MembershipUser 以外にも .NET 4 に含まれないクラスが多々あります。理由不明ですが、今日はもうこれ以上追求する気力がないです。(笑)

----- 2014/2/13 追記 -----

.NET 3.5 以前では System.Web.dll のみを参照設定に追加すれば使えた MembershipUser その他のクラスが .NET 4 では使えない理由が分かりました。

Microsoft の ASP.NET のサイトの ASP.NET 4 Breaking Changes という記事の MembershipUser and Other Types in the System.Web.Security Namespace Have Been Moved という記事に以下のように書いてありました。

"Some types that are used in ASP.NET membership have been moved from System.Web.dll to the new System.Web.ApplicationServices.dll assembly. The types were moved in order to resolve architectural layering dependencies between types in the client and in extended .NET Framework SKUs."

従って、.NET 4 以降では System.Web.ApplicationServices.dll も参照設定に追加しないと MembershipUser クラスは使えません。

ただし、"System.Web.ApplicationServices.dll was added to the list of referenced assemblies that is used by default by the ASP.NET compilation system." とのことなので、ASP.NET Web アプリケーションではプログラマが参照設定を追加する必要はないそうです。

詳しくは上に紹介した Microsoft のサイトの記事を見てください。

Tags: ,

Authentication

About this blog

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

Calendar

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

View posts in large calendar