DioDocs for Excel のテンプレート機能が少し不満があったので自作した

#概要
・Visual Stadio 2019 Blazor Server で DioDocs for Excel を使用してみたが
　DioDocsのテンプレート機能が少し使いにくかったので、オリジナルで作成してみた。
　※明細の表が複数でも出力可能になる。

##手順
1.Visual Stadio 2019 で新しいプロジェクトで Blazor Server を選択し
　「DioDocs2」と言う名称でプロジェクトを作成する。

2.プロジェクトの追加で、コンソールアプリケーションを選択し、「Reports」という名称
　で追加する。

3.プロジェクトのプロパティ設定で、出力の種類を「クラスライブラリ」に変更する。

4.以下のファイルを追加

```DioDocsReports.cs
using System.Collections.Generic;
using GrapeCity.Documents.Excel;
using Reports.Model;

namespace Reports
{
    /// <summary>
    /// DioDocs帳票作成クラス
    /// </summary>
    public static class DioDocsReports
    {
        /// <summary>ワークブック</summary>
        private static Workbook _Workbook;

        /// <summary>ヘッダー定義記号（左）長さ</summary>
        private static int _HederKigoLeftLen = 4;

        /// <summary>明細定義記号（左）長さ</summary>
        private static int _DetailKigoLeftLen = 3;

        /// <summary>
        /// テンプレートOPEN
        /// </summary>
        /// <param name="fileName">ファイル名</param>
        /// <returns>ワークブック</returns>
        public static Workbook OpenTemplate(string fileName)
        {
            _Workbook = new Workbook();
            _Workbook.Open(fileName, OpenFileFormat.Xlsx);
            return _Workbook;
        }

        /// <summary>
        /// シート削除
        /// </summary>
        /// <param name="fileName">ファイル名</param>
        public static void DeleteSheet(string sheetName)
        {
            _Workbook.Worksheets[sheetName].Delete();
        }

        /// <summary>
        /// PDF出力(シート)
        /// </summary>
        /// <param name="fileName">ファイル名</param>
        public static void OutputPdfSheet(string fileName)
        {
            _Workbook.ActiveSheet.Save(fileName);
        }

        /// <summary>
        /// PDF出力(ブック)
        /// </summary>
        /// <param name="fileName">ファイル名</param>
        /// <returns>ステータス</returns>
        public static void OutputPdf(string fileName)
        {
            _Workbook.ActiveSheet.Save(fileName);
        }

        /// <summary>
        /// Excel Save
        /// </summary>
        /// <param name="fileName">ファイル名</param>
        public static void Save(string fileName)
        {
            _Workbook.Save(fileName);
        }

        /// <summary>
        /// マージン設定
        /// </summary>
        /// <param name="top">上</param>
        /// <param name="bottom">下</param>
        /// <param name="left">左</param>
        /// <param name="right">右</param>
        public static void SetMargin(double top, double bottom, double left, double right)
        {
            var sheet = _Workbook.ActiveSheet;
            sheet.PageSetup.TopMargin = top;
            sheet.PageSetup.BottomMargin = bottom;
            sheet.PageSetup.LeftMargin = left;
            sheet.PageSetup.RightMargin = right;
        }

        /// <summary>
        /// ヘッダーエリア設定
        /// </summary>
        /// <param name="row1">行1</param>
        /// <param name="col1">列1</param>
        /// <param name="rowcnt">行数</param>
        /// <param name="colcnt">列数</param>
        public static List<AreaModel> SetHeaderArea(int row1, int col1, int rowcnt, int colcnt)
        {
            List<AreaModel> areaList = new List<AreaModel>();
            var range = _Workbook.ActiveSheet.Range[row1, col1, rowcnt, colcnt];

            for (int r = 0; r < range.RowCount; r++)
            {
                for (int c = 0; c < range.ColumnCount; c++)
                {
                    var tmp = range[0 + r, c].Value == null ? null : range[0 + r, c].Value.ToString();
                    if (!string.IsNullOrEmpty(tmp))
                    {
                        if ((tmp.Length > 5) && (tmp.Substring(0, _HederKigoLeftLen) == "{***") && tmp.Substring(tmp.Length - 1, 1) == "}")
                        {
                            var addData = new AreaModel()
                            {
                                AreaName = tmp.Substring(_HederKigoLeftLen, tmp.Length - _HederKigoLeftLen).Replace("}", ""),
                                Row = row1 + r,
                                Col = col1 + c
                            };
                            areaList.Add(addData);
                            range[0 + r, c].Value = "";
                        }
                    }
                }
            }
            return areaList;
        }

        /// <summary>
        /// 明細エリア設定
        /// </summary>
        /// <param name="row1">行1</param>
        /// <param name="col1">列1</param>
        /// <param name="rowcnt">行数</param>
        /// <param name="colcnt">列数</param>
        public static List<AreaModel> SetDetailArea(int row1, int col1, int rowcnt, int colcnt)
        {
            List<AreaModel> areaList = new List<AreaModel>();
            var range = _Workbook.ActiveSheet.Range[row1, col1, rowcnt, colcnt];
            for (int r = 0; r < range.RowCount; r++)
            {
                for (int c = 0; c < range.ColumnCount; c++)
                {
                    var tmp = range[r, c].Value == null ? null : range[r, c].Value.ToString();
                    if (!string.IsNullOrEmpty(tmp))
                    {
                        if ((tmp.Length > (_DetailKigoLeftLen + 1)) && (tmp.Substring(0, _DetailKigoLeftLen) == "{**") && (tmp.Substring(tmp.Length - 1, 1) == "}"))
                        {
                            if ((tmp.Substring((_DetailKigoLeftLen + 1), 1) != "*"))
                            {
                                var addData = new AreaModel()
                                {
                                    AreaName = tmp.Substring(_DetailKigoLeftLen, tmp.Length - _DetailKigoLeftLen).Replace("}", ""),
                                    Row = row1 + r,
                                    Col = col1 + c
                                };
                                areaList.Add(addData);
                                range[0 + r, c].Value = "";
                            }
                        }
                    }
                }
            }
            return areaList;
        }

        /// <summary>
        /// ヘッダーデータ設定
        /// </summary>
        /// <typeparam name="T">テーブル</typeparam>
        /// <param name="list">データリスト</param>
        /// <param name="areaList">エリアリスト</param>
        public static void SetHedar<T>(List<T> list, List<AreaModel> areaList)
        {
            foreach (var a in list)
            {
                foreach (var property in a.GetType().GetProperties())
                {
                    foreach (var dat in areaList)
                    {
                        if (dat.AreaName == property.Name)
                        {
                            _Workbook.ActiveSheet.Cells[dat.Row, dat.Col].Value = a.GetType().GetProperty(property.Name).GetValue(a);
                        }
                    }
                }
                break;
            }
        }

        /// <summary>
        /// 明細データ設定
        /// </summary>
        /// <typeparam name="T">テーブル</typeparam>
        /// <param name="list">データリスト</param>
        /// <param name="areaList">エリアリスト</param>
        public static void SetDetail<T>(IEnumerable<T> list, List<AreaModel> areaList)
        {
            foreach (var a in list)
            {
                foreach (var property in a.GetType().GetProperties())
                {
                    foreach (var dat in areaList)
                    {
                        if (dat.AreaName == property.Name)
                        {
                            _Workbook.ActiveSheet.Cells[dat.Row++, dat.Col].Value = a.GetType().GetProperty(property.Name).GetValue(a);
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Cellデータ設定
        /// </summary>
        /// <typeparam name="data">値</typeparam>
        /// <param name="list">データリスト</param>
        /// <param name="areaList">エリアリスト</param>
        public static void SetCell(object obj, int row, int col)
        {
            _Workbook.ActiveSheet.Cells[row, col].Value = obj;
        }

        /// <summary>
        /// コピーテンプレート(シート)
        /// </summary>
        public static void CopyTemplete()
        {
            // テンプレートのシートをアクティブ
            _Workbook.Worksheets["temp"].Activate();

            // テンプレートのシートをコピー
            var copy_worksheet = _Workbook.ActiveSheet.Copy();

            // コピーしたシートに名前を設定
            copy_worksheet.Name = _Workbook.Worksheets.Count.ToString();

            // コピーしたシートをアクティブ
            copy_worksheet.Activate();
        }

        /// <summary>
        /// ページコピー
        /// </summary>
        /// <param name="row"></param>
        /// <param name="col"></param>
        /// <param name="page"></param>
        public static void PageCopy(int row, int col, int page)
        {
            for (int c = 1; c < page; c++)
            {
                _Workbook.ActiveSheet.Range[0, 0, row - 1, col - 1].Copy(_Workbook.ActiveSheet.Range[c * row, 0, row - 1, col - 1]);
                _Workbook.ActiveSheet.HPageBreaks.Add(_Workbook.ActiveSheet.Range[c * row, 0]);
            }
        }

        /// <summary>
        /// 明細行コピー
        /// </summary>
        /// <param name="list"></param>
        /// <param name="areaList"></param>
        public static void SetDetailCopy(int lineNo, int gyoCnt)
        {
            for (int c = 1; c < gyoCnt; c++)
            {
                _Workbook.ActiveSheet.Range[lineNo, 0, 1, 999].Insert(InsertShiftDirection.Down);
                _Workbook.ActiveSheet.Range[lineNo, 0, 1, 999].Copy(_Workbook.ActiveSheet.Range[lineNo + 1, 0, 1, 999]);
            }
        }
    }
}
```

```CreateReports.cs
using System.Collections.Generic;
using GrapeCity.Documents.Excel;

namespace Reports
{
    public class CreateReports
    {
        private Workbook _Workbook;


        public void PdfOutput()
        {
            List<HeaderModel> hederList = new List<HeaderModel>();
            List<UserModel> DataList = new List<UserModel>();

            var dat0 = new HeaderModel()
            {
                 Shozoku = "0001:大阪府"
            };
            hederList.Add(dat0);
            var dat1 = new UserModel()
            {
                Id = "0001",
                Name = "テスト０１",
                Gender = "M",
                Age = 20,
            };
            DataList.Add(dat1);
            var dat2 = new UserModel()
            {
                Id = "0002",
                Name = "テスト０２",
                Gender = "F",
                Age = 21,
            };
            DataList.Add(dat2);

            for(var c = 3; c < 60; c++)
            {
                var dat3 = new UserModel()
                {
                    Id = c.ToString("0000"),
                    Name = "テスト" + c.ToString(),
                    Gender = "M",
                    Age = c + 21,
                };
                DataList.Add(dat3);
            }

            // テンプレートOPEN
            var _Workbook = DioDocsReports.OpenTemplate(@"C:\Test\DioDocs2\DioDocs2\Template\ユーザー一覧表.xlsx");

            // テンプレートシートをコピー
            DioDocsReports.CopyTemplete();

            // ヘッダー領域を設定
            var listHeader = DioDocsReports.SetHeaderArea(0, 0, 5, 8);
            // 明細領域を設定
            var listDetail = DioDocsReports.SetDetailArea(6, 0, 1, 8);
            // データ件数に合わせて明細行をコピー
            DioDocsReports.SetDetailCopy(6, DataList.Count);
            // ヘッダー領域にデータを設定
            DioDocsReports.SetHedar<HeaderModel>(hederList, listHeader);
            // 明細領域にデータを設定
            DioDocsReports.SetDetail<UserModel>(DataList, listDetail);
            // テンプレートのシートを削除
            DioDocsReports.DeleteSheet("temp");
            // PDF出力
            DioDocsReports.OutputPdf(@"C:\Test\DioDocs2\DioDocs2\wwwroot\DownLoad\ユーザー一覧表.pdf");
            DioDocsReports.Save(@"C:\Output\ユーザー一覧表.xlsx");
        }
    }
    /// <summary>
    /// 詳細モデル
    /// </summary>
    public class UserModel
    {
        public string Id { get; set; }
        public string Name { get; set; }
        public string Gender { get; set; }
        public int Age { get; set; }
    }
    /// <summary>
    /// ヘッダーモデル
    /// </summary>
    public class HeaderModel
    {
           public string Shozoku { get; set; }
    }
}
```

5.NavMenu.razor を以下のように修正

```
<div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
    <ul class="nav flex-column">
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="" Match="NavLinkMatch.All">
                <span class="oi oi-home" aria-hidden="true"></span> Home
            </NavLink>
        </li>
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="counter">
                <span class="oi oi-plus" aria-hidden="true"></span> Counter
            </NavLink>
        </li>
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="fetchdata">
                <span class="oi oi-list-rich" aria-hidden="true"></span> Fetch data
            </NavLink>
        </li>
        /// -->追加
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="PdfOutput">
                <span class="oi oi-print" aria-hidden="true"></span>PdfOutput
            </NavLink>
        </li>
        /// <--追加
    </ul>
</div>
```

6.Page フォルダに以下のファイルを追加

```PdfOutput.razor
@page "/PdfOutput"
@using Reports
@inject IJSRuntime jsRuntime

<h3>PdfOutput</h3>

<button class="btn btn-primary" @onclick="CreatePdf">Pdf</button>
```

```php:PdfOutput.razor.cs

using System;
using System.Threading.Tasks;
using System.Text.Json;
using System.Security.Cryptography;
using Microsoft.JSInterop;
using Microsoft.AspNetCore.Components;
using Reports;

namespace DioDocs2.Pages
{
    public partial class PdfOutput
    {

        public async Task CreatePdf()
        {
            try
            {

                var rep = new CreateReports();
                rep.PdfOutput();
                rep = null;
                await Task.Delay(10);
                await jsRuntime.InvokeVoidAsync("DownLoad", @"ユーザー一覧表.pdf");
            }
            catch (Exception ex){

                var a = ex.Message;
            }
        }
    }
}
```

7.テンプレートファイル用のフォルダを追加 ...DioDocs2\Template

8.PDF出力用のフォルダを追加 ...DioDocs2\DioDocs2\wwwroot\DownLoad\

9.テンプレートファイルの追加　ユーザー一覧表.xlsx
![no000.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/666811/845c70ee-e06c-9649-cb37-147896f84118.jpeg)

10._Host.cshtml　を以下のように修正

```
@page "/"
@namespace DioDocs2.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@{
    Layout = null;
}

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>DioDocs2</title>
    <base href="~/" />
    <link rel="stylesheet" href="css/bootstrap/bootstrap.min.css" />
    <link href="css/site.css" rel="stylesheet" />
    <link href="DioDocs2.styles.css" rel="stylesheet" />
</head>
<body>
    <component type="typeof(App)" render-mode="ServerPrerendered" />

    <div id="blazor-error-ui">
        <environment include="Staging,Production">
            An error has occurred. This application may no longer respond until reloaded.
        </environment>
        <environment include="Development">
            An unhandled exception has occurred. See browser dev tools for details.
        </environment>
        <a href="" class="reload">Reload</a>
        <a class="dismiss">🗙</a>
    </div>

    <script src="_framework/blazor.server.js"></script>
</body>
</html>

<script>
    // ファイルダウンロード
    function DownLoad(fileName) {
        const link = document.createElement('a');
        link.setAttribute("type", "hidden");
        link.href = './DownLoad/' + fileName;
        link.target = '_blank';
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
        link.remove();
    }
</script>
```

11.実行結果
![no002.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/666811/76b290e7-efc6-30d6-39be-5bbcef33b649.jpeg)

![no003.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/666811/ac30cab5-d69d-afe2-48ba-ba95960244d0.jpeg)

![no005.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/666811/d1ca6888-7f25-a201-8bfc-1c68a60e2a2e.jpeg)