.NET TIPS

[ASP.NET]動的に圧縮ファイルを生成するには?

山田 祥寛
2005/10/07

 サーバから複数のファイル、しかも大容量のデータをクライアントにダウンロードさせる場合、個々のファイルを個別にダウンロードさせるよりも、必要なデータを(ZIP形式などで)圧縮したうえで1ファイルにまとめた方が通信時間を短縮することができる。

 もちろんあらかじめ必要なデータが分かっている場合には、静的に圧縮ファイルを用意しておいた方がよいだろう。しかし取得するファイルを動的に選択させたい、あるいはデータベースから取得したデータに基づいて、ファイルそのものを動的に生成したいという場合には、アプリケーションで動的に圧縮ファイルを生成する必要がある。

 本稿では、(.NET Framework 1.1で新たに.NET言語として加わった)「J#」が提供するZipOutputStreamクラス(java.util.zip名前空間)を利用して、データベースから取得したテーブルの内容(タブ区切りテキスト)を動的に圧縮し、ダウンロード提供する方法について紹介する。

 なお本稿のサンプルを実行するに当たっては、あらかじめbooks、schedule、sitemapという名前でテーブルを作成し、中に適当なデータをセットしておくこと。フィールド・レイアウトは特に問わない。

 それではさっそく、具体的な手順を見てみることにしよう。

1. アセンブリ“vjslib.dll”への参照を追加する

 本稿のサンプルでは、J#が提供するjava.io、java.util.zipのような名前空間のクラスを使用している。そのため、サンプルを動作させるには、NET Framework 1.1のほかに、「Visual J# .NET Version 1.1再頒布可能パッケージ」をインストールしておく必要がある。

 また、J#のアセンブリである“vjslib.dll”への参照をweb.configに追加しておくこと(アセンブリの追加に関する詳細については、「TIPS:[ASP.NET]ASP.NETでVB.NET固有の関数をC#から利用するには?」を参照されたい)。

 “vjslib.dll”への参照を追加するための具体的なweb.configの記述は以下のとおりだ。

<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
  <system.web>
    <compilation debug="true">
      <assemblies>
        <add assembly="vjslib, Version=1.0.5000.0, Culture=Neutral, PublicKeyToken=b03f5f7f11d50a3a" />
      </assemblies>
    </compilation>
  </system.web>
</configuration>
“vjslib.dll”への参照を追加する設定(web.config)

2. Webフォームから圧縮ファイル(ZIPファイル)を生成する

 以上の設定ができたら、データベース内のテーブルから複数のタブ区切りテキスト・ファイルを生成し、これを圧縮ファイル化してみよう。以下が具体的なコードだ。

<%@ Page ContentType="text/html" Language="C#" %>
<%@ Import Namespace="java.io" %>
<%@ Import Namespace="java.util.zip" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<%@ Import Namespace="System.IO" %>
<script runat="Server">

void Page_Load(Object sender, EventArgs e) {
  // dataフォルダは圧縮ファイルを一時的に保存するフォルダ。
  // 生成して5分以上経過しているファイルはゴミと見なし、削除
  DirectoryInfo dinf = new DirectoryInfo(Server.MapPath("data"));
  foreach (FileInfo file in dinf.GetFiles()) {
    if (file.CreationTime.AddMinutes(5) < DateTime.Now) {
      file.Delete();
    }
  }
  // セッションIDから圧縮ファイル名を生成
  String fileName = "data/" + FormsAuthentication.HashPasswordForStoringInConfigFile(Session.SessionID, "MD5") + ".zip";

  // データを取得するテーブル名を配列として定義
  String[] tbls = { "books", "schedule", "sitemap" };

  // 指定されたファイルへの圧縮出力ストリームを生成
  ZipOutputStream zipStream = new ZipOutputStream(
    new FileOutputStream(Server.MapPath(fileName)));

  SqlConnection db = new SqlConnection("Data Source=(local);User ID=sa;Password=sa;Persist Security Info=True;Initial Catalog=dotnet");
  db.Open();

  // books、schedule、sitemapを順に処理し、タブ区切りテキストを生成
  for (int i = 0; i <= tbls.GetUpperBound(0); i++)
    // 取得したデータをバイト配列に書き込むための
    // OutputStreamWriterを用意
    ByteArrayOutputStream bArray = new ByteArrayOutputStream();
    OutputStreamWriter oStream =
      new OutputStreamWriter(bArray, "SJIS");
    SqlCommand comm = new SqlCommand("SELECT * FROM " + tbls[i],db);
    SqlDataReader reader = comm.ExecuteReader();

    // テーブルから取得したDataReaderに基づき、
    //タイトル行をタブ区切りで出力
    for (int j = 0; j < reader.FieldCount; j++) {
      oStream.write(reader.GetName(j));
      if (j < reader.FieldCount - 1) { oStream.write("\t"); }
    }
    oStream.write("\r\n");
    // DataReaderに含まれるデータをタブ区切りテキストとして出力
    while (reader.Read()) {
      for (int j = 0; j < reader.FieldCount; j++) {
        oStream.write(reader.GetValue(j).ToString());
        if (j < reader.FieldCount - 1) { oStream.write("\t"); }
      }
      oStream.write("\r\n");
    }
    oStream.close();

    // Zipファイル内のファイル・エントリを生成
    //(ファイル名は「<テーブル名>.txt」)
    ZipEntry zEntry = new ZipEntry(tbls[i] + ".txt");
    zEntry.setMethod(ZipOutputStream.DEFLATED);

    // 圧縮出力ストリームにファイル・エントリを追加し、
    // 上で作成したバイト配列を書き込む
    zipStream.putNextEntry(zEntry);
    zipStream.write(bArray.toByteArray(),
      0, bArray.toByteArray().Length);

    zipStream.closeEntry();
    reader.Close();
  }
  zipStream.flush();
  zipStream.close();

  // HyperLinkコントロールに生成された圧縮ファイルへのパスをセット
  lnk.NavigateUrl = fileName;
}
</script>
<html>
<head>
<title>圧縮ファイルの作成</title>
</head>
<body>
<asp:HyperLink id="lnk" runat="Server" Text="ファイル・ダウンロード" />
</body>
</html>
データベースから取得したデータから圧縮ファイルを生成するWebフォーム(C#版:zip_cs.aspx)
 
<%@ Page ContentType="text/html" Language="VB" %>
<%@ Import Namespace="java.io" %>
<%@ Import Namespace="java.util.zip" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<%@ Import Namespace="System.IO" %>
<script runat="Server">

Sub Page_Load(sender As Object, e As EventArgs)
  ' dataフォルダは圧縮ファイルを一時的に保存するフォルダ。
  ' 生成して5分以上経過しているファイルはゴミと見なし、削除
  Dim dinf As New DirectoryInfo(Server.MapPath("data"))
  For Each file As FileInfo In dinf.GetFiles()
    If file.CreationTime.AddMinutes(5) < DateTime.Now Then file.Delete()
  Next
  ' セッションIDから圧縮ファイル名を生成
  Dim fileName As String = "data/" & Session.SessionID & ".zip"

  ' データを取得するテーブル名を配列として定義
  Dim tbls() As String = {"books", "schedule", "sitemap"}

  ' 指定されたファイルへの圧縮出力ストリームを生成
  Dim zipStream As New ZipOutputStream( _
    New FileOutputStream(Server.MapPath(fileName)))

  Dim db As New SqlConnection("Data Source=(local);User ID=sa;Password=sa;Persist Security Info = True;Initial Catalog=dotnet")
  db.Open()

  ' books、schedule、sitemapを順に処理し、タブ区切りテキストを生成
  For i As Integer = 0 To tbls.GetUpperBound(0)
    ' 取得したデータをバイト配列に書き込むための
    ' OutputStreamWriterを用意
    Dim bArray As New ByteArrayOutputStream()
    Dim oStream As New OutputStreamWriter(bArray, "SJIS")
    Dim comm As New SqlCommand("SELECT * FROM " & tbls(i), db)
    Dim reader As SqlDataReader = comm.ExecuteReader()

    ' テーブルから取得したDataReaderに基づき、
    'タイトル行をタブ区切りで出力
    For j As Integer = 0 To reader.FieldCount-1
      oStream.write(reader.GetName(j))
      If j< reader.FieldCount - 1 Then oStream.write(Chr(9))
    Next
    oStream.write(Chr(13) & Chr(10))
    ' DataReaderに含まれるデータをタブ区切りテキストとして出力
    Do While reader.Read()
      For j As Integer = 0 To reader.FieldCount - 1
        oStream.write(reader.GetValue(j).ToString())
        If j < reader.FieldCount - 1 Then oStream.write(Chr(9))
      Next
      oStream.write(Chr(13) & Chr(10))
    Loop
    oStream.close()

    ' Zipファイル内のファイル・エントリを生成

    '(ファイル名は「<テーブル名>.txt」)
    Dim zEntry As New ZipEntry(tbls(i) & ".txt")
    zEntry.setMethod(ZipOutputStream.DEFLATED)

    ' 圧縮出力ストリームにファイル・エントリを追加し、
    ' 上で作成したバイト配列を書き込む
    zipStream.putNextEntry(zEntry)
    zipStream.write(bArray.toByteArray(), _
      0, bArray.toByteArray().length)

    zipStream.closeEntry()
    reader.Close()
  Next
  zipStream.flush()
  zipStream.close()

  ' HyperLinkコントロールに生成された圧縮ファイルへのパスをセット
  lnk.NavigateUrl = fileName
End Sub
</script>
<html>
<head>
<title>圧縮ファイルの作成</title>
</head>
<body>
<asp:HyperLink id="lnk" runat="Server" Text="ファイル・ダウンロード" />
</body>
</html>
データベースから取得したデータから圧縮ファイルを生成するWebフォーム(VB.NET版:zip_vb.aspx)

 複数の出力ストリームを介しているため、やや複雑に見えるかもしれないが、順を追って全体の流れを見ていくことにしよう。なおタブ区切りテキスト生成の手順は、本稿の主題ではないので、ここでは割愛する。詳細はコード内のコメントを参照していただきたい(なお本稿の例では、「books.txt」「schedule.txt」「sitemap.txt」という3つのテキスト・ファイルを生成し、1つのZIPファイルに圧縮している)。

 まずZIP形式の圧縮データを出力するのは、ZipOutputStreamクラス(java.util.zip名前空間)の役割だ。このクラスは、出力ストリームに対してZIP圧縮フィルタを提供する。ここでは、出力ストリームとして、FileOutputStreamクラス(java.io名前空間)を介しているので、ファイル・ストリームへの出力時に圧縮処理が実行されることになる。

 ZipOutputStreamオブジェクトへの書き込みは、以下の手順で行うことができる。

 (1)putNextEntryメソッドによるファイル・エントリの生成
 (2)writeメソッドによる実データの書き込み
 (3)closeEntryメソッドによるファイル・エントリのクローズ

 個々のファイル・エントリ(本稿の例では、「books.txt」「schedule.txt」「sitemap.txt」のそれぞれのファイル)を表すのは、ZipEntryオブジェクト(java.util.zip名前空間)の役割だ。圧縮処理を行う場合には、圧縮メソッドとしてsetMethodメソッドにZipOutputStream.DEFLATED(圧縮処理)をセットすること。

 実データ(本稿では、タブ区切りテキスト)を書き込むwriteメソッドの構文は、以下のとおり。

public void write(sbyte[] b, int off, int len)
Public Sub write(ByVal b() As SByte, ByVal off As Integer, ByVal len As Integer)
ZipOutputStream.writeメソッドの構文(上:C#、下:VB.NET)
第1パラメータは書き込むデータ(バイト配列)を、第2パラメータはデータの開始オフセットを、第3パラメータは書き込むデータのバイト数を指定する。

 バイト配列への書き込みは、ByteArrayOutputStreamオブジェクト(java.io名前空間)によって行う。ByteArrayOutputStreamオブジェクトをラップしているOutputStreamWriterオブジェクト(java.io名前空間)は、文字ストリームからバイトストリームへの橋渡し的な役割を果たすものだと思っていただければよいだろう。

 OutputStreamWriterオブジェクトを介することで、渡された文字列は指定された文字コードを使用してバイト・コードに符号化されるというわけだ。ByteArrayOutputStreamオブジェクトに蓄積されたバイト・データは、toByteArrayメソッドでバイト配列として取得することができる。

 これら(1)〜(3)の手順を繰り返すことで、ZIP圧縮データに複数のファイル・エントリを追加することができる。すべてのエントリを追加したら、closeメソッドでZipOutputStreamオブジェクトをクローズして完了だ。

 以上が理解できたら、さっそくサンプル・プログラムを実行してみよう。

サンプル・プログラムの実行結果
ダウンロードした圧縮ファイルを解凍ソフトで閲覧したところ(上画面)。また、個々のファイル・エントリ(タブ区切りテキスト)はMicrosoft Excelなどの表計算アプリケーションから開くことができる(下画面)。

 ダウンロードした圧縮ファイルに3つのファイル(「books.txt」「schedule.txt」「sitemap.txt」)が含まれており、各ファイルがMicrosoft Excelなどの表計算アプリケーションから正しく表示できれば成功だ。

 本稿のサンプル・プログラムでは、取得するテーブルをハード・コーディングしているが、もちろんフォーム上からエンド・ユーザーが指定し、動的に変更することも可能だ。余力のある方は改良してより良いツールを作成していただきたい。End of Article

カテゴリ:Webフォーム 処理対象:ファイル・ダウンロード
使用ライブラリ:ZipOutputStreamクラス(java.util.zip名前空間)
使用ライブラリ:FileOutputStreamクラス(java.io名前空間)
使用ライブラリ:ZipEntryクラス(java.util.zip名前空間)
使用ライブラリ:ByteArrayOutputStreamクラス(java.io名前空間)
使用ライブラリ:OutputStreamWriterクラス(java.io名前空間)
関連TIPS:[ASP.NET]ASP.NETでVB.NET固有の関数をC#から利用するには?
 
この記事と関連性の高い別の.NET TIPS
GZIP形式でファイルを圧縮/解凍するには?
HTTP圧縮を使用してWebページを取得するには?
[ASP.NET]データベースの内容をクライアントにダウンロード提供するには?
[ASP.NET]動的にJPEG画像を作成するには?
ZIPファイルを解凍するには?(ZipArchive編)[C#、VB]
このリストは、(株)デジタルアドバンテージが開発した
自動関連記事探索システム Jigsaw(ジグソー) により自動抽出したものです。
generated by

「.NET TIPS」


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Insider.NET 記事ランキング

本日 月間