ASP.NET MVCはじめました~ajaxメソッドでローディング画像を表示して検索結果を取得しExcelでダウンロードする
概要
Excelダウンロード処理を書いてみましたが、データの取得に時間がかかるとブラウザが固まってしまいます。なので今回は処理中にローディング画像をぐるぐる表示するようにしてみます。
処理の流れも変わりまして、jQueryのajaxメソッドで一旦サーバー上にExcelファイルを作成して、コールバックでファイルをダウンロードします。
環境
Visual Studio 2010
ASP.NET MVC2
SQLServer2008 R2
EPPlus 3.1:EPPlus-Create advanced Excel spreadsheets on the server - Home
今回の要件は
- 処理中にローディング画像を表示する
- 検索結果がExcelでダウンロードできる
として、サンプルコードを書いてみました。
実装
Ajax.BeginFormの記事で書いたコードに追記する感じでダウンロード機能を実装しました。
JavaScriptファイルの用意
最初はajaxメソッドでダウンロード用のアクションを呼ぶだけでいいかと思っていたのですが、ダウンロードダイアログが表示されず処理が終了してしまいました。調べたところ、ajaxメソッド的にExcelファイルのダウンロードはレスポンスでダメというのを見つけて、じゃあどうするかってことで、また調べてたらstackoverflowに丁度いい感じの質問と回答があったので参考にさせてもらいました(まるっと)。
■Mvc2App/Scripts/Libs/PeopleAjaxSearch.js
$(function () { $('#downloading').hide(); //ダウンロードボタン押下時イベント var req; $('#btnDownload').click(function () { $('#btnDownload').attr("disabled", "disabled"); $('#downloading').show(); var dataLength = $("#gridData tbody").children().length; if (dataLength <= 1) { $('#downloading').hide(); alert('There is no data.') $('#btnDownload').removeAttr("disabled"); return; } req = $.ajax({ url: getPath("People/CreateReport"), type: 'POST', cache: false, success: function (returnValue) { var rep = getPath("People/DownloadFile"); window.location = rep + '?file=' + returnValue; }, error: function () { alert('NG...'); }, complete: function () { $('#downloading').hide(); $('#btnDownload').removeAttr("disabled"); } }); }); $('#downloading').click(function () { req.abort(); alert('ダウンロードを中止しました。'); }); });
コントローラーの用意
アクションメソッドはExcelファイルを作成するものと、作成したファイルをダウンロードする2段階です。
■Mvc2App/Controllers/PeopleController.cs
private static readonly string eOutputDir = "~/Report"; [HttpPost] public string CreateReport() { var condition = Session["Conditions"] as PeopleSearchConditionModel; if (condition == null) { return "ERROR"; } System.Threading.Thread.Sleep(5000); var file = _service.CreateReport(condition, Server.MapPath(eOutputDir)); return file; } [HttpGet] public ActionResult DownloadFile(string file) { if (file == "ERROR") { return RedirectToAction("Show", "Error", new { code = "NotFound" }); } var contentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; var fullPath = Path.Combine(Server.MapPath(eOutputDir), file); if (System.IO.File.Exists(fullPath)) { return File(fullPath, contentType, file); } return RedirectToAction("Show", "Error", new { code = "NotFound" }); }
サービスインタフェースとサービスクラスの用意
■Mvc2App/Services/PeopleService.cs
string CreateReport(PeopleSearchConditionModel condition, string root);
Excelファイルを作成します。ファイル名がかぶらないようにファイル名に日時を付与していますが、サーバーにファイルがたまり続けてしまうので、前日以前分は削除するようにしました。
■Mvc2App/Services/PeopleService.cs
public string CreateReport(PeopleSearchConditionModel condition, string root) { FileClearner(root); var file = CreateFileName(); try { var data = _repository.Search(condition); var fmt = Mapping<Person>.ToArray<PeopleExcelFormat>(data); MakeFile(file, root, fmt); return file; } catch (Exception e) { Logger.Error(e.Message); Logger.Debug(e.StackTrace); if (e.InnerException != null) { Logger.Error(e.InnerException.Message); Logger.Debug(e.InnerException.StackTrace); } throw; } } private static readonly string prefixFile = "People_"; private void FileClearner(string root) { var today = DateTime.Parse(DateTime.Now.ToString("yyyy/MM/dd")); var pattern = prefixFile + "*"; string[] files = Directory.GetFiles(root, pattern); foreach (var item in files) { var lastWriteTime = File.GetLastWriteTime(item); if (lastWriteTime < today) { File.Delete(item); } } } private static void MakeFile(string file, string root, PeopleExcelFormat[] fmt) { var fullPath = Path.Combine(root, file); var newFile = new FileInfo(fullPath); using (var excelFile = new ExcelPackage(newFile)) { var worksheet = excelFile.Workbook.Worksheets.Add("Sample"); var range = worksheet.Cells[1, 1]; //開始位置 var length = fmt.Length; for (int i = 0; i < length; i++) { var updateDateRow = i + 2; var updateDateCol = 6; worksheet.Cells[updateDateRow, updateDateCol].Style.Numberformat.Format = "yyyy/mm/dd"; } range.LoadFromCollection(Collection: fmt, PrintHeaders: true); excelFile.Save(); } } private static string CreateFileName() { var format = "yyyyMMddHHmmssfff"; var currentDate = DateTime.Now.ToString(format); var file = prefixFile + currentDate + ".xlsx"; return file; }
ビューの用意
ダウンロード時のローディング画像だけ追加しました。
■Mvc2App/Views/People/SearchByAjax.aspx
<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<Mvc2App.ViewModels.People.PeopleSearchViewModel>" %> <asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server"> AjaxSearch </asp:Content> <asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server"> <h2>AjaxSearch</h2> <% using (Ajax.BeginForm("AjaxSearch", new AjaxOptions { UpdateTargetId = "ajaxGrid", LoadingElementId = "loading", OnSuccess = "ajaxSearchOnSuccess", OnFailure = "ajaxSearchOnFailure" } )) { %> <fieldset> <legend>Search Conditions</legend> <div class="field-box"> <div class="field"> <span class="field-label">Name</span> <%: Html.TextBoxFor(model => model.condition.Name)%> </div> <div class="field"> <span class="field-label">Address</span> <%: Html.TextBoxFor(model => model.condition.Address)%> </div> <div class="field"> <span class="field-label">PhoneNumber</span> <%: Html.TextBoxFor(model => model.condition.PhoneNumber)%> </div> <div class="field"> <span class="field-label">UpdatedBy</span> <%: Html.TextBoxFor(model => model.condition.UpdatedBy)%> </div> <div class="field"> <span class="field-label">UpdateDate</span> <%: Html.TextBoxFor(model => model.condition.UpdateDate)%> </div> </div> <div id="btnBox1"> <div id="btnBox2"> <input id="btnSearch" type="submit" value="Search" /> <input id="btnDownload" type="button" value="Download" /> <img id="loading" src="<%: Url.Content("~/Content/images/loading.gif") %>" alt="" class="loader" /> <img id="downloading" src="<%: Url.Content("~/Content/images/downloading.gif") %>" alt="" /> </div> </div> </fieldset> <%} %> <div id="ajaxGrid"> </div> </asp:Content> <asp:Content ID="ScriptContent" ContentPlaceHolderID="ScriptContent" runat="server"> <script type="text/javascript" src="<%: Url.Content("~/Scripts/Libs/PeopleAjaxSearch.js") %>"></script> </asp:Content>