WebSurfer's Home

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

型付 DataSet の名前に注意

by WebSurfer 2018年4月9日 14:33

Visual Stidio のデータソース構成ウィザードを使って型付 DataSet を生成するとき、その名前を DataSet とすると、System.Data.DataSet と名前が衝突して問題を起こすので要注意という話を書きます。

型付 DataSet の作成

上の画像は、ウィザードで型付 DataSet(.xsd ファイル)を生成しようとしているところですが、ファイル名がデフォルトで DataSet.xsd となるのが諸悪の根源(?)です。

デフォルトの名前が DataSet.xsd のなるのは、自分が調べた限りでは、Visual Studio Cummunity 2015 で、ASP.NET Web Forms アプリケーションを Web サイトプロジェクトとして作り、その App_Code フォルダに .xsd ファイルを生成する場合だけでした。(それ以外ではデフォルトの名前は DataSet1.xsd となります)

なので余計に気づき難いと思います。もちろんコンパイルエラーは出ませんし。

具体的にどういうことになるかと言うと、以下の通りです。

ファイル名を DataSet.xsd とすると、それから生成される型付 DataSet の名前も以下の通り DataSet となります。

/// <summary>
///Represents a strongly typed in-memory cache of data.
///</summary>
 [global::System.Serializable()]
// ・・・中略・・・
public partial class DataSet : global::System.Data.DataSet {
        
    private ProductsDataTable tableProducts;

    // ・・・中略・・・
}

そうなると、単に DataSet と宣言しただけでは型付 DataSet に定義された DataSet と見なされます。using 句に System.Data を設定しても同じです。

下の画像を見てください。上の型付 DataSet のコードの summary に設定された "Represents a strongly typed in-memory cache of data." がインテリセンスで表示されています。

インテリセンスの表示

この状態で System.Data.DataSet を使うには、上の画像の一番下の行に書いたようにしなければなりません。

自分のケースでは、上の ds を ds.ReadXml(xmlTextReader); というように使ったのですが、ds にデータを取得できない(エラーも出ない)というトラブルにハマって 1 ~ 2 時間悩みました。

今時こんなトラブルにハマるのは自分だけかもしれませんが、一応備忘録として残しておくことにします。(笑)

Tags:

.NET Framework

SessionStateModule によるロック

by WebSurfer 2018年4月6日 18:21

ASP.NET Web アプリで Session を利用する場合、同一ユーザー(= SessionID が同じユーザー)が同時にアクセスすると、最初の要求に受けた時点で Session へのアクセスがロックされるので、最初の要求に対する応答が返されるまで次の要求は待たされるという話を書きます。

(元の話は MSDN フォーラムのスレッド「非同期ポストバック中のキャンセルとその後のポストバックについて」です)

非同期ポストバック中のキャンセル

SessionStateModule によるロックメカニズムはマイクロソフト公式解説書「プログラミング Microsoft ASP.NET 4」という本の 17.2.1 章「セッション状態 HTTP モジュール」の「セッション状態へのアクセスの同期」というセクションに詳しく書いてあります。その一部を抜粋すると:

"セッション状態モジュールはリーダー / ライター方式のロックメカニズムを実装し、状態値へのアクセスをキューで管理します。セッション状態への書き込みアクセスを許可されたページは、リクエストの処理が終了するまで、そのセッションのライターロックを保持します。 ・・・(中略)・・・ セッション状態への読み取りアクセスを許可されたページは、リクエストの処理が終了するまでセッションのリーダーロックを保持します"

上記の通り、Session を使うケースでは、応答が返されるまで Session へのアクセスはロックされ、その間次の要求は処理されないのですが、普通に同期ポストバックを行っている限り、それにユーザーが気が付くことはなさそうです。

ところが、ScriptManager + UpdataPanel を利用して非同期ポストバックで要求を出すようにし、それにキャンセル機能を実装した場合は話が違ってきます。

ユーザーが、処理に長い時間がかかる「要求 A」を出したが、応答が返ってくるのを待ちきれないので、一旦その要求をキャンセルして、処理に時間のかからない(=すぐ応答が返ってくる)「要求 B」を出すというケースを考えてみてください。

ユーザーは、「要求 A」はキャンセルしたのだから、「要求 B」はすぐ処理されて応答が返ってくると期待するはずです。

ところが Session を利用しているアプリの場合はユーザーの期待通りにはなりません。「要求 A」をキャンセルする / しないにかかわらず、「要求 B」の応答が返ってくるのは、普通に「要求 A」を処理する時間だけ待たされた後になります。

ユーザーが「要求 A」をキャンセルしてもサーバー側での実行が中断されるわけではなく、サーバーは「要求 A」を処理して応答を返すところまで行うということがポイントです。

Session を利用していない場合、「要求 B」をサーバーが受けると、先に受けた「要求 A」の処理と並行して、直ちに「要求 B」の処理が始まって応答が返されます。結果、ユーザーの期待通り、処理に時間のかからない「要求 B」の応答はすぐ返ってきます。

ところが、Session を利用している場合、Session ロックによって、「要求 A」の処理が終わって応答が返されるまで、「要求 B」の処理は待たされます。結果、「要求 A」の処理のキャンセルが効いてないように見えます。

下の画像を見てください。この記事の下の方に示したサンプルコードを実行し、Fiddler で要求・応答をキャプチャしたものです。

Fiddler によるキャプチャ

実行手順は以下の通りです。

  1. [非同期]ボタンをクリックして非同期ポストバックによる「要求 A」をかける。(この記事の一番上の画像がその時のもの)
  2. [Cancel]ボタンをクリックして「要求 A」をキャンセル。
  3. [同期]ボタンをクリックして「要求 B」をかける。

上の Fiddler のキャプチャ画像で、キャンセルした上記 1 の要求もサーバー側では正しく処理され、完全な応答が返ってきているのが分かるでしょうか? (ただし、ブラウザで abort されています)

問題のページで Session に書き込んでいる場合は何ともならないですが、そうでない場合は以下の解決策で対応できるはずです。

  1. 他のページで Session を使っているが、問題のページでは使ってなければ EnableSessionState を False に設定する。
  2. 問題のページも Session を使っているが、読み取りのみの場合は EnableSessionState を ReadOnly に設定する。

なお、自分では Session は使って���ないつもり(コードで明示的に Session["Data"] = xxx のようなことはしていない)でも、Global.asax に Session_Start ハンドラがあるとすべてのページで Session を使っているのと同じことになります。

Visual Studio のテンプレートを使ってプロジェクトを作ると、Global.asax に空の Session_Start ハンドラが生成されることがありますので注意してください。

最後に、この記事を書くときに検証用に使ったサンプルコードを以下に書いておきます。

<%@ Page Language="C#" EnableSessionState="ReadOnly" %>

<!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 Button_Click(object sender, EventArgs e)
    {
        System.Threading.Thread.Sleep(5000);
    }

    protected void Button2_Click(object sender, EventArgs e)
    {
        
    }
</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
  <script type="text/javascript">
  //<![CDATA[
  var manager;

  function pageLoad(sender, args) {
    if (args.get_isPartialLoad() === false) {
      manager = Sys.WebForms.PageRequestManager.getInstance();
      manager.add_initializeRequest(OnInitializeRequest);
      manager.add_endRequest(OnEndRequest);
    }
  }

  function OnInitializeRequest(sender, args) {
    $get("<%=UpdatePanel1.ClientID%>").style.display = "none";
  }

  function OnEndRequest(sender, args) {
    $get("<%=UpdatePanel1.ClientID%>").style.display = "block";
  }

  function AbortPostBack() {
    if (manager.get_isInAsyncPostBack()) {
      manager.abortPostBack();
    }
  }
  //]]>
  </script>

    <style type="text/css">
    #UpdatePanel1 { 
      width: 200px; 
      height: 200px; 
      border: gray 2px solid;
      position: relative;
      float: left; 
      margin-left: 10px; 
      margin-top: 10px;
    }

    #UpdateProgress1 {
      width: 400px; 
      background-color: #FFC080;
      border: gray 1px solid;
      /*bottom: 0%; 
      left: 0px; 
      position: absolute;*/
    }

  </style>

</head>
<body>
    <form id="form1" runat="server">
    <asp:ScriptManager ID="ScriptManager1" runat="server">
    </asp:ScriptManager>

    <asp:Button ID="Button1" runat="server" 
        Text="非同期" OnClick="Button_Click" />  
    <asp:Button ID="Button2" runat="server" 
        Text="同期" OnClick="Button2_Click" />
    <br />
    <asp:UpdatePanel ID="UpdatePanel1" runat="server">
        <ContentTemplate>
            UpdatePanel
            <hr />            
            <%=DateTime.Now.ToString() %> <br />
            
            <br /><br />
            [非同期]ボタンをクリックすると、
            5 秒後にこのパネル内が更新されます。
            その間 UpdateProgress が表示されます。
        </ContentTemplate>
        <Triggers>
            <asp:AsyncPostBackTrigger ControlID="Button1" 
                EventName="Click">
            </asp:AsyncPostBackTrigger>
        </Triggers>
    </asp:UpdatePanel>

    <asp:UpdateProgress ID="UpdateProgress1" runat="server">
        <ProgressTemplate>
            非同期ポストバックで更新中です・・・  
            <input type="button" value="Cancel" 
                onclick="AbortPostBack()" />            
        </ProgressTemplate>
    </asp:UpdateProgress>
    </form>
</body>
</html>

Tags: ,

ASP.NET

非同期 HTTP ハンドラ (2)

by WebSurfer 2018年3月8日 18:56

先の記事「非同期 HTTP ハンドラ」で、Web サービスに非同期でアクセスする HTTP ジェネリックハンドラのサンプルを書きました。

その HTTP ハンドラは IHttpAsyncHandler インターフェイスを継承して BeginProcessRequest, EndProcessRequest メソッドを実装するという旧来の方法を使っており、記事に書いてありますように非常に複雑なコードになります。

ここまで複雑なことをしなければならないのなら、HTTP 503 エラー (Server Too Busy) が頻発して困っているというような事情がなければ、従来通り同期版のままでもいいかなって気もします。(笑)

ですが、.NET 4.5 から簡単に非同期呼び出しが実装できるようになったそうです。どのぐらい簡単にできるのか、同じ機能を async / await を利用して実装してみました。

非同期 HTTP ハンドラ

確かにはるかに簡単でした。前のように、何がどうなっているのかを調べて、悩んで、時間をかけて実装するという手間は大幅に減っています。

上の画像は、この記事に書いた方法で作成した HTTP ハンドラを、ブラウザから直接呼び出した結果です。具体的にどのように実装したかは以下に書きます。

HelloWorldWcfService.cs

先の記事の Web サービスは WCF サービスに変更しました。HelloWorld メソッドの実装は先の記事の Web サービスのものと全く同じです。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using System.Threading;
using System.ServiceModel.Activation;

[AspNetCompatibilityRequirements(RequirementsMode =
            AspNetCompatibilityRequirementsMode.Allowed)]
public class HelloWorldWcfService : IHelloWorldWcfService
{
    public string HelloWorld(int callDuration)
    {
        Thread.Sleep(callDuration);
        return String.Format(
          "Hello World from WcfService. Call Time: {0}",
          callDuration);
    }
}

WCF サービスに変更したのは、Web サービスが Legacy Technology だからということもありますが、一番の理由はサービス参照の追加を行うと自動生成されるサービスプロキシに含まれる非同期版メソッドが利用できるからです。

(非同期版メソッドについて、詳しくは先に記事「WCF サービスの非同期呼び出し」を見てください)

HelloWorldAsyncHnadler.ashx

Visual Studio で、既存の ASP.NET Web Forms アプリにジェネリックハンドラーを追加します。.ashx ファイルには何も手を加える必要はありません。

<%@ WebHandler Language="C#"
    CodeBehind="HelloWorldAsyncHandler.ashx.cs"
    Class="WebFormsApp.HelloWorldAsyncHandler" %>

Web アプリケーションプロジェクトの場合、.ashx ファイルとそのコードビハインドの .ashx.cs ファイルは別になります。Web サイトプロジェクトの場合、すべて .ashx ファイルに含まれるという違いがあります。

HelloWorldAsyncHandler.ahsx.cs

自動生成されたコードは同期版ハンドラのベースとなるものです。これを HttpTaskAsyncHandler クラスを継承したクラスに書き換えます。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Threading;
using System.Threading.Tasks;
using WebFormsApp.HelloWorldServiceReference;

namespace WebFormsApp
{
    public class HelloWorldAsyncHandler : HttpTaskAsyncHandler
    {
        HelloWorldWcfServiceClient client;

        // これが呼び出されることはない。万一呼び出されたら例外
        // をスローして自爆する
        public override void ProcessRequest(HttpContext context)
        {
            throw new InvalidOperationException();
        }
        
        public override async Task ProcessRequestAsync(
                                            HttpContext context)
        {
            context.Response.Write(
                "<p>Before await:<br />" +
                " IsThreadPoolThread is " +
                Thread.CurrentThread.IsThreadPoolThread +
                "<br />" +
                " ManagedThreadId is " +
                Thread.CurrentThread.ManagedThreadId +
                "</p>");

            client = new HelloWorldWcfServiceClient();

            string result = await client.HelloWorldAsync(3000);

            context.Response.Write(
                "<p>After await:<br />" +
                " IsThreadPoolThread is " +
                Thread.CurrentThread.IsThreadPoolThread +
                "<br />" +
                " ManagedThreadId is " +
                Thread.CurrentThread.ManagedThreadId +
                "</p>");

            context.Response.Write("<p>" + result + "</p>");
        }

        public override bool IsReusable
        {
            get
            {
                return false;
            }
        }
    }
}

上のコードの WebFormsApp.HelloWorldServiceReference はサービスプロキシの名前空間です。HelloWorldWcfServiceClient コンストラクタ、HelloWorldAsync メソッドの定義は自動生成される Reference.cs ファイルにあります。

結局は先の記事「非同期 HTTP ハンドラ」と同様なことを行っていて、先の記事のコードの BeginProcessRequest メソッドが await キーワードの前に、EndProcessRequest メソッドが await の後に実行されているようです。

BeginProcessRequest, EndProcessRequest メソッドをプログラマがコードを書いて実装しなくて済むよう、コンパイラが肩代わりしているという感じです。

Tags: , ,

ASP.NET

About this blog

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

Calendar

<<  2018年7月  >>
24252627282930
1234567
891011121314
15161718192021
22232425262728
2930311234

View posts in large calendar