コンソールアプリケーションからWebサービスを叩いて取得したZIPファイルをローカルファイルとして保存してみた

はじめに
-----------------------------------------------------
良いタイトルが思いつかなかった。。。
PDFファイルとかを一纏めにしたZIPファイルのバイナリデータをWebサーバから取得して
クライアント端末に保存したい。
ということで、C#(.Net FW 4.5.2)で作成してみます。


システム構成図
-----------------------------------------------------
ざっくりとこういうイメージで作ります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/146632/5d851118-b2e5-09ac-3e94-ada67c6f97d5.png)

1. ConsoleAppからWeb I/Fを叩く
2. Web I/FがAPIをコールしてZIPのバイナリデータを作成する
3. Web I/FがAPIで作成したバイナリデータをConsoleAppに返却する
4. ConsoleAppがバイナリデータをクライアント端末に保存する

という処理の流れになります。

コンソールアプリケーション
-----------------------------------------------------
まずはクライアント端末に配置するコンソールアプリケーションから。

```c#:DownloadApp.cs
using System;
using System.Xml;
using System.Web;
using System.Net.Http;
using System.IO;
using System.Configuration;
using System.Text;
using System.Threading.Tasks;

namespace DownloadApp
{
    class DownloadApp
    {
        /// <summary>
        /// メイン処理
        /// </summary>
        /// <param name="args"></param>
        static void Main(string[] args)
        {
            try
            {
                DownloadZipAsync().Wait();
            }
            catch (Exception ex)
            {
                // エラー処理
            }
        }

        static async Task DownloadZipAsync()
        {
            using (var client = new HttpClient())
            {
                // 1. GETでデータ取得
                string uri = "http://localhost:50984/WebService/WebService.asmx/GetBinaryOfZip?userId=1";
                HttpResponseMessage res = await client.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead);

                // 2. 保存するzipファイルのストリームをオープン
                string outputFilePath = "C:\temp\test.zip"
                using (var fs = new FileStream(@outputFilePath, FileMode.Create, FileAccess.Write))
                {
                    // 3. 受信データの中身を読み込む
                    var resXml = await res.Content.ReadAsStringAsync();

                    var xmlDoc = new XmlDocument();
                    xmlDoc.LoadXml(resXml);

                    if (xmlDoc.GetElementsByTagName("base64Binary").Count == 0)
                    {
                        throw new ApplicationException("base64Binaryタグが存在しません。");
                    }

                    // 4. Webサービスから取得したBase64文字列をバイト型配列に変換
                    byte[] zipBinary =
                        System.Convert.FromBase64String(xmlDoc.GetElementsByTagName("base64Binary")[0].InnerText);

                    // 5. ファイルに書き込む
                    await fs.WriteAsync(zipBinary, 0, zipBinary.Length);
                }
            }
        }
    }
}
```
やっていることはシンプルです。
コード上の番号付きコメントを見れば大方わかると思います。
GETのURLの中身はテキトーです。環境に合わせて変更が必要です。

いくつかポイントを解説します。

#####・DownloadZipAsync().Wait()について
なぜ**Wait**しているのか？
と思われた方がいらっしゃると思いますが、
コンソールアプリケーションで非同期メソッドを使用する場合、
**Wait**で処理の終了を待ってあげないと一瞬でMainの処理が終了してしまい、期待通りの動作になりません。
※今回はGETでWeb I/Fを呼び出す方法として「HttpClient」を使用しているため、DownloadZipAsyncメソッドは非同期メソッドとなっています。

#####・System.Convert.FromBase64Stringについて
今回のサンプルで呼び出すWeb I/Fは「asmx.cs」に定義されているメソッドになります。
ここからバイナリデータを返却するとbase64でエンコードされるので、
クライアントで受け取った際にbase64でデコードしてやる必要があります。
※特に意識せずとも自動的にbase64でエンコードされて返却されるようなのですが、こういう仕様なのでしょうか？わかる方いらっしゃいましたらご教授いただけると幸いです。

Web I/F
-----------------------------------------------------
次にコンソールアプリケーションからコールされるWeb I/F。

```c#:WebService.asmx.cs
[WebMethod]
public byte[] GetBinaryOfZip(string userId)
{
    return GetBinary(userId);    
}

private byte[] GetBinary(string userId)
{
    using (var ms = new System.IO.MemoryStream())
    {
        // メモリストリーム上にZipArchiveを作成する
        using (var zipArchive = new System.IO.Compression.ZipArchive(ms, ZipArchiveMode.Create, true))
        {
			string entryName = userId + "_test.jpg";
			byte[] buffer = [jpgのバイナリデータ];
            // ファイル名を指定してエントリを作成
            System.IO.Compression.ZipArchiveEntry entry = zipArchive.CreateEntry(entryName);
            using (Stream es = entry.Open())
            {
                // エントリにバイナリを書き込む
                es.Write(buffer, 0, buffer.Length);
            }
        }
        return ms.ToArray();
    }
}
```

すみません、説明簡略化のためにAPIの処理もここに含めてしまっています。
本来privateのGetBinaryメソッドはここにいるべきではありません！
ビジネスロジックをWebの顔に書くのはやめましょう。

では、こちらもポイントを解説します。

#####・ZipArchiveについて
これ非常に便利です。
ZipArchiveとZipArchiveEntryを絵で説明するとこんな感じです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/146632/0f1ccf54-4030-5d69-f98f-8626ec026899.png)
ZipArchiveという箱(要するにzipファイル)の中に
ZipArchiveEntryという物(要するに画像やPDFのファイル)を
入れ込んでいるといったイメージです。

このコード例ではエントリを１つしか書き込んでいませんが、
複数書き込むことで複数のファイルをzipファイルの中に入れることができます。

おわりに
-----------------------------------------------------
備忘録代わりにと思ってだいぶ駆け足で書いてしまいました。
それでも誰かの参考になれば幸いです。