追記: インポート版も作りましたの♪
DB のデータを CSV に出力してウェブブラウザからダウンロードしたいですの!今回、それを実現するコードを残しておきますわ♪
CSV エクスポートの対象となるシステム
エクスポートされる CSV の仕様
- ヘッダ行を出力する。
- 1 行に Parent とその Child を 2 人分まで出力する。
- セルは常にダブルクォーテーション「”」で囲む。
- セルの区切り文字はカンマ「,」とする。
- 行末に区切り文字は付けない。
「1 行に Parent とその Child を 2 人分まで出力」いたしますので、子供が 0 人の場合は子どもの部分が空白となりますし、1 人の場合は 1 人分のみ埋まります。Child が 3 人の場合は、2 人分のみ CSV に書き込まれ、3 人目は記録されない点に注意ですの!
手順 1. CSV エクスポート用のコントローラー、ビューを作成
1. CsvController を作成
Controllers フォルダで右クリック > 追加 > コントローラー > MVC5 コントローラー – 空 > 「CsvController」と名づけて追加
2. Export アクションを追加
Index アクションは Export アクションへとリダイレクトするようにする。そして、Export アクションを追加する。
– Export アクションは、初期表示時の GET の場合と、エクスポート時の POST の場合とで引数が同じ。
– これにより、同名同引数のメソッドができてしまうがこれは C# の仕様上、NG
– そこで POST 時のメソッド名を ExportDownloaded へと変更し、Export の URL でこのメソッドが実行されるように [HttpPost, ActionName(“Export”)] としている。
// GET: Csv public ActionResult Index() { return RedirectToAction("Export"); } // GET: Parents/Export public ActionResult Export() { return View(); } // POST: Parents/Export [HttpPost, ActionName("Export")] [ValidateAntiForgeryToken] public ActionResult ExportDownloaded() { return View(); }
3. Export ビューを追加
Export アクションの上にカーソルを移動し、右クリック > ビューを追加 > ビュー名を「Export」として追加
ここまでできたら、一旦 Export ビューファイル上で Ctrl + F5 を押して正常にウェブブラウザに表示されることを確認します。
それから、いよいよ核となる CSV エクスポート部分へと進みましょう♪
手順 2. CSV エクスポート処理のコードを書く
1. ビューにエクスポート用の POST ボタンを設置
@{ ViewBag.Title = "エクスポート"; } <h2>エクスポート</h2> @using (Html.BeginForm("Export", "Csv", FormMethod.Post)) { @Html.AntiForgeryToken() <input type="submit" value="エクスポート" class="btn btn-primary" /> }
2. CSV ヘッダ定義、ヘッダ行生成処理、1行を生成する処理を含むクラスを作成
- クラス設計について
- DB データを入力し、CSV 文字列を出力する処理等をクラスに切り出して、これをコントローラーから呼び出すようにした。
- アプリのクラス設計として、これがベストかどうかは不明。
- 「CSV セルの開始終了マーク」「CSV セル区切り文字」「改行文字」を定数として定義したが、コードを書いていて長くて面倒を感じた。定数にせずに直に書いてもよかったのかもしれない。
- StringBuilder.Append メソッドは、連続してつなげることが可能。そのため、1 行単位の塊や、1 セルの塊分は Append をつなげ、区切りのよい所でステートメント(コードの 1 行)を終了するようにした。
- StringBuilder.Append メソッドを使用したのは、C# のコーディング規則 (C# プログラミング ガイド) の「言語ガイドライン」「文字列型 (String)」 に従ったため。
- 「CSV セルの開始終了マーク」をつなげるために Append(ENCLOSE_CHARACTER) を書くのは長い。。。もっと短くできそう。
公開後の追記。次を修正しました。
- 【C#】StringBuilder で改行文字を入れたい場合は、AppendLine 関数を使う! | oki2a24
- 改行文字 を表す private const プロパティの NEWLINE_CHARACTER を、StringBuilder.AppendLine の使用に伴い削除した。
- CreateCsv メソッドの StringBuilder.Append を、StringBuilder.AppendLine を使用するように変更した。
using Sample1.Models; using System.Collections.Generic; using System.Text; namespace Sample2.Services { public class CsvService { /// <summary> /// CSV セルの開始終了マーク /// </summary> private const string ENCLOSE_CHARACTER = "\""; /// <summary> /// CSV セル区切り文字 /// </summary> private const string DELIMITER = ","; /// <summary> /// CSV ヘッダー要素の配列 /// </summary> private static readonly string[] HEADER_ARRAY = new string[] { "親_名前", "親_性別", "親_メールアドレス", "子1_名前", "子1_性別", "子1_生年月日", "子2_名前", "子2_性別", "子2_生年月日" }; /// <summary> /// CSV 1 行に付加する Child の数 /// </summary> private const int ADDED_CHILDREN_NUMBER = 3; /// <summary> /// CSV ファイルデータの文字列を生成して返却します。 /// </summary> /// <param name="parentList">Parent オブジェクトの List</param> /// <returns>CSV ファイルデータの文字列</returns> public static string CreateCsv(List<Parent> parentList) { var sb = new StringBuilder(); sb.AppendLine(GetCsvHeader()); parentList.ForEach(p => sb.AppendLine(CreateCsvLine(p))); return sb.ToString(); } /// <summary> /// CSV ヘッダ定義文字列を返却します。 /// 改行文字は含みません。 /// </summary> /// <returns>CSV ヘッダ定義文字列</returns> private static string GetCsvHeader() { var sb = new StringBuilder(); foreach (var v in HEADER_ARRAY) { sb.Append(ENCLOSE_CHARACTER).Append(v).Append(ENCLOSE_CHARACTER).Append(DELIMITER); } // 最後のデリミタを削除して返却 return sb.Remove(sb.Length -1, 1).ToString(); } /// <summary> /// CSV の 1 行の文字列を生成して返却します。 /// Child は 2 つまで含め、3 つ目以降は返却文字列に付加されません。 /// 改行文字は含みません。 /// </summary> /// <param name="parent">Parent オブジェクト</param> /// <returns></returns> private static string CreateCsvLine(Parent parent) { var sb = new StringBuilder(); sb.Append(ENCLOSE_CHARACTER).Append(parent.Name).Append(ENCLOSE_CHARACTER).Append(DELIMITER); sb.Append(ENCLOSE_CHARACTER).Append(parent.Sex.Name).Append(ENCLOSE_CHARACTER).Append(DELIMITER); sb.Append(ENCLOSE_CHARACTER).Append(parent.Email).Append(ENCLOSE_CHARACTER).Append(DELIMITER); // ヘッダ定義で子どもは 2 人までと決めたので、同じクラスで CSV 出力対象とする人数を制限する。 var i = 0; foreach (var child in parent.Children ) { i++; if (i == ADDED_CHILDREN_NUMBER) break; sb.Append(ENCLOSE_CHARACTER).Append(child.Name).Append(ENCLOSE_CHARACTER).Append(DELIMITER); sb.Append(ENCLOSE_CHARACTER).Append(child.Sex.Name).Append(ENCLOSE_CHARACTER).Append(DELIMITER); sb.Append(ENCLOSE_CHARACTER).Append(child.Birthday.ToShortDateString()).Append(ENCLOSE_CHARACTER).Append(DELIMITER); } // 最後のデリミタを削除して返却 return sb.Remove(sb.Length - 1, 1).ToString(); } } }
3. コントローラーに CSV エクスポート処理を書く。
- 応答ヘッダー Content-Disposition に attachment; filename=CSVファイル名.csv などと指定してヘッダー追加することで、ダウンロードファイル名を設定できる。
- Internet Explorer ではダウンロードファイル名の全角部分が UTF-8 でないと文字化けてしまうため、HttpUtility.UrlEncode(fileName, Encoding.UTF8) で UTF-8 に変換した。
- return Content で CSV の中身を設定する。コンテンツタイプは text/csv、CSV の中身の文字コードはエクセルで編集すること念頭に Shift_JIS とする。
修正箇所である ExportDownloaded アクションのみ記述
// POST: Parents/Export [HttpPost, ActionName("Export")] [ValidateAntiForgeryToken] public ActionResult ExportDownloaded() { // DB からデータ取得 var parentList = db.Parents.ToList(); // CSV 内容生成 var csvString = CsvService.CreateCsv(parentList); // クライアントにダウンロードさせる形で CSV 出力 var fileName = string.Format("マスタデータ_{0}.csv", DateTime.Now.ToString("yyyyMMddHHmmss")); // IE で全角が文字化けするため、ファイル名を UTF-8 でエンコーディング Response.AddHeader("Content-Disposition", string.Format("attachment; filename={0}", HttpUtility.UrlEncode(fileName, Encoding.UTF8))); return Content(csvString, "text/csv", Encoding.GetEncoding("Shift_JIS")); }
これで完成です♪
確認
http://localhost:49808/Csv/Export などにアクセスします。エクスポート画面が表示されました。
エクスポートボタンをクリックし、次のファイルがダウンロードされました。
マスタデータ_20151024110751.csv
"親_名前","親_性別","親_メールアドレス","子1_名前","子1_性別","子1_生年月日","子2_名前","子2_性別","子2_生年月日" "井上郁夫","男","inoue.ikuo@example.com","井上恵美子","女","2015/01/16" "宇佐美景子","女","usami.keiko@example.com","宇佐美涼介","男","2010/02/01","宇佐美信介","男","2013/11/08" "青木篤志","男","aoki.atsushi@example.com"
「宇佐美景子」には 3 人の子がデータベースに登録されておりますが、2 人に制限した部分もきちんと反映されております。
大丈夫ですね♪
おまけ。試行錯誤時のメモ。失敗したことなど。
- DB から LINQ で取得し、Select に CSV の1行用のモデルを指定 ← と思ったが、DB テーブルと対応するモデルをそのまま使えばよいのでは無いかと思った。
- モデルにプロパティをつなげて 1 行用のフォーマットされたテキストを返す関数を用意して呼び出し。
- 子要素の取得には、ElementAtOrDefault 関数を使う。← 無理。例外発生でエラーになる。
- CSV 用モデルのプロパティ型は DB の型に合わせる。CSV 出力するときに文字列に変換する。
おわりに
こちらの方法で、CSV 出力できること、そしてその概略として、ひとつなぎの文字列をつくればよいことやダウンロードファイル名の付け方を知ることができました。ありがとう存じます!
以上です。
「【ASP.NET MVC5】CSV エクスポートのサンプルプロジェクト作成チュートリアル」への2件の返信
[…] 【ASP.NET MVC5】CSV エクスポートのサンプルプロジェクト作成チュートリアル追記: インポート版も作りましたの♪ 【ASP.NET MVC5】今度は CSV インポートのサンプルプロジェ…oki2a24.com […]
[…] 【ASP.NET MVC5】CSV エクスポートのサンプルプロジェクト作成チュートリアル追記: インポート版も作りましたの♪ 【ASP.NET MVC5】今度は CSV インポートのサンプルプロジェ…oki2a24.com […]