WebSurfer's Home

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

削除の際の @original_xxx はどこから取得?

by WebSurfer 2012年6月24日 00:21

楽観的同時実行制御を有効にした時、クエリの WHERE 句には [xxx] = @original_xxx というような条件が設定されます。SqlDataSource と DetailsView などを使った場合の削除操作の際、@original_xxx はどこから取得されるでしょうか?

DetailsView での削除操作

上の画像の例では、ItemTemplate に Label を配置して文字列を表示するようにしていますが、削除操作を行う場合、@original_xxx はどこから取得されるかというと、ItemTemplate に配置した Label の Text プロパティからです。

Label に文字列を表示しているのにブラウザの表示で改行されているのは DetailsView.PreRender イベントのハンドラで Environment.NewLine を "<br />" に置き換えているからです。

従って、このように表示される文字列をプログラムで書き換えてしまうと、DELETE クエリの WHERE 句で [xxx] = @original_xxx が成立しないので、削除に失敗します。(これは DetailsView に限らず、GridView などでも同じです)

ちなみに、Text='<%# Bind("text") %>' の Bind を Eval に変更すると、 DetailsViewDeleteEventArgs.Values["text"] は null になります。その 結果、削除ボタンクリックで InvalidOperationException がスローされます。

というわけで、楽観的同時実行制御を行って、この例のように Environment.NewLine を "<br />" に置き換えて表示するような場合、削除操作直前に、@original_text に代入される文字列を元に戻すしかなさそうです。

それは、以下のコードのように、SqlDataSource.Deleting イベントのハンドラで行うのがよさそうです。

<%@ 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">
  // ウィザードで作っていくと Label も Text='<%# Bind("text") %>' 
  // となる。なぜなら、@original_text に代入される文字列は Text 
  // プロパティから取得するから。それゆえ、以下のようにプログラム
  // で表示する文字列を書き換えると [text] = @original_text が 
  // true にならず、削除に失敗する。
  // Text='<%# Bind("text") %>' の Bind を Eval に変更すると、
  // DetailsViewDeleteEventArgs.Values["text"] は null になる。
  // 結果、[削除]ボタンクリックで InvalidOperationException
  // がスローされる。
  protected void DetailsView1_PreRender(object sender, 
    EventArgs e)
  {
    Control control = 
      ((DetailsView)sender).FindControl("Label1");

    if (control != null)
    {
      Label label = (Label)control;
      label.Text = 
        label.Text.Replace(Environment.NewLine, "<br />");
    }
  }

  // 検証用
  protected void DetailsView1_ItemDeleting(object sender, 
    DetailsViewDeleteEventArgs e)
  {
    // Environment.NewLine が <br /> に変更されて
    // いることが確認できる。
    string text = (string)e.Values["text"];
  }

  protected void SqlDataSource1_Deleting(object sender, 
    SqlDataSourceCommandEventArgs e)
  {
    // 削除を可能するには @original_text に代入される文字
    // 列を元に戻すしかない。
    string text = 
      (string)e.Command.Parameters["@original_text"].Value;
    text = text.Replace("<br />", Environment.NewLine);
    e.Command.Parameters["@original_text"].Value = text;
  }

</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
  <title></title>
</head>
<body>
  <form id="form1" runat="server">
  <div>
    <asp:SqlDataSource ID="SqlDataSource1" runat="server" 
      ConflictDetection="CompareAllValues" 
      ConnectionString="<%$ ConnectionStrings:MyDB %>" 
      DeleteCommand=
        "DELETE FROM [Table3] 
        WHERE [id] = @original_id AND [text] = @original_text" 
      InsertCommand=
        "INSERT INTO [Table3] ([text]) VALUES (@text)" 
      OldValuesParameterFormatString="original_{0}" 
      SelectCommand="SELECT [id], [text] FROM [Table3]" 
      UpdateCommand=
        "UPDATE [Table3] 
        SET [text] = @text 
        WHERE [id] = @original_id AND [text] = @original_text" 
      OnDeleting="SqlDataSource1_Deleting">
      <DeleteParameters>
        <asp:Parameter Name="original_id" Type="Int32" />
        <asp:Parameter Name="original_text" Type="String" />
      </DeleteParameters>
      <InsertParameters>
        <asp:Parameter Name="text" Type="String" />
      </InsertParameters>
      <UpdateParameters>
        <asp:Parameter Name="text" Type="String" />
        <asp:Parameter Name="original_id" Type="Int32" />
        <asp:Parameter Name="original_text" Type="String" />
      </UpdateParameters>
    </asp:SqlDataSource>

    <asp:DetailsView ID="DetailsView1" 
      runat="server" 
      AllowPaging="True" 
      AutoGenerateRows="False" 
      DataKeyNames="id" 
      DataSourceID="SqlDataSource1" 
      Width="550px" 
      OnPreRender="DetailsView1_PreRender" 
      OnItemDeleting="DetailsView1_ItemDeleting">
      <Fields>
        <asp:BoundField DataField="id" 
          HeaderText="id" 
          InsertVisible="False" 
          ReadOnly="True" 
          SortExpression="id" />
        <asp:TemplateField HeaderText="text" 
          SortExpression="text">
          <EditItemTemplate>
            <asp:TextBox ID="TextBox1" 
              runat="server" 
              Rows="15" 
              Text='<%# Bind("text") %>' 
              TextMode="MultiLine" 
              Width="500px">
            </asp:TextBox>
          </EditItemTemplate>
          <InsertItemTemplate>
            <asp:TextBox ID="TextBox1" 
              runat="server" 
              Rows="15" 
              Text='<%# Bind("text") %>' 
              TextMode="MultiLine" 
              Width="500px">
            </asp:TextBox>
          </InsertItemTemplate>
          <ItemTemplate>
            <asp:Label ID="Label1" 
              runat="server" 
              Text='<%# Eval("text") %>'>
            </asp:Label>
          </ItemTemplate>
        </asp:TemplateField>
        <asp:CommandField 
          ShowDeleteButton="True" 
          ShowEditButton="True" 
          ShowInsertButton="True" />
      </Fields>
    </asp:DetailsView>
  </div>
  </form>
</body>
</html>

Tags:

ASP.NET

SHDocVw.dll と AxSHDocVw.dll の作り方と使い方

by WebSurfer 2012年6月23日 16:11

ActiveX の WebBrowser コントロール (SHDocVw.dll) を .NET Windows Forms アプリで扱おうとしてかなりハマったので、再びそのようなことがないよう備忘録を残しておきます。

AxSHDocVw.dll を使用した Windows Forms アプリ

.NET Framework 2.0 以降では、SHDocVw.dll のマネージラッパーである WebBrowser コントロール が提供されていますので、.NET Windows Forms アプリで SHDocVw.dll を扱うことはあまりないかもしれませんが、公開され���ないイベントを利用したい場合などは困ります。

マネージラッパーの WebBrowser を拡張する方法(Extended .NET 2.0 WebBrowser Control 参照)もあるようですが、今回の目的(target="_blank" のリンクをクリックすると、セッションを維持して、上の画像のように別の Form1 に表示)程度なら、直接 SHDocVw.dll を扱ったほうが簡単そうに思えたので、そうしてみました。

以下の記事を読む前に、shdocvw.dll コンポーネントに関する予備知識として、MSDN ライブラリの Internet Explorer のアーキテクチャ に目を通しておくことをお勧めします。

まず、Windows Forms アプリで ActiveX コントロールをホストするには、AxHost クラス から派生するラッパーコントロールを生成する必要があります。

それには、SDK に含まれている Aximp.exe (Windows フォーム ActiveX コントロールインポーター) を使って、ActiveX コントロール用の COM タイプライブラリに属する型定義を Windows フォームコントロールに変換します。

具体的には、以下の画像のようにコマンドプロンプトから aximp.exe を起動し、shdocvw.dll(ActiveX コントロール用の COM タイプライブラリ。即ち IE のコンポーネント)から、Microsoft Web Browser コントロール用の共通言語ランタイムプロキシ (SHDocVw.dll) および Windows フォームプロキシ (AxSHDocVw.dll) を作成します。

コマンドプロンプトから aximp.exe を起動し SHDocVw.dll と AxSHDocVw.dll を生成

紛らわしいのが、IE のコンポーネントの shdocvw.dll と、Aximp.exe が生成する共通言語ランタイムプロキシ SHDocVw.dll の名前が同じという点です。名前が同じなだけで、中身は別物ですので注意してください。実は、自分が最初にハマったのがここです。(笑)

共通言語ランタイムプロキシの SHDocVw.dll を生成するので、IE のコンポーネントの shdocvw.dll が存在するフォルダで作業すると、「AxImp Error: 出力ファイル 'SHDocVw.dll' への書き込みエラーです。」というエラーが出てうまくいきません。

オプションで /out: e:\AxSHDocVw.dll というように、プロキシを出力したいフォルダを指定し(この例では e:\)、ファイル名を AxSHDocVw.dll とすれば、以下の画像のように期待通りプロキシが生成されます。なお、この例では /source オプションを追加しているので、AxSHDocVw.dll のソースと PDB ファイルが追加されています。

生成された SHDocVw.dll と AxSHDocVw.dll

以上で Microsoft Web ブラウザコントロール用の共通言語ランタイムプロキシ (SHDocVw.dll) および Windows フォームプロキシ (AxSHDocVw.dll) が作成できました・・・が、実は、このように手動で作る必要はなかったのでした。(汗)

Visual Studio のウィザードで作ることが可能で、その方法は以下の通りです。

まず、Visual Studio のツールボックスに Microsoft Web Browser を追加します。

Visual Studio のツールボックスに Microsoft Web Browser を追加

ツールボックスの空白部分をクリックして出てくるダイアログで[アイテムの選択(I)...]をクリックして[ツールボックス アイテムの選択]ダイアログボックスを表示します。

上の画像のように、[COM コンポーネント]タブをクリックし、一覧から Microsoft Web Browser を探してチェックを入れます。

[OK]をクリックすると、ツールボックスに WebBrowser コントロールが追加されます("Microsoft Web Browser" というテキストで表示されます)。

ツールボックスから Microsoft Web Browser を Form にドラッグ&ドロップすると自動的に SHDocVw.dll と AxSHDocVw.dll 作って、参照設定に加えてくれます。(下の「2012/7/1 注記追加」を参照)

参照設定に追加された AxSHDocVw と SHDocVw

あとは、以下のような NewWindow2 イベントのハンドラのコードを記述すれば、target="_blank" のリンクをクリックすると新たに Form1 生成されて表示され、その中の axWebBrowser に pdf が表示されます。また、セッションも引き継がれます(IE7 以前のブラウザはダメかも・・・未検証です)。

private void axWebBrowser1_NewWindow2(object sender, 
    AxSHDocVw.DWebBrowserEvents2_NewWindow2Event e)
{
    // target="_blank" のリンクをクリックしたとき、
    // 以下のコードがないと、新たに IE が開きそこ
    // に pdf が表示される。セッションは切れる。

    // 以下のコードがあると、新たに Form1 が表示さ
    // れ、その中の axWebBrowser に pdf が表示され
    // る。セッションは引き継がれる。

    Form1 frmWB = new Form1();
    frmWB.axWebBrowser1.RegisterAsBrowser = true;
    e.ppDisp = frmWB.axWebBrowser1.Application;
    frmWB.Visible = true;
}

----- 2012/7/1 注記追加 -----

Visual Studio 2010 のツールボックスから Microsoft Web Browser を Form にドラッグ&ドロップすると自動的に生成されるプロキシ(ラッパー)の名前は、Interop.SHDocVw.dll と AxInterop.SHDocVw.dll になります。(aximp.exe を使って作ったものとは中身も少々違うようです)

SHDocVw.dll と AxSHDocVw.dll は何が違うかというと、AxSHDocVw.dll のソースを見ての想像ですが、前者が ActiveX コントロールのプロパティ、メソッドの COM ラッパー、後者がそれらの COM ラッパーと .NET アプリを仲介するためのプロパティ、メソッド、イベントを提供するクラスのようです。

例えば、AxSHDocVw.dll に Application というプロパティが以下のように定義されています。SHDocVw.dll には return this.ocx.Application; で使われている Application という COM ラッパーが定義されてるということのようです。

namespace AxSHDocVw {
  [System.Windows.Forms.AxHost.ClsidAttribute(
    "{8856f961-340a-11d0-a96b-00c04fd705a2}")]
  [System.ComponentModel.DesignTimeVisibleAttribute(true)]
  [System.ComponentModel.DefaultProperty("Name")]
  public class AxWebBrowser : System.Windows.Forms.AxHost {
        
    private SHDocVw.IWebBrowser2 ocx;
        
    private AxWebBrowserEventMulticaster eventMulticaster;
        
    private System.Windows.Forms.
              AxHost.ConnectionPointCookie cookie;
        
    public AxWebBrowser() : 
            base("8856f961-340a-11d0-a96b-00c04fd705a2") {
    }
    
    [System.ComponentModel.DesignerSerializationVisibility(
      System.ComponentModel.
      DesignerSerializationVisibility.Hidden)]
    [System.Runtime.InteropServices.DispIdAttribute(200)]
    public virtual object Application {
      get {
        if ((this.ocx == null)) {
          throw 
            new System.Windows.Forms.AxHost.
                InvalidActiveXStateException(
                  "Application", 
                  System.Windows.Forms.AxHost.
                    ActiveXInvokeKind.PropertyGet);
        }
        return this.ocx.Application;
      }
    }
    // ・・・中略・・・
  }
  // ・・・中略・・・
}

上記はプロパティの場合ですが、イベントの場合はもっと複雑で、AxSHDocVw.dll には、イベントの宣言、デリゲートの定義、イベントの引数クラスの定義、クライアント・シンクのクラス定義等々が含まれます。詳しくは別の記事 WebBrowser の拡張 に書きましたのでそちらを見てください。

Tags:

.NET Framework

ListView でソート(行の並び替え)

by WebSurfer 2012年5月26日 15:35

ListView で、ヘッダのリンクボタンをクリックして、その列に設定した SortExpression に従って行の並べ替えを行う例です。

ListView でソート(列の並べ替え)を行う例

GridView の場合は、ウィザードを使ってコードは一行も書かないで簡単にできますが、ListView では少しですが自力でコードを書かなければなりません。

GridView の場合は AllowSorting プロパティを true に設定すれば、ヘッダが自動的に LinkButton になって、それをクリックすれば、その列に設定した SortExpression に従って列の並べ替えが行われます。

ところが、ListView には AllowSorting プロパティという便利なものはありません。ウィザードで作っていくと、ヘッダは普通のテキストになりますので、それを自力で LinkButton に置き換えて、Text、CommandName、CommandArgument の各プロパティを設定してやる必要があります。

Text にはヘッダに表示するテキスト、CommandName は Sort、CommandArgument は SortExpression を設定します。以下のコード例を参考にしてください。それだけで、GridView と同様な列の並べ替えが可能になります。

昇順/降順を示すイメージをヘッダのテキストに追加して表示する場合は、MSDN ライブラリの ListView.Sorting イベント のページのコードが参考になると思います。

以下、余談かもしれませんが、気がついたポイントを書いておきます。

SortExpression は複数の列を指定することもできます。例えば、A, B, C 列があるとすると、C 列の SortExpression を A, B, C のようにすることも可能です。LinkButton のクリックを繰り返すと、昇順/降順が切り替わりますが、ASC/DESC は SortExpression の末尾に追加されます(A, B, C DESC のように)。

SQL Server に投げられるクエリに ORDER BY xxxxx が追加されることはありません(プロファイラで見ると分かりますが、ヘッダの LinkButton をクリックすると、SqlDataSource の SelectCommand に設定されたクエリが毎回そのまま投げられます)。ListView と SqlDataSource の中でどのような操作が行われているかは見えないので定かではないですが、たぶん、取得したデータから DataView を作って、その Sort プロパティ に SortExpression を設定し、ソートした結果を ListView に表示しているようです。(これは GridView の場合も同じです)

SqlDataSource.DataSourceMode は DataSet でなければなりません。DataReader では、ソートを行うために LinkButton をクリックすると、System.NotSupportedException がスローされます。

以下にコード例をアップしておきます。上の画像の ListView を表示したコードです。

<%@ 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">
  // ListView でソートするサンプル

  protected void Button1_Click(object sender, EventArgs e)
  {
    // 初期状態に戻すには SortExpression を "" にして
    // ListView.Sort メソッドを実行する。(GridView で
    // も同様)
    ListView1.Sort("", SortDirection.Ascending);
  }
</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
  <title></title>
  <style type="text/css">
    table.style1
    {
      border-style: solid;
      border-width: 2px;
      border-color: Black;
      text-align: center;
      border-collapse: collapse;
    }
       
    table.style1 th
    {
      border-style: solid;
      border-width: 2px 1px 2px 1px;
      border-color: Black;
      padding: 0px 5px 0px 5px;
    }
        
    table.style1 td
    {
      border-style: solid;
      border-width: 1px;
      border-color: Black;
      padding: 0px 5px 0px 5px;
    }
  </style>

</head>
<body>
  <form id="form1" runat="server">
  <div>
    <asp:Button ID="Button1" 
      runat="server" 
      Text="初期状態に戻す" 
      OnClick="Button1_Click" />

    <asp:SqlDataSource ID="SqlDataSource1" 
      runat="server" 
      ConnectionString="<%$ ConnectionStrings:Northwind2 %>" 
      SelectCommand=
        "SELECT SupplierID, CategoryID, UnitPrice, ProductName 
        FROM Products" 
      DataSourceMode="DataSet">
    </asp:SqlDataSource>

    <asp:ListView ID="ListView1" 
      runat="server" 
      DataSourceID="SqlDataSource1">
      <ItemTemplate>
        <tr>
          <td>
            <asp:Label ID="SupplierIDLabel" runat="server" 
              Text='<%# Eval("SupplierID") %>' />
          </td>
          <td>
            <asp:Label ID="CategoryIDLabel" runat="server" 
              Text='<%# Eval("CategoryID") %>' />
          </td>
          <td>
            <asp:Label ID="UnitPriceLabel" runat="server" 
              Text='<%# Eval("UnitPrice") %>' />
          </td>
          <td>
            <asp:Label ID="ProductNameLabel" runat="server" 
              Text='<%# Eval("ProductName") %>' />
          </td>
        </tr>
      </ItemTemplate>
      <LayoutTemplate>
        <table>
          <tr>
            <td>
              <table ID="itemPlaceholderContainer" 
                runat="server" 
                class="style1">
                <tr>
            <%--ソーティングのためヘッダーのテキストを LinkButton 
               に置き換える。CommandName を Sort に設定し、
              CommandArgument を SortExpression に設定。--%>
                  <th>
                    <asp:LinkButton ID="LinkButton1" 
                      runat="server" 
                      Text="SupplierID" 
                      CommandName="Sort" 
                      CommandArgument="SupplierID" />
                  </th>
                  <th>
                    <asp:LinkButton ID="LinkButton2" 
                      runat="server" 
                      Text="CategoryID" 
                      CommandName="Sort" 
                      CommandArgument="SupplierID,CategoryID" />
                  </th>
                  <th>
                    <asp:LinkButton ID="LinkButton3" 
                      runat="server" 
                      Text="UnitPrice" 
                      CommandName="Sort" 
                      CommandArgument=
                        "SupplierID,CategoryID,UnitPrice" />
                  </th>
                  <th>
                    <asp:LinkButton ID="LinkButton4" 
                      runat="server" 
                      Text="ProductName" 
                      CommandName="Sort" 
                      CommandArgument="ProductName" />
                  </th>
                </tr>
                <tr runat="server" ID="itemPlaceholder">
                </tr>
              </table>
            </td>
          </tr>
          <tr>
            <td>
            <%--以下はオマケのちょっと凝ったページャー。--%>
              <asp:DataPager ID="DataPager1" runat="server">
                <Fields>
                  <asp:TemplatePagerField>              
                    <PagerTemplate>
                      Page
                      <asp:Label runat="server" 
                        ID="CurrentPageLabel" 
                        Text="<%# Container.TotalRowCount>0 ? 
                          (Container.StartRowIndex / Container.PageSize) + 1 : 0 %>" />
                      of
                      <asp:Label runat="server" 
                        ID="TotalPagesLabel" 
                        Text="<%# Math.Ceiling (
                          (double)Container.TotalRowCount / Container.PageSize) %>" />
                      (
                      <asp:Label runat="server" 
                        ID="TotalItemsLabel" 
                        Text="<%# Container.TotalRowCount%>" />
                      records)
                      <br />
                    </PagerTemplate>
                  </asp:TemplatePagerField>
                                    
                  <asp:NextPreviousPagerField
                    ButtonType="Button"
                    ShowFirstPageButton="true"
                    ShowNextPageButton="false"
                    ShowPreviousPageButton="false" />
                  <asp:NumericPagerField 
                    PreviousPageText="< Prev 3"
                    NextPageText="Next 3 >"
                    ButtonCount="3" />
                  <asp:NextPreviousPagerField
                    ButtonType="Button"
                    ShowLastPageButton="true"
                    ShowNextPageButton="false"
                    ShowPreviousPageButton="false" />
                </Fields>
              </asp:DataPager>
            </td>
          </tr>
        </table>
      </LayoutTemplate>
    </asp:ListView>
  </div>
  </form>
</body>
</html>

Tags: ,

ASP.NET

About this blog

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

Calendar

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

View posts in large calendar