読者です 読者をやめる 読者になる 読者になる

1.21 jigowatts

Great Scott!

ASP.NET MVCはじめました~検索結果をExcelでダウンロードする

概要

前回は検索画面を作成してみましたので、検索結果をExcelでダウンロードできるようにしてみます。
Excelを扱う部分はEPPlusというライブラリを使用します。こちらこちらでも少し使っています。

環境

Visual Studio 2010
ASP.NET MVC2
SQLServer2008 R2
EPPlus 3.1:EPPlus-Create advanced Excel spreadsheets on the server - Home

今回の要件は

  1. 検索結果が0件の場合はデータがないよとメッセージを表示する
  2. 検索結果がExcelでダウンロードできる

として、サンプルコードを書いてみました。

実装

コントローラーの用意

ダウンロードボタンがクリックされたときのアクションメソッドを作成します。
Excelデータはbyte配列にし、Fileメソッドを使ってFileContentResultオブジェクトを返します。

namespace Mvc2App.Controllers
{
    [Authorize]
    public class PepleController : Controller
    {

        ...

        public ActionResult Download()
        {
            var condition = Session["condition"] as PepleSearchConditionModel;
            if (condition == null)
            {
                return View();
            }

            var data = _service.Export(condition);
            var fileName = "Peple.xlsx";

            var contentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";

            return File(data, contentType, fileName);
        }
    }
}
サービスインタフェースとサービスクラスの用意
namespace Mvc2App.Services.Abstractions
{
    public interface IPepleService
    {
        ...
        byte[] Export(PepleSearchConditionModel condition);
    }
}

検索結果をEPPlusを使ってExcelデータにし、byte配列として返します。
日付はyyyy/mm/dd形式にフォーマット指定しました。

namespace Mvc2App.Services
{  
    public class PepleService : IPepleService
    {

        ...

        public byte[] Export(PepleSearchConditionModel condition) 
        {
            var searcher = new PepleSearcher();
            var data = searcher.GetAllData(condition);


            //Excel出力フォーマット用クラスにマッピング
            var fmt = Mapping<Person>.ToArray<PepleExcelFormat>(data);

            byte[] excelData;
            using (var excelFile = new ExcelPackage())
            {
                var worksheet = excelFile.Workbook.Worksheets.Add("Peple");
                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);
                excelData = excelFile.GetAsByteArray();
            }

            return excelData;        
        }
    }
}
リフレクションによるマッピングの仕組みを書いてみたり

オブジェクトの詰め替えが結構発生するんですよね。AutoMapperを使ってあげるといいんでしょうけど、勉強兼ねて自作です(`ω´)

namespace Mvc2App.Framework
{
    public class Mapping<T>
    {
        public Mapping() 
        {
        }
        public static Tres Execute<Tres>(T item) 
        {
            return ReflectionMapping<Tres>(item);
        }

        public static Tres[] ToArray<Tres>(T[] target)
        {
            IList<Tres> list = new List<Tres>();
            ReflectionMappings<Tres>(target, list);
            return list.ToArray();
        }

        public static IList<Tres> ToList<Tres>(IList<T> target)
        {
            IList<Tres> results = new List<Tres>();
            ReflectionMappings<Tres>(target, results);
            return results;
        }

        private static void ReflectionMappings<Tres>(IEnumerable<T> target, IList<Tres> results)
        {            
            foreach (var item in target)
            {
                Tres result = ReflectionMapping<Tres>(item);

                results.Add(result);
            }
        }

        private static Tres ReflectionMapping<Tres>(T item)
        {
            Type type = item.GetType();
            PropertyInfo[] props = type.GetProperties();

            Tres result = Activator.CreateInstance<Tres>();
            Type resType = result.GetType();

            foreach (var prop in props)
            {
                PropertyInfo resProp = resType.GetProperty(prop.Name);
                if (resProp != null)
                {
                    resProp.SetValue(result, prop.GetValue(item, null), null);
                }
            }
            return result;
        }
    }
}
Excel出力時のフォーマットクラスの用意

Excel出力時に違うフォーマットにしたいこともあるでしょうが今回は1対1なので意味なし!

namespace Mvc2App.Report
{
    public class PepleExcelFormat
    {
        public int ID { get; set; }       
        public string Name { get; set; }
        public string Address { get; set; }
        public string PhoneNumber { get; set; }
        public string UpdatedBy { get; set; }
        public DateTime UpdateDate { get; set; } 
    }
}
ビューとJavaScriptの用意

■Site.Master
マスタービューでjQueryを読み込ませるのと、各ビューでJavaScriptを読み込めるようプレースホルダーを設置します。

<head runat="server">

    ...
    <script type="text/javascript" src="<%: Url.Content("~/Scripts/jquery-1.4.1.min.js") %>"></script>
    <script type="text/javascript" src="<%: Url.Content("~/Scripts/MicrosoftAjax.debug.js") %>"></script>
    <script type="text/javascript" src="<%: Url.Content("~/Scripts/MicrosoftMvcAjax.debug.js") %>"></script>
       

    <asp:ContentPlaceHolder ID="ScriptContent" runat="server" />
</head>

■Search.aspx
SearchビューではJavaScriptファイルを読み込ませ、jQueryで制御できるようにtableタグにIDを指定しておきます。

<asp:Content ID="ScriptContent" ContentPlaceHolderID="ScriptContent" runat="server">
    <script type="text/javascript" src="<%: Url.Content("~/Scripts/Libs/PepleSearch.js") %>"></script>
</asp:Content>

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">

        ...

        <div class="field-box">
            <p>
                <input type="submit" value="Search" />
                <input type="button" id="btnDownload" value="Download" />
            </p>
        </div>

    ...

    <div class="grid">
    <table id="gridData">
        ...
    </table>


</asp:Content>

■PepleSearch.js
jQueryでダウンロードボタン押下時にtableタグより検索結果が存在するか判断し、なければアラートを、あればDownloadアクションを実行します。

$(function () {
    //ダウンロードボタン押下時イベント
    $('#btnDownload').click(function () {

        var dataLength = $("#gridData tbody").children().length;
        if (dataLength <= 1) {
            alert('There is no data.')
            return;
        }

        var url = "/Peple/Download";
        window.location = url;
    });
});

実行結果

検索結果が一覧表示されていない状態でダウンロードボタンをクリックしてもデータがないよと怒られます。


f:id:sh_yoshida:20141128204710p:plain

まずは条件を指定せず検索し、全件ダウンロードします。


f:id:sh_yoshida:20141128204717p:plain
Excelファイルを開いて確認!いい感じ♪

f:id:sh_yoshida:20141128204729p:plain

次に条件を指定し検索、結果を絞ってダウンロードします。


f:id:sh_yoshida:20141128204751p:plain
絞られた結果が取得できました~。

f:id:sh_yoshida:20141128204800p:plain


ところで、ずーっとPeopleをPepleと間違えてました!
ちゃんとリファクタリングしましょう。


ASP.NET MVCはじめました~検索画面を作る - 1.21 jigowatts