VB.NET MVCにてOpenXMLを使用して、Excelをサーバーサイドで生成しダウンロードする

##はじめに
集計表などの帳票出力機能でExcelファイルをダウンロードさせる機能を作ることになったときに調べたことのメモ。
開発前に参考資料として[エクセル出力はこちらを参考にとこのURL](https://tnakamura.hatenablog.com/entry/2015/10/29/075452)を教えていただき、MemoryStreamに出力する方法はわかった。しかし、残念ながら今回参画したプロジェクトの使用言語はVBであったことと、罫線や値のセル内の配置位置、複数シート作成、というようなこともできないと困るので、他にいくつかサイトを検索しC#で書かれていたサンプルコードをVBでも動作するように改変した。

開発スケジュールの制約からあまりきれいにはまとまっていないが、とにかくExcelファイル作成ができるようになるための機能をOpenxmlUtility.vbというファイルにまとめ、レイアウトに関してはmodelで頑張って書くことで凌ぐことにした。


###動作環境
- サーバー側
	- ASP.NET（VB） MVC
	- OpenXML

###改変したソースコード

C#で書かれた情報が載っているだけでもありがたい。下記は参考サイトをもとに罫線や値のセル内の配置位置指定のメソッドをまとめたファイルである。


```VB:OpenxmlUtility.vb
Imports System
Imports DocumentFormat.OpenXml
Imports DocumentFormat.OpenXml.Packaging
Imports DocumentFormat.OpenXml.Spreadsheet
Imports Color = DocumentFormat.OpenXml.Spreadsheet.Color
Imports System.ComponentModel
Imports System.ComponentModel.DataAnnotations


Namespace Models

    ''' <summary>
    ''' OpenXmlの操作に便利なメソッド
    ''' Models直下に任意の名前で作成した。好きなところに置けばよい。Importsは必要なものを後で整理する。
    ''' </summary>
    Public Class OpenXmlUtility

        Private _DefaultFileName As String = "dummy.xlsx"

        Public ReadOnly Property DefaultFileName() As String
            Get
                Return _DefaultFileName
            End Get
        End Property

        

        ''' <summary>
        ''' 指定したセルの値の配置を設定する
        ''' 
        ''' </summary>
        ''' <param name="bookPart"></param>
        ''' <param name="cell"></param>
        ''' <param name="hAlign"></param>
        ''' <param name="vAlign"></param>
        Public Sub SetCellAlignment(ByRef bookPart As WorkbookPart,ByRef cell As Cell, hAlign As HorizontalAlignmentValues, vAlign As VerticalAlignmentValues)
            If cell Is Nothing Or bookPart Is Nothing Then
                Return
            End If

            Dim cellFormats = bookPart.WorkbookStylesPart.Stylesheet.CellFormats.Elements(Of CellFormat)
            If cell.StyleIndex IsNot Nothing Then
                Dim currentXf = cellFormats.ElementAt(Convert.ToInt32(cell.StyleIndex.Value))

                '同じ属性をもち、かつ指定されたalignと同じ設定をもつCellFormatを探す
                Dim formatTo As CellFormat
                Try
                    formatTo = cellFormats.FirstOrDefault(Function(r) r.GetAttributes().SequenceEqual(currentXf.GetAttributes()) _
                                                                And r.Alignment IsNot Nothing _
                                                                And r.Alignment.Horizontal IsNot Nothing And r.Alignment.Horizontal.Value = hAlign _
                                                                And r.Alignment.Vertical IsNot Nothing And r.Alignment.Vertical.Value = vAlign)
                Catch ex As NullReferenceException

                End Try
                If formatTo Is Nothing Then
                    formatTo                            = currentXf.Clone()
                
                    If formatTo.Alignment Is Nothing Then
                        formatTo.Alignment            = New Alignment()
                    End If
                    If formatTo.Alignment.Horizontal Is Nothing Then
                        formatTo.Alignment.Horizontal = New EnumValue(Of HorizontalAlignmentValues)
                    End If
                    formatTo.Alignment.Horizontal.Value = hAlign

                    If formatTo.Alignment.Vertical Is Nothing Then
                        formatTo.Alignment.Vertical   = New EnumValue(Of VerticalAlignmentValues)
                    End If
                    formatTo.Alignment.Vertical.Value   = vAlign

                    bookPart.WorkbookStylesPart.Stylesheet.CellFormats.Append(formatTo)
                    bookPart.WorkbookStylesPart.Stylesheet.Save()
                    cell.StyleIndex = New UInt32Value(CType(cellFormats.LongCount(), UInteger) -1UI)
                Else
                    cell.StyleIndex = New UInt32Value(CType(cellFormats.ToList().IndexOf(formatTo), UInteger))
                End If
            Else
                Dim formatTo As CellFormat          = New CellFormat()
                formatTo.Alignment                  = New Alignment()

                formatTo.Alignment.Horizontal       = New EnumValue(Of HorizontalAlignmentValues)
                formatTo.Alignment.Horizontal.Value = hAlign

                formatTo.Alignment.Vertical         = New EnumValue(Of VerticalAlignmentValues) 
                formatTo.Alignment.Vertical.Value   = vAlign

                bookPart.WorkbookStylesPart.Stylesheet.CellFormats.Append(formatTo)
                bookPart.WorkbookStylesPart.Stylesheet.Save()
                cell.StyleIndex                     = New UInt32Value(CType(cellFormats.LongCount(), UInteger) -1UI)

            End If
        End Sub

        ''' <summary>
        ''' 
        ''' </summary>
        ''' <param name="targetWsPart"></param>
        ''' <param name="cell"></param>
        ''' <param name="cellAddress"></param>
        ''' <param name="border"></param>
        Public Sub AppendCellBorder(ByRef targetWsPart As WorksheetPart,ByRef cell As Cell, cellAddress As String, border As Border)
            'Dim cell As Cell = GetCell(targetWsPart, cellAddress)
            Dim bookPart As WorkbookPart = targetWsPart.GetParentParts().First()

            Dim cellFormat As CellFormat
            If cell.StyleIndex Is Nothing Then
                cellFormat = new CellFormat()
            Else
                cellFormat = bookPart.WorkbookStylesPart.Stylesheet.CellFormats.ElementAt(CType(cell.StyleIndex.Value, Integer))
            End If

            Dim currentBorder As Border
            Dim mergedBorder  As Border
            If cellFormat.BorderId Is Nothing Then
                mergedBorder = border
            Else
                currentBorder = bookPart.WorkbookStylesPart.Stylesheet.Borders.ElementAt(CType(cellFormat.BorderId.Value, Integer))
                mergedBorder  = MergeBorder(currentBorder, border)
            End If

            Dim sameBorder As Border = bookPart.WorkbookStylesPart.Stylesheet.Borders.FirstOrDefault(Function(b)  b.OuterXml = mergedBorder.OuterXml)
            Dim borderId As UInt32

            If sameBorder Is Nothing Then
                bookPart.WorkbookStylesPart.Stylesheet.Borders.Append(mergedBorder)
                bookPart.WorkbookStylesPart.Stylesheet.Borders.Count.Value += 1
                borderId   = bookPart.WorkbookStylesPart.Stylesheet.Borders.Count.Value - 1
                sameBorder = mergedBorder
            Else
                borderId = bookPart.WorkbookStylesPart.Stylesheet.Borders.ToList().IndexOf(sameBorder)
            End If

            'borderIdを持ち、かつその他の属性が「cellFormatのborder以外」と一致するようなcellFormatを探す
            Dim cellFormatEnumerable As IEnumerable(Of CellFormat) = bookPart.WorkbookStylesPart.Stylesheet.CellFormats.Elements(Of CellFormat)()
            Dim targetFormat As CellFormat
            Try

                targetFormat = cellFormatEnumerable.FirstOrDefault(Function(xf) xf.GetAttributes(). _
                                                                                        Where(Function(at) at.LocalName <> "borderId" ).SequenceEqual(cellFormat.GetAttributes(). _ 
                                                                                        Where(Function(at) at.LocalName <> "borderId")) _ 
                                                                                        And (xf.BorderId <> Nothing And xf.BorderId.Value = borderId) And xf.InnerXml = cellFormat.InnerXml)
            Catch ex As NullReferenceException

            End Try
            If targetFormat Is Nothing Then
                targetFormat = cellFormat.CloneNode(True)
                If targetFormat.BorderId Is Nothing Then
                    targetFormat.BorderId = New UInt32Value(CType(borderId, UInteger))
                Else
                    targetFormat.BorderId.Value = CType(borderId, UInteger)
                End If
                
                bookPart.WorkbookStylesPart.Stylesheet.CellFormats.Append(targetFormat)
                bookPart.WorkbookStylesPart.Stylesheet.CellFormats.Count.Value += 1

                cell.StyleIndex = new UInt32Value(CType(bookPart.WorkbookStylesPart.Stylesheet.CellFormats.Count.Value - 1, UInteger))

            Else
                cell.StyleIndex = new UInt32Value(CType(cellFormatEnumerable.ToList().IndexOf(targetFormat), UInteger))
            End If

        End Sub

        Public Function MergeBorder(borderModified As Border,borderAppend As  Border) As Border
            Dim mergedBorder As Border = borderModified.CloneNode(true)

            'TopBorder
            If borderAppend.TopBorder IsNot Nothing And borderAppend.TopBorder.HasAttributes Then
                mergedBorder.TopBorder = borderAppend.TopBorder.CloneNode(true)
            End If

            'LeftBorder
            If borderAppend.LeftBorder IsNot Nothing And borderAppend.LeftBorder.HasAttributes Then
                mergedBorder.LeftBorder = borderAppend.LeftBorder.CloneNode(true)
            End If

            'BottomBorder
            If borderAppend.BottomBorder IsNot Nothing And borderAppend.BottomBorder.HasAttributes Then
                mergedBorder.BottomBorder = borderAppend.BottomBorder.CloneNode(true)
            End If

            'RightBorder
            If borderAppend.RightBorder IsNot Nothing And borderAppend.RightBorder.HasAttributes Then
                mergedBorder.RightBorder = borderAppend.RightBorder.CloneNode(true)
            End If

            'DiagonalBorder
            If borderAppend.DiagonalBorder IsNot Nothing And borderAppend.DiagonalBorder.HasAttributes Then
                mergedBorder.DiagonalBorder = borderAppend.DiagonalBorder.CloneNode(true)
            End If

            'DiagonalAttribute
            If borderAppend.DiagonalDown IsNot Nothing  Then
                mergedBorder.DiagonalDown = borderAppend.DiagonalDown.Clone()
            End If

            If borderAppend.DiagonalUp IsNot Nothing  Then
                mergedBorder.DiagonalUp = borderAppend.DiagonalUp.Clone()
            End If

            Return mergedBorder

        End Function

        ''' <summary>
        ''' セルアドレスで指定されたセルのオブジェクトを取得する（はずだが「sheetPart.Worksheet.Descendants(Of Row)().FirstOrDefault(Function(r As Row) r.RowIndex.Value = rowIndex)」で落ちる）
        ''' </summary>
        ''' <param name="sheetPart"></param>
        ''' <param name="cellAddress"></param>
        ''' <returns></returns>
        Private Function GetCell(sheetPart As WorksheetPart, cellAddress As String) As Cell

            Dim rowIndex As UInt32 = GetRowIndex(cellAddress)
            'Dim shData As SheetData = sheetPart.Worksheet.Descendants(Of SheetData)().First()  <- これは動く
            '下記行のようにrowを取得するとNothingのときNullReferenceException発生で先の処理に進めなくなったので改変する
            'Dim row As Row         = sheetPart.Worksheet.Descendants(Of Row)().FirstOrDefault(Function(r As Row) r.RowIndex.Value = rowIndex)
            Dim row As Row
            If sheetPart.Worksheet.Descendants(Of Row)().FirstOrDefault(Function(r As Row) r.RowIndex.Value = rowIndex) Is Nothing Then
                row = Nothing
            End If
            'Dim row As Row = Nothing
            If row Is Nothing Then
                row = New Row() With { .RowIndex = new UInt32Value(rowIndex) }

                Dim shData As SheetData = sheetPart.Worksheet.Descendants(Of SheetData)().First()
                '場所を特定して行を挿入(行は行番号で昇順に並ばないとだめ)
                Dim rRow As Row = shData.Elements(Of Row).LastOrDefault(Function(r)  r.RowIndex.Value < rowIndex)
                If rRow Is Nothing Then
                    shData.InsertAt(row, 0)
                Else
                    shData.InsertAfter(row, rRow)
                End If
            End If

            Dim colIndex As UInt32 = GetColumnIndex(cellAddress)
            Dim cell As Cell       = row.Elements(Of Cell).FirstOrDefault(Function(c) c.CellReference.Value = cellAddress)
            If cell Is Nothing Then
                cell = new Cell() With { .CellReference = new StringValue(cellAddress) }

                'cellは昇順に書き込む必要がある
                Dim rCell As Cell = row.Elements(Of Cell).LastOrDefault(Function(c) GetColumnIndex(c.CellReference) < colIndex)
                if rCell Is Nothing Then
                    row.InsertAt(cell, 0)
                Else
                    row.InsertAfter(cell, rCell)
                End If
            End If
            Return cell

        End Function

        Public Function GetRowIndex(cellName As String) As UInt32
            Dim regex As Regex = New Regex("\d+")
            Dim match As Match = regex.Match(cellName)
            return UInt32.Parse(match.Value)
        End Function

        Public Function GetColumnIndex(cellName As String) As UInt32
            Dim columnName As String = GetColumnName(cellName).ToUpper()
            Const colChars As String = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
            Dim columnIndex As UInt32 = 0
            Dim i As Integer
            For i = columnName.Length To 0 Step -1
                columnIndex += CType((colChars.IndexOf(columnName.Substring(columnName.Length - i, 1)) + 1) * Math.Pow(26, i - 1), UInteger)
            Next 
            Return columnIndex
        End Function

        Public Function GetColumnName(cellName As String) As String
            Dim match As Match = Regex.Match(cellName, "[A-Za-z]+")
            Return match.Value
        End Function

        Public Function GetWorksheetIndex(bookPart As WorkbookPart, sheetName As String) As Integer
            Dim sheets As Sheets = bookPart.Workbook.GetFirstChild(Of Sheets)
            'シートのエレメントの順番
            Dim sheetIndex As Integer = 0
            For Each sh As Sheet In sheets
                If sh.Name = sheetName Then
                    Exit For
                End If
                sheetIndex += 1
            Next
            Return sheetIndex
        End Function

        Public Function GetWorksheetPart(bookPart As WorkbookPart, index As Integer) As WorksheetPart
            Dim sheets As Sheets = bookPart.Workbook.GetFirstChild(Of Sheets)
            If index >= sheets.Count() Then
                Return Nothing
            End If
            Dim targetRerationshipID = sheets.Descendants(Of Sheet).ElementAt(Index).Id
            Return CType(bookPart.GetPartById(targetRerationshipID), WorksheetPart)

        End Function

        Public Function CreateDefaultStyles() As Stylesheet
            Dim ss As Stylesheet = New Stylesheet()

            Dim fts As Fonts     = New Fonts()

            'ID:0のフォント　シートのデフォルトとして使用
            Dim ft As DocumentFormat.OpenXml.Spreadsheet.Font = New DocumentFormat.OpenXml.Spreadsheet.Font()
            Dim ftn As FontName  = New FontName()
            ftn.Val              = StringValue.FromString("Arial")
            Dim ftsz As FontSize = New FontSize()
            ftsz.Val             = DoubleValue.FromDouble(11)
            ft.FontName          = ftn
            ft.FontSize          = ftsz
            fts.Append(ft)

            'ID:1のフォント　StyleIndex=1で指定する
            ft                   = New DocumentFormat.OpenXml.Spreadsheet.Font()
            ftn                  = new FontName()
            ftn.Val              = StringValue.FromString("Times New Roman")
            ftsz                 = new FontSize()
            ftsz.Val             = DoubleValue.FromDouble(18)
            ft.FontName          = ftn
            ft.FontSize          = ftsz

            'Boldの指定を追加
            ft.Append(new Bold())
            fts.Append(ft)

            fts.Count = UInt32Value.FromUInt32(CType(fts.ChildElements.Count, UInteger))
            '塗りつぶしパターン　今回未使用だが、タグが必要なため定義
            Dim fills As Fills = New Fills()
            Dim fill  As Fill  = New Fill()
            fills.Append(fill)

            fills.Count = UInt32Value.FromUInt32(CType(fills.ChildElements.Count, UInteger))
            '罫線パターン　今回未使用だが、タグが必要なため定義
            Dim borders As Borders = New Borders()
            Dim border  As Border  = New Border()
            borders.Append(border)
            borders.Count          = UInt32Value.FromUInt32(CType(borders.ChildElements.Count, UInteger))

            Dim csfs As CellStyleFormats = New CellStyleFormats()
            Dim cf   As CellFormat       = New CellFormat()
            cf.FontId                    = 0
            cf.FillId                    = 0
            cf.BorderId                  = 0
            csfs.Append(cf)
            csfs.Count                   = UInt32Value.FromUInt32(CType(csfs.ChildElements.Count, UInteger))

            Dim nfs As DocumentFormat.OpenXml.Spreadsheet.NumberingFormats = New DocumentFormat.OpenXml.Spreadsheet.NumberingFormats()
            Dim cfs As CellFormats                                         = New CellFormats()

            cf = New CellFormat()
            cf.NumberFormatId = 0
            cf.FontId         = 0
            cf.FillId         = 0
            cf.BorderId       = 0
            cf.FormatId       = 0
            cfs.Append(cf)

            'index 1
            cf = New CellFormat()
            cf.FontId   = 1
            cf.FillId   = 0
            cf.BorderId = 0
            cf.FormatId = 0

            '文字の配置位置を指定する
            Dim alignment As Alignment  = New Alignment()
            alignment.Horizontal        = HorizontalAlignmentValues.Right
            alignment.Vertical          = VerticalAlignmentValues.Top

            '自動で折り返す場合
            'alignment.WrapText = true

            'CellFormatに配置情報を追加
            cf.Append(alignment)
            cfs.Append(cf)
            nfs.Count = UInt32Value.FromUInt32(CType(nfs.ChildElements.Count, UInteger))
            cfs.Count = UInt32Value.FromUInt32(CType(cfs.ChildElements.Count, UInteger))

            ss.Append(fts)
            ss.Append(fills)
            ss.Append(borders)
            ss.Append(csfs)
            ss.Append(cfs)

            Dim css As CellStyles = New CellStyles()
            Dim cs  As CellStyle  = New CellStyle()
            cs.Name               = StringValue.FromString("Normal")
            cs.FormatId           = 0
            cs.BuiltinId          = 0
            css.Append(cs)
            css.Count             = UInt32Value.FromUInt32(CType(css.ChildElements.Count, UInteger))
            ss.Append(css)

            Dim dfs As DifferentialFormats = New DifferentialFormats()
            dfs.Count                      = 0
            ss.Append(dfs)

            Dim tss As TableStyles = new TableStyles()
            tss.Count              = 0
            tss.DefaultTableStyle  = StringValue.FromString("TableStyleMedium9")
            tss.DefaultPivotStyle  = StringValue.FromString("PivotStyleLight16")
            ss.Append(tss)

            return ss
        End Function

    End Class

End Namespace
```


## 使用例
下記のようにOpenXMLUtility.vbをmodel.vb内で使用すれば、Cellに罫線や値の配置位置が設定できる。

作成したバイナリデータとファイル名はcontrollerからReturn File()に渡せば罫線や値の配置位置が設定されたエクセルファイルがダウンロードできる。
クラス名など名前は投稿用に編集したので、必要に応じて適宜書き換える。


```VB:model.vb
Imports System.ComponentModel
Imports System.ComponentModel.DataAnnotations
Imports System.ComponentModel.DataAnnotations.Schema
Imports System.Data.Entity
Imports Oracle.ManagedDataAccess.Client
Imports System.Linq
Imports System.IO

Imports DocumentFormat.OpenXml
Imports DocumentFormat.OpenXml.Packaging
Imports DocumentFormat.OpenXml.Spreadsheet
Imports Color = DocumentFormat.OpenXml.Spreadsheet.Color


Namespace Models

    'ControllerのReturn File()にバイナリデータとファイル名を渡す
    Public Class SpreadSheetData

        Public Property fileContents        As Byte()

        Public Property filename            As String

    End Class


    'エクセルファイルのレイアウトが確定したらここに定数化できるものは書く
    Public Class SpreadsheetsModel

        Public Property LabelsSpreadsheets                       As String = "ファイル名"

        Public Property LabelSpreadsheetSheetName                As String = "シート名"
        
        Public Property LabelSubTotal                            As String = "小計"


    End Class


    Public Class Model
        ''' <summary>
        ''' エクセル出力用OpenXmlのバイナリデータ作成
        ''' </summary>
        ''' <param name="spreadsheetsDatas "></param>
        ''' <param name="model"></param>
        ''' <returns></returns>
        Public Function createOpenXMLSpreadSheetData(spreadsheetsDatas As List(Of spreadsheetsDatas), model As model) As SpreadSheetData

            Dim SpreadSheetData As SpreadSheetData = New SpreadSheetData()

            Using stream As New MemoryStream()
                Dim spreadsheetsModel   As SpreadsheetsModel          = New SpreadsheetsModel
                Dim OpenXmlUtility      As New OpenXmlUtility
                SpreadSheetData.filename                              = OpenXmlUtility.DefaultFileName()
                '.xlsxファイル作成
                Dim spreadsheetDocument As SpreadsheetDocument = SpreadsheetDocument.Create(stream, SpreadsheetDocumentType.Workbook)
              
                'workbook.xmlファイル作成　中身は空っぽ
                Dim workbookpart        As WorkbookPart        = spreadsheetDocument.AddWorkbookPart
                'workbook.xmlの要素（中身）作成
                workbookpart.Workbook                          = New Workbook
                'worksheets/sheet.xmlファイル作成用変数宣言
                Dim worksheetPart       As WorksheetPart
                Dim sheetData           As SheetData
            

                'Excelのスタイル動作確認用
                Dim stylesPart          As WorkbookStylesPart  = workbookpart.AddNewPart(Of WorkbookStylesPart)()
                stylesPart.Stylesheet                          = OpenXmlUtility.CreateDefaultStyles()
                stylesPart.Stylesheet.Save()

                'Sheetsをワークブックに追加
                Dim sheets              As Sheets
                sheets                                         = spreadsheetDocument.WorkbookPart.Workbook.AppendChild(New Sheets())
                Dim sheet  As Sheet 

                'シートのヘッダー部分
                Dim row1   As Row
                Dim cellA1 As Cell
                Dim cellC1 As Cell
                Dim cellE1 As Cell
                Dim cellI1 As Cell
                Dim row2   As Row
                Dim cellA2 As Cell
                Dim row3   As Row
                Dim cellA3 As Cell
                Dim cellB3 As Cell 
                Dim cellC3 As Cell 
                Dim cellD3 As Cell 
                Dim cellE3 As Cell 
                Dim cellF3 As Cell 
                Dim cellG3 As Cell 
                Dim cellH3 As Cell 
                Dim cellI3 As Cell

                '罫線の設定
                Dim border As Dictionary(Of String, Border) = New Dictionary(Of String, Border)
                border.Add("GridThin", New Border() With{ _
                                                    .LeftBorder   = New LeftBorder() With{ _
                                                                        .Style = BorderStyleValues.Thin, _
                                                                        .Color = New Color() With{ _
                                                                                            .Rgb = New HexBinaryValue("00000000")
                                                                                            } _
                                                                        }, _
                                                    .TopBorder    = New TopBorder() With{ _
                                                                        .Style = BorderStyleValues.Thin, _
                                                                        .Color = New Color() With{ _
                                                                                            .Rgb = New HexBinaryValue("00000000")
                                                                                            } _
                                                                        }, _
                                                    .RightBorder  = New RightBorder() With{ _
                                                                        .Style = BorderStyleValues.Thin, _
                                                                        .Color = New Color() With{ _
                                                                                            .Rgb = New HexBinaryValue("00000000")
                                                                                            } _
                                                                        }, _
                                                    .BottomBorder = New BottomBorder() With{ _
                                                                        .Style = BorderStyleValues.Thin, _
                                                                        .Color = New Color() With{ _
                                                                                            .Rgb = New HexBinaryValue("00000000")
                                                                                            } _
                                                                        } _
                                                    } _
                            )
                border.Add("NShapeThin", New Border() With{ _
                                                    .LeftBorder   = New LeftBorder() With{ _
                                                                        .Style = BorderStyleValues.Thin, _
                                                                        .Color = New Color() With{ _
                                                                                            .Rgb = New HexBinaryValue("00000000")
                                                                                            } _
                                                                        }, _
                                                    .TopBorder    = New TopBorder() With{ _
                                                                        .Style = BorderStyleValues.Thin, _
                                                                        .Color = New Color() With{ _
                                                                                            .Rgb = New HexBinaryValue("00000000")
                                                                                            } _
                                                                        }, _
                                                    .RightBorder  = New RightBorder() With{ _
                                                                        .Style = BorderStyleValues.Thin, _
                                                                        .Color = New Color() With{ _
                                                                                            .Rgb = New HexBinaryValue("00000000")
                                                                                            } _
                                                                        } _
                                                    } _
                            )
                border.Add("UShapeThin", New Border() With{ _
                                                    .LeftBorder   = New LeftBorder() With{ _
                                                                        .Style = BorderStyleValues.Thin, _
                                                                        .Color = New Color() With{ _
                                                                                            .Rgb = New HexBinaryValue("00000000")
                                                                                            } _
                                                                        }, _
                                                    .RightBorder  = New RightBorder() With{ _
                                                                        .Style = BorderStyleValues.Thin, _
                                                                        .Color = New Color() With{ _
                                                                                            .Rgb = New HexBinaryValue("00000000")
                                                                                            } _
                                                                        }, _
                                                    .BottomBorder = New BottomBorder() With{ _
                                                                        .Style = BorderStyleValues.Thin, _
                                                                        .Color = New Color() With{ _
                                                                                            .Rgb = New HexBinaryValue("00000000")
                                                                                            } _
                                                                        } _
                                                    } _
                            )
                border.Add("CShapeThin", New Border() With{ _
                                                    .LeftBorder   = New LeftBorder() With{ _
                                                                        .Style = BorderStyleValues.Thin, _
                                                                        .Color = New Color() With{ _
                                                                                            .Rgb = New HexBinaryValue("00000000")
                                                                                            } _
                                                                        }, _
                                                    .TopBorder    = New TopBorder() With{ _
                                                                        .Style = BorderStyleValues.Thin, _
                                                                        .Color = New Color() With{ _
                                                                                            .Rgb = New HexBinaryValue("00000000")
                                                                                            } _
                                                                        }, _
                                                    .BottomBorder = New BottomBorder() With{ _
                                                                        .Style = BorderStyleValues.Thin, _
                                                                        .Color = New Color() With{ _
                                                                                            .Rgb = New HexBinaryValue("00000000")
                                                                                            } _
                                                                        } _
                                                    } _
                            )
                border.Add("BackwordsCShapeThin", New Border() With{ _
                                                    .TopBorder    = New TopBorder() With{ _
                                                                        .Style = BorderStyleValues.Thin, _
                                                                        .Color = New Color() With{ _
                                                                                            .Rgb = New HexBinaryValue("00000000")
                                                                                            } _
                                                                        }, _
                                                    .RightBorder  = New RightBorder() With{ _
                                                                        .Style = BorderStyleValues.Thin, _
                                                                        .Color = New Color() With{ _
                                                                                            .Rgb = New HexBinaryValue("00000000")
                                                                                            } _
                                                                        }, _
                                                    .BottomBorder = New BottomBorder() With{ _
                                                                        .Style = BorderStyleValues.Thin, _
                                                                        .Color = New Color() With{ _
                                                                                            .Rgb = New HexBinaryValue("00000000")
                                                                                            } _
                                                                        } _
                                                    } _
                            )
                border.Add("HorizonalParallelThin", New Border() With{ _
                                                    .TopBorder   = New TopBorder() With{ _
                                                                        .Style = BorderStyleValues.Thin, _
                                                                        .Color = New Color() With{ _
                                                                                            .Rgb = New HexBinaryValue("00000000")
                                                                                            } _
                                                                        }, _
                                                    .BottomBorder  = New BottomBorder() With{ _
                                                                        .Style = BorderStyleValues.Thin, _
                                                                        .Color = New Color() With{ _
                                                                                            .Rgb = New HexBinaryValue("00000000")
                                                                                            } _
                                                                        } _
                                                    } _
                            )

                Dim row        As Row
                Dim cellIdx    As Cell
                Dim rowIdx     As UInt32 = 4  'データ表示領域の行番号
                Dim sheetId    As UInt32 = 1  'シートを複数作成するときはsheetIdを加算しながら作成する
                Dim cellRef    As String
                Dim mergeCells As MergeCells = New MergeCells

                If SpreadSheetData.filename = "dummy.xlsx" Then
                        SpreadSheetData.filename = String.Concat(spreadsheetsModel.LabelsSpreadsheets ".xlsx")
                End If

                'worksheets/sheet.xmlファイル作成 中身は空っぽ
                worksheetPart           = workbookpart.AddNewPart(Of WorksheetPart)()

                'sheet.xmlにworksheet要素作成
                sheetData               = New SheetData()
                worksheetPart.Worksheet = New Worksheet()

                '列の幅設定
                '必要な列の数分幅を設定し、columnsオブジェクトにcolumnオブジェクトの幅情報をAppendし、
                '最後にcolumnsオブジェクトをworksheetオブジェクトにAppendすれば列の幅が設定できる。
                Dim columns  As Columns = New Columns()
                Dim column1  As Column  = New Column()
                column1.Min         = 1
                column1.Max         = 1
                column1.Width       = 9
                column1.CustomWidth = True

                columns.Append(column1)

                Dim column2  As Column  = New Column()
                column2.Min         = 2
                column2.Max         = 2
                column2.Width       = 11
                column2.CustomWidth = True

                columns.Append(column2)

                Dim column3  As Column  = New Column()
                column3.Min         = 3
                column3.Max         = 3
                column3.Width       = 21
                column3.CustomWidth = True

                columns.Append(column3)

                ～～～略～～～

                worksheetPart.Worksheet.Append(columns)

                worksheetPart.Worksheet.Append(sheetData)

                'ワークシートをワークブックに追加
                'sheets要素内にsheet要素が作成させる
                'シートが複数必要な帳票の場合は、「worksheetPart = workbookpart.AddNewPart(Of WorksheetPart)()」辺りから
                '各シートのヘッダー部分のデータ作成処理を含めて「sheets.Append(sheet)」すれば複数シートのエクセルファイルが作成される。
                sheet                = New Sheet
                sheet.Id             = spreadsheetDocument.WorkbookPart.GetIdOfPart(worksheetPart)
                sheet.SheetId        = sheetId
                sheet.Name           = String.Concat(spreadsheetsModel.LabelSpreadsheetSheetName , DateTime.Now.ToString("yyyyMMddHHmmss"))
                sheets.Append(sheet)

                'シートの列の幅のデフォルト設定
                worksheetPart.Worksheet.SheetFormatProperties                    = New SheetFormatProperties()
                worksheetPart.Worksheet.SheetFormatProperties.DefaultRowHeight   = 0
                worksheetPart.Worksheet.SheetFormatProperties.DefaultColumnWidth = 5

                'Rowオブジェクトにcellオブジェクトを1セルずつAppendし、1行の最後で
                'sheetDataオブジェクトにRowオブジェクトをAppendすれば1行追加できる。
                row1                 = New Row
                cellA1               = New Cell
                cellA1.DataType      = CellValues.String
                cellA1.CellReference = "A1" 
                cellA1.CellValue     = New CellValue("title")
                row1.Append(cellA1)

                cellC1               = New Cell
                cellC1.DataType      = CellValues.String
                cellC1.CellReference = "C1" 
                cellC1.CellValue     = New CellValue("yyyymmdd")
                row1.Append(cellC1)

                sheetData.Append(row1)

                '2行目以降のデータ追加は1行目と同様、Rowオブジェクトにcellオブジェクトを1セルずつAppendし、
                '1行の最後でsheetDataオブジェクトにRowオブジェクトをAppendする。
                '途中の行を飛ばすとエクセルが開けなかったので、空行は空行のオブジェクトをAppendする
                row2                 = New Row
                cellA2               = New Cell
                cellA2.DataType      = CellValues.String
                cellA2.CellReference = "A2" 
                cellA2.CellValue     = New CellValue("")

                row2.Append(cellA2)
 
                sheetData.Append(row2)

                row3                 = New Row
                cellA3               = New Cell
                cellA3.DataType      = CellValues.String
                cellA3.CellReference = "A3" 
                cellA3.CellValue     = New CellValue(LABEL_TITLEA3)
                row3.Append(cellA3)

                cellB3               = New Cell
                cellB3.DataType      = CellValues.String
                cellB3.CellReference = "B3" 
                cellB3.CellValue     = New CellValue(LABEL_TITLEB3)
                row3.Append(cellB3)

                cellC3               = New Cell
                cellC3.DataType      = CellValues.String
                cellC3.CellReference = "C3" 
                cellC3.CellValue     = New CellValue(LABEL_TITLEC3)
                row3.Append(cellC3)

                ～～～略～～～

                sheetData.Append(row3)

                '罫線および値の配置位置の設定はどこでしてももよさそう
                '罫線の設定
                OpenXmlUtility.AppendCellBorder(worksheetPart, cellA3, "A3", border("GridThin"))
                OpenXmlUtility.AppendCellBorder(worksheetPart, cellB3, "B3", border("GridThin"))
                OpenXmlUtility.AppendCellBorder(worksheetPart, cellC3, "C3", border("GridThin"))
                OpenXmlUtility.AppendCellBorder(worksheetPart, cellD3, "D3", border("GridThin"))
                OpenXmlUtility.AppendCellBorder(worksheetPart, cellE3, "E3", border("GridThin"))
                OpenXmlUtility.AppendCellBorder(worksheetPart, cellF3, "F3", border("GridThin"))
                OpenXmlUtility.AppendCellBorder(worksheetPart, cellG3, "G3", border("GridThin"))
                OpenXmlUtility.AppendCellBorder(worksheetPart, cellH3, "H3", border("GridThin"))
                OpenXmlUtility.AppendCellBorder(worksheetPart, cellI3, "I3", border("GridThin"))
                        
                '値の配置位置
                OpenXmlUtility.SetCellAlignment(workbookpart, cellA3, HorizontalAlignmentValues.Center, VerticalAlignmentValues.Center)
                OpenXmlUtility.SetCellAlignment(workbookpart, cellB3, HorizontalAlignmentValues.Center, VerticalAlignmentValues.Center)
                OpenXmlUtility.SetCellAlignment(workbookpart, cellC3, HorizontalAlignmentValues.Center, VerticalAlignmentValues.Center)
                OpenXmlUtility.SetCellAlignment(workbookpart, cellD3, HorizontalAlignmentValues.Center, VerticalAlignmentValues.Center)
                OpenXmlUtility.SetCellAlignment(workbookpart, cellE3, HorizontalAlignmentValues.Center, VerticalAlignmentValues.Center)
                OpenXmlUtility.SetCellAlignment(workbookpart, cellF3, HorizontalAlignmentValues.Center, VerticalAlignmentValues.Center)
                OpenXmlUtility.SetCellAlignment(workbookpart, cellG3, HorizontalAlignmentValues.Center, VerticalAlignmentValues.Center)
                OpenXmlUtility.SetCellAlignment(workbookpart, cellH3, HorizontalAlignmentValues.Center, VerticalAlignmentValues.Center)
                OpenXmlUtility.SetCellAlignment(workbookpart, cellI3, HorizontalAlignmentValues.Center, VerticalAlignmentValues.Center)

                For Each spreadsheetsData In spreadsheetsDatas
                    'データベースより取得した数値をエクセルシートの各セルに入力
                    row                   = New Row

                    cellRef               = "A" & rowIdx.ToString("D")
                    cellIdx               = New Cell
                    cellIdx.DataType      = CellValues.String
                    cellIdx.CellReference = cellRef    
                    cellIdx.CellValue     = New CellValue(spreadsheetsData.VALUE_A)
                    OpenXmlUtility.AppendCellBorder(worksheetPart, cellIdx, cellRef , border("GridThin"))
                    row.Append(cellIdx)
                
                    cellRef               = "B" & rowIdx.ToString("D")
                    cellIdx               = New Cell
                    cellIdx.DataType      = CellValues.String
                    cellIdx.CellReference = cellRef    
                    cellIdx.CellValue     = New CellValue(spreadsheetsData.VALUE_B)
                    OpenXmlUtility.AppendCellBorder(worksheetPart, cellIdx, cellRef , border("GridThin"))
                    row.Append(cellIdx)

                    ～～～略～～～

                    '計算式はCellオブジェクトのCellFormulaに計算式を書けば設定できる
                    cellRef               = "F" & rowIdx.ToString("D")
                    cellIdx               = New Cell
                    cellIdx.CellReference = cellRef    
                    cellIdx.CellFormula   = New CellFormula("=[何か計算式]"))
                    OpenXmlUtility.AppendCellBorder(worksheetPart, cellIdx, cellRef , border("GridThin"))
                    row.Append(cellIdx)

                    ～～～略～～～

                    sheetData.Append(row)

                    rowIdx += 1
                Next

                'フッタ部分
                cellRef                 = "A" & rowIdx.ToString("D")
                row                     = New Row
                cellIdx                 = New Cell
                cellIdx.DataType        = CellValues.String
                cellIdx.CellReference   = cellRef    
                cellIdx.CellValue       = New CellValue(LABEL_FOOTER_A)
                OpenXmlUtility.SetCellAlignment(workbookpart, cellIdx, HorizontalAlignmentValues.Center, VerticalAlignmentValues.Center)
                OpenXmlUtility.AppendCellBorder(worksheetPart, cellIdx, cellRef   , border("CShapeThin"))
                row.Append(cellIdx)

                cellRef               = "B" & rowIdx.ToString("D")
                cellIdx               = New Cell
                cellIdx.CellReference = cellRef
                '下記のようにExcelの関数も入力できる
                cellIdx.CellFormula   = New CellFormula("SUM(B4:B" & rowIdx.ToString("D") - 1 & ")")
                OpenXmlUtility.AppendCellBorder(worksheetPart, cellIdx, cellRef   , border("HorizonalParallelThin"))
                row.Append(cellIdx)

                ～～～略～～～

                sheetData.Append(row)

                '次の行
                cellRef                 = "A" & rowIdx.ToString("D")
                row                     = New Row
                cellIdx                 = New Cell
                cellIdx.DataType        = CellValues.String
                cellIdx.CellReference   = cellRef    
                cellIdx.CellValue       = New CellValue("[何か値]")
                OpenXmlUtility.SetCellAlignment(workbookpart, cellIdx, HorizontalAlignmentValues.Center, VerticalAlignmentValues.Center)
                OpenXmlUtility.AppendCellBorder(worksheetPart, cellIdx, cellRef   , border("GridThin"))
                row.Append(cellIdx)

                'Cellのマージは下記のように書けばできる
                Dim mergeCell6 As MergeCell  = New MergeCell

                mergeCell6.Reference         = new StringValue("A"& rowIdx.ToString("D") & ":B"& rowIdx.ToString("D"))
                mergeCells.Append(mergeCell6)

                'セルを結合した場合、罫線は別途Cellに対して設定する必要がある
                cellRef                 = "B" & rowIdx.ToString("D")
                cellIdx                 = New Cell
                cellIdx.CellReference   = cellRef    
                OpenXmlUtility.AppendCellBorder(worksheetPart, cellIdx, cellRef   , border("GridThin"))
                row.Append(cellIdx)


                ～～～略～～～

                sheetData.Append(row)

                rowIdx += 1

                cellRef                 = "A" & rowIdx.ToString("D")
                row                     = New Row
                cellIdx                 = New Cell
                cellIdx.DataType        = CellValues.String
                cellIdx.CellReference   = cellRef    
                cellIdx.CellValue       = New CellValue(spreadsheetsModel.LabelSubTotal)
                OpenXmlUtility.SetCellAlignment(workbookpart, cellIdx, HorizontalAlignmentValues.Center, VerticalAlignmentValues.Center)
                OpenXmlUtility.AppendCellBorder(worksheetPart, cellIdx, cellRef   , border("GridThin"))
                row.Append(cellIdx)

                Dim mergeCell7 As MergeCell  = New MergeCell

                mergeCell7.Reference         = new StringValue("A"& rowIdx.ToString("D") & ":B"& rowIdx.ToString("D"))
                mergeCells.Append(mergeCell7)

                'セルの結合情報のAppendは、workbookをsaveする直前に1回実行すればExcelに反映されるので最後に1回実行すればよい。
                worksheetPart.Worksheet.Append(mergeCells)

                cellRef                 = "B" & rowIdx.ToString("D")
                cellIdx                 = New Cell
                cellIdx.CellReference   = cellRef    
                OpenXmlUtility.AppendCellBorder(worksheetPart, cellIdx, cellRef   , border("GridThin"))
                row.Append(cellIdx)

                cellRef                 = "C" & rowIdx.ToString("D")
                cellIdx                 = New Cell
                cellIdx.CellReference   = cellRef    
                cellIdx.CellFormula     = New CellFormula("=[合計の計算式]")
                OpenXmlUtility.SetCellAlignment(workbookpart, cellIdx, HorizontalAlignmentValues.Center, VerticalAlignmentValues.Center)
                OpenXmlUtility.AppendCellBorder(worksheetPart, cellIdx, cellRef   , border("GridThin"))
                row.Append(cellIdx)

                ～～～略～～～

                sheetData.Append(row)

                workbookpart.Workbook.Save()

                spreadsheetDocument.Close()

                SpreadSheetData.fileContents = stream.ToArray()

            End Using

            Return SpreadSheetData

        End Function

    End Class


End Namespace

```


##参考サイト
- [ASP.NET MVC で Excel ファイルを出力](https://tnakamura.hatenablog.com/entry/2015/10/29/075452)
- [OpenXML + C#のプログラムで罫線を引く](https://symfoware.blog.fc2.com/blog-entry-1142.html)
- [Open XML SDK 備忘録 セルの書式設定 - 罫線 その1](https://p-space.jp/index.php/development/open-xml-sdk/94-openxmlsdk16)
- [jOpenXML + C#のプログラムでセルの文字の配置を指定する](https://symfoware.blog.fc2.com/blog-entry-1140.html)
- [Open XML SDK 備忘録 セルの書式設定 - 文字の配置](https://p-space.jp/index.php/development/open-xml-sdk/89-openxmlsdk12
)
- [OpenXML + C#のプログラムでExcelの行の高さ、列の幅を指定する](https://symfoware.blog.fc2.com/blog-entry-1138.html)