WebSurfer's Home

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

ASP.NET の ID オブジェクト

by WebSurfer 2014年1月20日 16:36

ASP.NET Web Forms アプリケーションの Page 内では、以下の 3 つのプロパティ/メソッドで ID オブジェクト(WindowsIdentity または GenericIdentity)を取得できます。

それぞれどのように違うかを調べましたので、備忘録として書いておきます。


HttpRequest.LogonUserIdentity

現在 IIS が HTTP 要求を受けているユーザーの WindowsIdentity(Windows ユーザーを表す)オブジェクトを取得します。

匿名アクセス(Page.User.Identity.IsAuthenticated が false)の場合は常に IUSR となります。IUSR とは、匿名認証が有効化されている場合は常に IIS によって使用される既定の ID です。

ログイン済みユーザー(Page.User.Identity.IsAuthenticated が true)の場合は認証方式によって異なり、以下の通りとなります。

  • フォーム認証:ワーカープロセス(例:IIS7 では NETWORK SERVICE)
  • Windows 認証:ログインユーザーの Windows アカウント

ASP.NET 偽装の有効・無効には影響を受けず、常に上記の通りとなります。


WindowsIdentity.GetCurrent

ASP.NET 偽装が無効になっている場合は、ワーカープロセスを実行しているアカウント(IIS7 では NETWORK SERVICE)の WindowsIdentity オブジェクトを取得します。

ASP.NET 偽装が有効になっている場合は、偽装の設定方法、認証方式、匿名アクセスかログイン済みかによって、取得される WindowsIdentity オブジェクトは異なります(「偽装」しているだけで、実際にワーカープロセスを実行しているアカウントは変わらないのかもしれません・・・未確認です)。

web.config での偽装の設定を、単純に <identity impersonate="true" /> とした場合には以下のようになります。

フォーム認証

  • 匿名アクセス:IUSR
  • ログイン済み:ワーカープロセス(例:IIS7 では NETWORK SERVICE)

Windows 認証

  • 匿名アクセス:IUSR
  • ログイン済み:ログインユーザーの Windows アカウント

上記の偽装の設定に userName 属性、password 属性の設定を追加し、特定の Windows ユーザーアカウントを偽装した場合は、認証方式や認証済みか否かに関わらず、常に userName 属性に設定したユーザーアカウントになります。


Page.User.Identity

現在ページ要求を行っているユーザーを表す ID オブジェクトを取得します。

Page.User プロパティは、Windows 認証が有効になっている場合は WindowsPrincipal を、Windows 認証が無効の場合は RolePrincipal(ロール メンバシップを含む現在の HTTP 要求のセキュリティ情報)を取得します。

そして、WindowsPrincipal.Identity からは WindowsIdentity オブジェクトが、RolePrincipal.Identity からは GenericIdentity(標準ユーザーを表す)が取得されます。

ただし、それらの中身は ASP.NET が書き換えており、特に Name は、匿名アクセスの場合は IUSR ではなく空文字になること、フォーム認証のログイン済みユーザーの場合は Windows アカウント名ではなくフォーム認証のユーザー ID となる点に注意してください。

具体的には、IsAuthenticated, AuthenticationType, Name プロパティは以下の通りとなります。

フォーム認証、匿名アクセス

  • IsAuthenticated: false
  • AuthenticationType:(空文字)
  • Name:(空文字)

フォーム認証、ログイン済み

  • IsAuthenticated: true
  • AuthenticationType: Forms
  • Name: フォーム認証のユーザー ID

Windows 認証、匿名アクセス

  • IsAuthenticated: false
  • AuthenticationType:(空文字)
  • Name:(空文字)

Windows 認証、ログイン済み

  • IsAuthenticated: true
  • AuthenticationType: Negotiate
  • Name: ログインユーザーの Windows アカウント

Tags:

ASP.NET

Validator の Display="Dynamic" 時の注意点

by WebSurfer 2014年1月16日 17:45

ASP.NET の検証コントロール(RequiredFieldValidator, RegularExpressionValidator など)を使用して、Display プロパティを Dynamic に設定したときの注意点です。

Validator の Display="Dynamic" 時の注意点

この件は stackoverflow の記事 でも報告されています。ただ、実は自分はつい最近までこの問題は知らなかったです。(汗)

例えば、以下のように RequiredFieldValidator を配置して、その直後に Button を配置したとします。

<%@ 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">
  protected void Button1_Click(object sender, EventArgs e)
  {
    if (Page.IsValid)
    {
      // 何らかの処置。
    }
  }
</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
  <title></title>
</head>
<body>
  <form id="form1" runat="server">
  <div>
    <asp:TextBox ID="TextBox1" runat="server"></asp:TextBox>
    <asp:RequiredFieldValidator ID="RequiredFieldValidator1" 
      runat="server" 
      ErrorMessage="エラーメッセージ"  
      ControlToValidate="TextBox1" 
      Display="Dynamic">
    </asp:RequiredFieldValidator>
    <asp:Button ID="Button1" 
      runat="server" 
      Text="Button" 
      OnClick="Button1_Click" />                
  </div>
  </form>
</body>
</html>

上記のコードでは以下の手順で問題を再現できます。

  1. TextBox は空のまま Button をクリックする。
  2. クライアント側で検証がかかり、ErrorMessage プロパティに設定した「エラーメッセージ」が表示される。
  3. TextBox に文字を入力して Button をクリックする。 ⇒ 「エラーメッセージ」は消えるがポストバックがかからない。
  4. 再度 Button をクリックする。 ⇒ ポストバックがか���る。

期待される動きは、上記 3 で「エラーメッセージ」が消えるとともにポストバックがかかるということのはずですが、そうはなりません。

上記のコードにおける解決策は、Button の直前に改行 ( <br /> ) を入れることです。そうしないとうまく行かない理由は以下の通りです。

Validator のエラーメッセージは html コードでは span 要素となり、JavaScript による検証結果により表示/非表示を切り替えています。

Display プロパティが Dynamic に設定されている場合は、当該 span 要素の style 属性を "display:none;" または "display:inline;" に設定することにより表示/非常時を切り替えます。

検証対象の TextBox にフォーカスを当ててからフォーカスを外す(例えば、TextBox に入力してから form を submit するために Button をクリックする)と、 そのタイミングで JavaScript による検証がかかるようになっています。

検証結果によって "display:inline;" が "display:none;" に(またはその逆に)書き換えられるので、エラーメッセージの部分のページレイアウトが変わることになります。

従って、上記のコードのように、RequiredFieldValidator の直後に Button が配置されているような場合、エラーメッセージが表示/非表示になる分だけ画面上でボタンが左右に移動します。

ボタンが移動すると、<input type="submit" ... /> タイプのボタンをクリックしたにもかかわらず form が submit されません。 即ち、ポストバックされないという期待に反する動作になります。

なお Button が動くのは左右でなくても、例えばエラーメッセージを p 要素に入れると上下に移動しますが、その場合でも同じくform は submit されません。

このことは、ASP.NET の検証コントロールを使った場合に限った話ではなく、html 要素と JavaScript だけでも再現できます。 以下のサンプルコードは、html 要素と JavaScript だけでこの問題(ボタンが動くと submit されない)を再現する例です。

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

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

</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
  <title>WebSurfer's Page - 実験室</title>
  <script src="/scripts/jquery-1.8.3.min.js" type="text/javascript">
  </script>
  <script type="text/javascript">
  //<![CDATA[
    function toggleDisplay() {
      var validator = $('#RequiredFieldValidator1');
      if (validator.css("display") == "none") {
        validator.css("display", "inline");
      } else {
        validator.css("display", "none");
      }            
    }
  //]]>
  </script>
</head>
<body>
  <form id="form1" runat="server" 
    onsubmit="javascript:return confirm('Submit しますか?');">

  <div>
    <input name="textbox1" type="text" id="1extbox1" 
      onblur="javascript: toggleDisplay();" />
    <span id="RequiredFieldValidator1" 
      style="display:none;">エラーメッセージ</span>
    <input type="submit" name="button1" value="POST" 
      id="button1" />    
  </div>
  </form>
</body>
</html>

ちなみに、Display プロパティが Static に設定されている場合は、style 属性を "visibility:hidden;" または "visibility:visible" に設定するので、 表示/非表示を切り替えてもページのレイアウトは変わりません。結果、ボタンは動かないのでこの問題は起こりません。

Tags: ,

Validation

Paging 機能付 GridView の行選択

by WebSurfer 2013年12月19日 16:59

GridView に CheckBox を配置し、ユーザーに複数行を選択してもらうというシナリオはよくあると思います。この場合、GridView にページング機能が実装されていると問題です。

Paging 機能付 GridView の行選択結果取得

例えば、Page 1 でいくつかの行を選択して CheckBox にチェックを入れてから Page 2 に移動し、再び Page 1 に戻った場合、先に Page 1 で CheckBox に入れたはずのチェックが消えてしまいます。

この問題に対処するためには、以前のチェック情報を ViewState に保持しておき、ページが変わったら ViewState からそのページのチェック情報を取得して CheckBox.Checked プロパティを true に設定してやるというような操作が必要になります。

ページャーがクリックされるとポストバックが発生しますので、そのタイミングでクライアントスクリプトによってチェック結果を取得して隠しフィールドに格納し、それをサーバーに送信して ViewState に保存するようにしてみました。

CheckBox の ID には、GridView.DataKeys プロパティから主キー値を取得し、それを設定しています。それゆえ、クライアントスクリプトでもチェックされた CheckBox の name 属性から当該レコードの主キー値が分かります。

ただし、ID から name への名付けルールが変わるとうまく行かなくなる可能性がありますので、できれば主キー値を GridView に表示して、それから取得するようにした方がいいかもしれません。

以下のソースコードは主キー値を name 属性から取得する場合の例です。上の画像がソースコードの実行結果です。

注意事項はソースコード内のコメントに書きましたので、詳しくはそれを見てください。手抜きですみません。(汗)

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

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

<script runat="server">
  // CheckBox をクリックするたびポストバックしないで、ページン
  // グでポストバックした際に、まとめてチェック結果を取得し、
  // チェックされた行の主キー (ID) リストを更新する。

  // クライアント側で、input type="checkbox" 要素の id 属性また
  // は name 属性から、その要素のある行の主キー (ID) を取得する
  // ため、CheckBox.ID に主キー値を設定。主キー値が 17 の場合
  // id="GridView1_17_0" name="GridView1$ctl02$17" のようになる。
  // このサンプルでは、クライアントスクリプトで name 属性から
  // 主キー値を取得している。名前付けルールが変わってうまくいか
  // なくなる可能性があるので注意。
    
    
  // チェック入り行の主キー (ID) のリスト。ViewState に保持。    
  protected List<String> checkedIds;
    
  protected void Page_Load(object sender, EventArgs e)
  {
    // ViewState から ID リスト checkedIds を取得。
    if (checkedIds == null)
    {
      object obj = ViewState["CheckedIds"];
      if (obj != null)
      {
        checkedIds = (List<String>)obj;
      }
      else
      {
        // ViewState が未設定の場合は新たに初期化。
        checkedIds = new List<String>();
      }
    }

    // form を送信する際、CheckBox のチェック有無を調べて、
    // その結果をサーバーに送信するスクリプトを設定。
    String csname = "OnSubmitScript";
    Type cstype = this.GetType();
    ClientScriptManager cs = Page.ClientScript;
    if (!cs.IsOnSubmitStatementRegistered(cstype, csname))
    {
      String cstext = 
          "getCheckedIDs('" + GridView1.ClientID + "');";
      cs.RegisterOnSubmitStatement(cstype, csname, cstext);
    }
  }

  // ページャクリックの場合のイベント発生順序に注意:
  // 現ページの GridView.RowCreated ⇒ Page.Load ⇒ GridView.
  // PageIndexChanging ⇒ 次ページの GridView.RowCreated

  // ちなみに、ボタンクリックによるイベント発生順序は:
  // 現ページの GridView.RowCreated ⇒ Page.Load ⇒ 
  // Button.Click
    
  // CheckBox.ID を動的に設定するのを RowDataBound イベント
  // で行うのは NG。ボタンクリックではデータバインドが起こ
  // らないので、CheckBox.ID が設定されず(ASP.NET が勝手に
  // 独自の ID を生成する)、うまくいかない。
  protected void GridView1_RowCreated(object sender, 
        GridViewRowEventArgs e)
  {
    if (e.Row.RowType == DataControlRowType.DataRow)
    {
      // ViewState から ID リスト checkedIds を取得。
      // ボタンクリックまたはページングによるポストバック
      // では Page.Load よりこちらのイベントが先に発生す
      // るのでここでも必要。
      if (checkedIds == null)
      {
        object obj = ViewState["CheckedIds"];
        if (obj != null)
        {
          checkedIds = (List<String>)obj;
        }
        else
        {
          // ViewState が未設定の場合は新たに初期化。
          checkedIds = new List<String>();
        }
      }
            
      // この行の id(主キー)値を取得。
      string id = ((GridView)sender).
          DataKeys[e.Row.RowIndex].Value.ToString();

      // e.Row.Cells[0] の中から CheckBox を探す。
      foreach (Control control in e.Row.Cells[0].Controls)
      {
        if (control is CheckBox)
        {
          CheckBox cb = (CheckBox)control;

          // CheckBox.ID に id を設定。クライアントスクリプト
          // で CheckBox の name 属性から id を取得できるよう
          // にするため。
          // このサンプルでは name は GridView1$ctl02$17 のよ
          // うになり、最後の 17 が id に該当。
          cb.ID = id;

          // id がリスト checkedIds にあればチェックを入れる。
          foreach (string checkedid in checkedIds)
          {
            if (checkedid == id)
            {
              cb.Checked = true;
              return;
            }
          }

          cb.Checked = false;
          break;
        }
      }
    }
  }

  // ページが変更される際にリスト checkedIds を更新する。
  protected void GridView1_PageIndexChanging(object sender, 
        GridViewPageEventArgs e)
  {
    // form の onsubmit イベントで、クライアントスクリプトが
    // CheckBox のチェック有無の情報を取得し、当該行の id を
    // 隠しフィールドに格納するので、それから id を取得する。
    char[] chars = new char[] { ',' };
    string s = Request.Form["__CHECKEDIDLIST"].Trim(chars);
    string[] check = s.Split(chars);
    s = Request.Form["__UNCHECKEDIDLIST"].Trim(chars);
    string[] uncheck = s.Split(chars);

    // 重複を避けるため、一旦、GridView 上のすべての ID を
    // リスト checkedIds から削除してから、
    foreach (string id in check)
    {
      checkedIds.Remove(id);
    }

    foreach (string id in uncheck)
    {
      checkedIds.Remove(id);
    }

    // チェック済の ID をリスト checkedIds に加える。
    foreach (string id in check)
    {
      if (!String.IsNullOrEmpty(id))
      {
        checkedIds.Add(id);
      }
    }

    // リスト checkedIds を ViewState に保持する。
    ViewState["CheckedIds"] = checkedIds;
  }
        
  // ボタンクリックでチェックされた全行の id を書き出す。
  protected void Button1_Click(object sender, EventArgs e)
  {
    // ボタンクリック直前のチェック結果をここで取得。
    char[] chars = new char[] { ',' };
    string s = Request.Form["__CHECKEDIDLIST"].Trim(chars);
    string[] check = s.Split(chars);
    s = Request.Form["__UNCHECKEDIDLIST"].Trim(chars);
    string[] uncheck = s.Split(chars);
        
    // チェック入り行の主キー (ID) リスト checkedIds を更新。
    foreach (string id in check)
    {
      checkedIds.Remove(id);
    }

    foreach (string id in uncheck)
    {
      checkedIds.Remove(id);
    }
        
    foreach (string id in check)
    {
      if (!String.IsNullOrEmpty(id))
      {
        checkedIds.Add(id);
      }
    }
        
    ViewState["CheckedIds"] = checkedIds;

    // リスト checkedIds の内容を書き出す。
    string str = "Selected Ids:";
    foreach (string id in checkedIds)
    {
      str += " " + id;
    }
    Label1.Text = str;
  }
</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
  <title></title>
  <script src="Scripts/jquery-1.8.3.js" type="text/javascript">
  </script>
  <script type="text/javascript">
  //<![CDATA[
    // CheckBox のチェック有無を調べて、その結果を隠し
    // フィールドに設定するスクリプト。
    function getCheckedIDs(gridViewClientId) {
      var checked = "";
      var unchecked = "";

      $('#' + gridViewClientId + ' input:checkbox').each(
        function () {
          // このサンプルでは name は GridView1$ctl02$17 
          // のようになり、最後の 17 が id に該当。
          // ただし MasterPage を使うなど、名前付コンテナ
          // に GridView を入れると違ってくるので注意。
          var name = $(this).attr('name');
          var arrayOfString = name.split('$');
          var id = arrayOfString[arrayOfString.length - 1];
          if ($(this).attr('checked') == "checked") {
            checked += ',' + id;
          } else {
            unchecked += ',' + id;
          }
        });

      $('#__CHECKEDIDLIST').val(checked);
      $('#__UNCHECKEDIDLIST').val(unchecked);
    }
  //]]>
  </script>
</head>
<body>
  <form id="form1" runat="server">

  <%--チェック結果をサーバに送信する隠しフィールド。--%>
  <input type="hidden" 
    name="__CHECKEDIDLIST" 
    id="__CHECKEDIDLIST" 
    value="" />
  <input type="hidden" 
    name="__UNCHECKEDIDLIST" 
    id="__UNCHECKEDIDLIST" 
    value="" />

  <div>        
    <asp:SqlDataSource ID="SqlDataSource1" runat="server" 
      ConnectionString="<%$ ConnectionStrings:Northwind %>" 
      SelectCommand= "SELECT [ProductID], [ProductName] 
          FROM [Products] ORDER BY [ProductID]">
    </asp:SqlDataSource>
    <asp:GridView ID="GridView1" 
      runat="server" 
      AllowPaging="True" 
      AutoGenerateColumns="False" 
      DataKeyNames="ProductID" 
      DataSourceID="SqlDataSource1" 
      OnPageIndexChanging="GridView1_PageIndexChanging" 
      OnRowCreated="GridView1_RowCreated">
      <Columns>                
        <asp:TemplateField HeaderText="Select">                    
          <ItemTemplate>
            <asp:CheckBox runat="server" />
          </ItemTemplate>                    
        </asp:TemplateField>                
        <asp:BoundField DataField="ProductID" 
          HeaderText="ProductID" 
          InsertVisible="False" 
          ReadOnly="True" 
          SortExpression="ProductID" />
        <asp:BoundField DataField="ProductName" 
          HeaderText="ProductName" 
          SortExpression="ProductName" />
      </Columns>
    </asp:GridView>
    <asp:Button ID="Button1" 
      runat="server" 
      Text="Show Checked Ids" 
      OnClick="Button1_Click" />
    <asp:Label ID="Label1" runat="server"></asp:Label>
  </div>
  </form>
</body>
</html>

Tags: ,

ASP.NET

About this blog

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

Calendar

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

View posts in large calendar