WebSurfer's Home

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

DataPager のバグその2

by WebSurfer 2010年10月30日 14:34

先のポスト DataPager のバグその1 に続いて、再び DataPager のバグの話です。なお、このバグも .NET 4 では修正されており、.NET 3.5 のみの問題です。

ListView の中に配置した DataPager の設定にある条件が重なると Stack Overflow を起こします。条件というのは以下の 5 つです。

  1. ListView の LayoutTemplate の中に DataPager を配置
  2. ListView に EmptyDataTemplate を配置
  3. ListView にバインドされるデータの件数が 0
  4. DataPager.QueryStringField を設定(例: QueryStringField="pageNumber")
  5. そのページを、クエリ文字列 ?pageNumber=1 として要求

残念ながら原因は分かりません。(涙)

でも、回避策は簡単で、DataPager を ListView の外に出してしまえばいいのです。件数 0 の場合は ListView の外に DataPager を配置しても、それが表示されることはないです。

または、当然ながら、DataPager.QueryStringField の設定を止めても回避できます。DataPager.QueryStringField を使う理由が、GridView.PageIndex プロパティのようにコードでページを指定したいということであれば、別の手段があります。

その「別の手段」の具体的な方法は、Resetting the Page Index in a ListView が参考になると思います。

Tags:

Paging

DataPager のバグその1

by WebSurfer 2010年10月29日 23:18

.NET 3.5 で ListView コントロールと共に導入された DataPager コントロールには、QueryStringField プロパティ周りにバグがあります。

QueryStringField プロパティというのは、クエリ文字列を利用してページの移動先を指定し、ページの移動に HTTP GET コマンド(デフォルトでは POST)を使用できるようにするためのものです。

例えば、QueryStringField="pageNumber" と設定した場合、ページャーをクリックして指定した移動先のページ番号が 5 であったとすると、URL にクエリ文字列 ?pageNumber=5 を追加して HTTP GET コマンドでそのページを要求するようになります。

MSDN ライブラリ「DataPager.QueryStringField プロパティ」によると "検索エンジンですべてのデータ ページにインデックスが付けられるようにするには、このプロパティを設定すると便利です。" とのことです。

ところが、このページを要求する際に、クエリ文字列のキーに null が含まれる場合(例えば、?pageNumber=5& とすると、& の後に null のキーがあると判断されるようです)、NullReferenceException がスローされるという問題があります。

ページ内でページャーをクリックしているだけなら、自動的に ?pageNumber=x (x は要求するページ番号)というクエリ文字列が URL に追加されて要求がかかるので間違いなく動作しますが、別のページでクエリ文字列を組み立ててそのページを要求する場合、間違って ?pageNumber=x& などとすると例外がスローされます

この問題は、2008 年 7 月に Connect に報告されています。

この報告によると、受信したクエリ文字列のキーに null が含まれていると、ライブラリ内のメソッドの以下の部分で key(String オブジェクト)が null になって NullReferenceException がスローされるということだそうです。これはバグとして次期バージョンで修正されるという話になっていました。

foreach (string key in request.QueryString.AllKeys)
{
  if (... && !key.Equals(queryStringField, 
                StringComparison.OrdinalIgnoreCase))

今日、このページをのぞいてみたら、すでに修正済みというようなことが書いてあります。ということは、少なくとも .NET 4 では修正されているはずです。

ということで、早速試してみました。結果は:

  • .NET 3.5 SP1: ダメでした。NullReferenceException がスローされます。
  • .NET 4: 例外はスローされなかったので、バグは修正されているようです。

というわけで、ASP.NET 3.5 では回避策がまだ必要なようです。上に紹介した Connect のページに回避策も提案されていますので、そちらを参照してください。

Tags:

Paging

CSV パーサー

by WebSurfer 2010年10月28日 12:18

CSV ファイルをパースして DataTable を作るような場合、ファイルを一行ずつ読んで文字列を作り、String.Split メソッドでその文字列を区切るといった方法を考えると思います。

ところが、改行コードやデリミタ(コンマのような区切り文字)がフィールド値の中にあったり、改行コードが異なったり(例: Windows は CR + LF、Unix は LF)するのに対応する場合、上記ような単純な方法は使えません。

改行コードやデリミタがフィールド値の中にある場合、フィールド値をダブルクォート (") で囲むと言った約束を設けて対応しますが、そのような CSV ファイルの処置が可能なパーサーを自力で作るのは簡単ではありません。

という訳で、いつものように他力本願で(笑)、既存のパーサーを使うのがよさそうです。既存のパーサーにはいろいろなものがあります。詳しくは、CSV 形式のファイルを DataTable や配列等として取得する が参考になると思います。

なので、わざわざここに書く必要もないかもしれませんが、自分でも 2, 3 試してみましたので、使ってみた感想と、CSV ファイルを読んで DataTable を作るサンプルを書いておきます。

使ってみたのは Jet Provider, A Fast CSV Reader, TextFieldParser の 3 つです。感想は以下のとおりです。

  1. Jet Provider を使うのが最も簡単な方法です。ただし、文字コードが Shift_JIS 以外はダメです。UTF-8 なら日本語が文字化けする程度ですが、Unicode は全く処置できません。

    2014/5/26 追記:下にコメントいただいたとおり、schema.ini というファイルを作り、それに文字コードを指定して CSV ファイルと同じディレクトリに配置することにより UTF-8 他の文字コードに対応できます。

    また、JET プロバイダはデフォルトで最初の 8 行のデータをスキャンして各列のデータ型を推測しますが、その際、予期しない型に推測されてしまうことも schema.ini で型を指定することとにより防ぐことができます。
  2. A Fast CSV Reader は The Code Project のサイトで MIT License にて提供されているものです。解析のスピードの速さがウリのようです。使い方も簡単ですし、Shift_JIS, UTF-8, Unicode いずれも対応できるので、自分的にはこれが気に入っています。ただし、dll をダウンロードしてこなければならない点と、日本語の説明がない点が問題かも。
  3. TextFieldParser は Microsoft が提供している Visual Basic .NET 用のクラスライブラリです。これも使い方は簡単で、Shift_JIS, UTF-8, Unicode いずれも対応できます。C# でも Microsoft.VisualBasic.dll を参照に追加してやれば使えます。何といっても Microsoft のライブラリなので、これを使うのが一番無難そうな気がします。

CSV ファイルをパースして DataTable を作り、それを GridView にバインドして表示するサンプルをアップしておきます。検証用なので、ユーザーインターフェイスはかなり省略して書いてます。すみません。(汗)

A Fast CSV Reader を使用するには LumenWorks.Framework.IO.dll を The Code Project のサイト A Fast CSV Reader からダウンロードしてきて、それを Bin フォルダに置く必要があります。test.csv は検証用のテキストファイルです。

<%@ Page Language="C#" %>
<%@ Import Namespace="LumenWorks.Framework.IO.Csv" %>
<%@ Import Namespace="System.IO" %>
<%@ 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">
  // CSV パーサーの選択
  int select = 0;
    
  // CSVファイルのあるフォルダ、ファイル名
  string csvDir = @"C:\WebSites\MsdnTestNew\App_Data\";
  string csvFileName = "test.csv";

  DataTable dt;
    
  protected void Page_Load(object sender, EventArgs e)
  {
    if (!Page.IsPostBack)
    {
      if (select == 0)
      {
        // A Fast CSV Reader を使う場合

        // ストリームの最初の 3 バイトを参照して、エン
        // コーディングの検出を試みます。
        // ファイルが該当する BOM で開始される場合は、UTF-8、
        // リトルエンディアン Unicode、ビッグエンディアン
        // Unicode の各テキストが自動的に認識されます。
        // それ以外の場合は、ユーザー指定のエンコーディング
        // (以下の例では Shift_JIS)が使用されます。
        using (CsvReader csv = 
          new CsvReader(
            new StreamReader(
              csvDir + csvFileName, 
              Encoding.GetEncoding("Shift_JIS")
            ), 
            true
          )
        )
        {
          int fieldCount = csv.FieldCount;
          string[] headers = csv.GetFieldHeaders();

          dt = new DataTable();
          DataRow dr;
          DataColumn dc;

          for (int i = 0; i < fieldCount; i++)
          {
            dc = new DataColumn(headers[i], typeof(String));
            dt.Columns.Add(dc);
          }

          while (csv.ReadNextRecord())
          {
            dr = dt.NewRow();
            for (int i = 0; i < fieldCount; i++)
            {
              dr[headers[i]] = csv[i];
            }
            dt.Rows.Add(dr);
          }
        }
      }
      else if (select == 1)
      {
        // Jet Provider を使う場合

        // 接続文字列。HDR=Yes で一行目をヘッダーとして扱う
        string conString = 
          "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" +
          csvDir + 
          ";Extended Properties=\"text;HDR=Yes;FMT=Delimited\"";

        System.Data.OleDb.OleDbConnection con = 
          new System.Data.OleDb.OleDbConnection(conString);
        string commText = "SELECT * FROM [" + csvFileName + "]";
        System.Data.OleDb.OleDbDataAdapter da = 
          new System.Data.OleDb.OleDbDataAdapter(commText, con);

        dt = new DataTable();
        da.Fill(dt);
      }
      else
      {
        // TextFieldParser を使う場合
        using (Microsoft.VisualBasic.FileIO.TextFieldParser tfp =
          new Microsoft.VisualBasic.FileIO.TextFieldParser(
            csvDir + csvFileName,
            Encoding.GetEncoding("Shift_JIS")
          )
        )
        {
          //フィールドがデリミタで区切られている
          tfp.TextFieldType = 
            Microsoft.VisualBasic.FileIO.FieldType.Delimited;
          // デリミタを , とする
          tfp.Delimiters = new string[] { "," };
          // フィールドを " で囲み、改行文字、デリミタを
          // 含めることができるか
          tfp.HasFieldsEnclosedInQuotes = true;
          // フィールドの前後からスペースを削除
          tfp.TrimWhiteSpace = true;

          string[] headers = tfp.ReadFields();
          int fieldCount = headers.Length;
                    
          dt = new DataTable();
          DataRow dr;
          DataColumn dc;

          for (int i = 0; i < fieldCount; i++)
          {
            dc = new DataColumn(headers[i], typeof(String));
            dt.Columns.Add(dc);
          }

          while (!tfp.EndOfData)
          {
            string[] fields = tfp.ReadFields();
                        
            dr = dt.NewRow();
            for (int i = 0; i < fieldCount; i++)
            {
              dr[headers[i]] = fields[i];
            }
            dt.Rows.Add(dr);                        
          }
        }
      }
      GridView1.DataSource = dt;
      GridView1.DataBind();
    }
  }
</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
  <form id="form1" runat="server">
  <div>
    <asp:GridView ID="GridView1" runat="server">
    </asp:GridView>
  </div>
  </form>
</body>
</html>

Tags:

その他

About this blog

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

Calendar

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

View posts in large calendar