public static FileStreamResult StreamExcelDocument(List listOfReports, string filename, string sheetName) { var tempFilename = Path.Join(Path.GetTempPath(), filename); var dt=ListToDataTable(listOfReports); dt.TableName = sheetName; CreateExcelDocument(dt, tempFilename); var rawData = File.ReadAllBytes(tempFilename); var ms = new MemoryStream(rawData); var fr = new FileStreamResult(ms, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") { FileDownloadName = filename }; File.Delete(tempFilename); // We don't need this anymore return fr; } /// /// Create an Excel file, and write it to a file. /// /// DataTable containing the data to be written to the Excel. /// Name of file to be written. /// True if successful, false if something went wrong. public static bool CreateExcelDocument(DataTable dt, string excelFilename) { try { using (SpreadsheetDocument spreadsheet = SpreadsheetDocument.Create(excelFilename, SpreadsheetDocumentType.Workbook)) { WriteExcelFile(dt, spreadsheet); } return true; } catch (Exception) { return false; } } private static void WriteExcelFile(DataTable dt, SpreadsheetDocument spreadsheet) { // Create the Excel file contents. This function is used when creating an Excel file either writing // to a file, or writing to a MemoryStream. spreadsheet.AddWorkbookPart(); if (spreadsheet.WorkbookPart == null) return; spreadsheet.WorkbookPart.Workbook = new Workbook(); var definedNamesCol = new DefinedNames(); // The following line of code (which prevents crashes in Excel 2010) spreadsheet.WorkbookPart.Workbook.Append(new BookViews(new WorkbookView())); // If we don't add a "WorkbookStylesPart", OLEDB will refuse to connect to this .xlsx file ! var workbookStylesPart = spreadsheet.WorkbookPart.AddNewPart("rIdStyles"); workbookStylesPart.Stylesheet = GenerateStyleSheet(); workbookStylesPart.Stylesheet.Save(); var sheets = spreadsheet.WorkbookPart.Workbook.AppendChild(new Sheets()); // For each worksheet you want to create var worksheetName = dt.TableName; // Create worksheet part, and add it to the sheets collection in workbook var worksheetPart = spreadsheet.WorkbookPart.AddNewPart(); SheetData sheetData = new SheetData(); //add the first row Row firstRow = new Row(); firstRow.RowIndex = (UInt32)1; //create a cell in A1 (the upper left most cell of the merged cells) Cell dataCell = new Cell(); dataCell.CellReference = "A1"; CellValue cellValue = new CellValue(); cellValue.Text = "Raw Matrials and Price List"; dataCell.Append(cellValue); firstRow.AppendChild(dataCell); sheetData.AppendChild(firstRow); //add the second row Row secondrow = new Row(); secondrow.RowIndex = (UInt32)2; //create a cell in A2 (the upper left most cell of the merged cells) Cell secdataCell = new Cell(); secdataCell.CellReference = "A2"; CellValue seccellValue = new CellValue(); seccellValue.Text = "From: 121"; Cell secdataCell2 = new Cell(); secdataCell2.CellReference = "C2"; CellValue seccellValue2 = new CellValue(); seccellValue2.Text = "To: 250"; secdataCell.Append(seccellValue); secdataCell2.Append(seccellValue2); secondrow.AppendChild(secdataCell); secondrow.AppendChild(secdataCell2); sheetData.AppendChild(secondrow); //based on the table data to add row and cell. // Add a WorkbookPart to the document. worksheetPart.Worksheet = new Worksheet(sheetData); //create a MergeCells class to hold each MergeCell MergeCells mergeCells = new MergeCells(); //append a MergeCell to the mergeCells for each set of merged cells mergeCells.Append(new MergeCell() { Reference = new StringValue("A1:d1") }); mergeCells.Append(new MergeCell() { Reference = new StringValue("A2:b2") }); mergeCells.Append(new MergeCell() { Reference = new StringValue("c2:d2") }); worksheetPart.Worksheet.InsertAfter(mergeCells, worksheetPart.Worksheet.Elements().First()); WriteDataTableToExcelWorksheet(dt, worksheetPart, definedNamesCol); var sheet = new Sheet { Id = spreadsheet.WorkbookPart.GetIdOfPart(spreadsheet.WorkbookPart.WorksheetParts.First()), SheetId = 1, Name = worksheetName }; // If you want to define the Column Widths for a Worksheet, you need to do this *before* appending the SheetData // http://social.msdn.microsoft.com/Forums/en-US/oxmlsdk/thread/1d93eca8-2949-4d12-8dd9-15cc24128b10/ sheets.Append(sheet); // Append this worksheet's data to our Workbook, using OpenXmlWriter, to prevent memory problems //WriteDataTableToExcelWorksheet(dt, worksheetPart, definedNamesCol); spreadsheet.WorkbookPart.Workbook.Append(definedNamesCol); spreadsheet.WorkbookPart.Workbook.Save(); } private static void WriteDataTableToExcelWorksheet(DataTable dt, WorksheetPart worksheetPart, DefinedNames definedNamesCol) { var writer = OpenXmlWriter.Create(worksheetPart, Encoding.ASCII); writer.WriteStartElement(new Worksheet()); // To demonstrate how to set column-widths in Excel, here's how to set the width of all columns to our default of "25": uint inx = 1; writer.WriteStartElement(new Columns()); foreach (DataColumn dc in dt.Columns) { writer.WriteElement(new Column { Min = inx, Max = inx, CustomWidth = true, Width = 20 }); inx++; } writer.WriteEndElement(); writer.WriteStartElement(new SheetData()); // Create a Header Row in our Excel file, containing one header for each Column of data in our DataTable. // We'll also create an array, showing which type each column of data is (Text or Numeric), so when we come to write the actual // cells of data, we'll know if to write Text values or Numeric cell values. var numberOfColumns = dt.Columns.Count; var isIntegerColumn = new bool[numberOfColumns]; var isFloatColumn = new bool[numberOfColumns]; var isDateColumn = new bool[numberOfColumns]; var excelColumnNames = new string[numberOfColumns]; for (var n = 0; n < numberOfColumns; n++) { excelColumnNames[n] = GetExcelColumnName(n); } // Create the Header row in our Excel Worksheet // We'll set the row-height to 20px, and (using the "AppendHeaderTextCell" function) apply some formatting to the cells. // uint rowIndex = 3; writer.WriteStartElement(new Row { RowIndex = rowIndex, Height = 20, CustomHeight = true }); for (var colInx = 0; colInx < numberOfColumns; colInx++) { var col = dt.Columns[colInx]; AppendHeaderTextCell(excelColumnNames[colInx] + "1", col.ColumnName, writer); isIntegerColumn[colInx] = (col.DataType.FullName.StartsWith("System.Int")); isFloatColumn[colInx] = (col.DataType.FullName == "System.Decimal") || (col.DataType.FullName == "System.Double") || (col.DataType.FullName == "System.Single"); isDateColumn[colInx] = (col.DataType.FullName == "System.DateTime"); } writer.WriteEndElement(); // End of header "Row" // Now, step through each row of data in our DataTable. var ci = new CultureInfo("en-US"); foreach (DataRow dr in dt.Rows) { // ...create a new row, and append a set of this row's data to it. ++rowIndex; writer.WriteStartElement(new Row { RowIndex = rowIndex }); for (int colInx = 0; colInx < numberOfColumns; colInx++) { var cellValue = dr.ItemArray[colInx].ToString(); cellValue = ReplaceHexadecimalSymbols(cellValue); var cellReference = excelColumnNames[colInx] + rowIndex.ToString(); // Create cell with data if (isIntegerColumn[colInx] || isFloatColumn[colInx]) { // For numeric cells without any decimal places. // If this numeric value is NULL, then don't write anything to the Excel file. double cellFloatValue = 0; var bIncludeDecimalPlaces = isFloatColumn[colInx]; if (double.TryParse(cellValue, out cellFloatValue)) { cellValue = cellFloatValue.ToString(ci); AppendNumericCell(cellReference, cellValue, bIncludeDecimalPlaces, writer); } } else if (isDateColumn[colInx]) { // For date values, we save the value to Excel as a number, but need to set the cell's style to format // it as either a date or a date-time. if (DateTime.TryParse(cellValue, out var dateValue)) { AppendDateCell(cellReference, dateValue, writer); } else { // This should only happen if we have a DataColumn of type "DateTime", but this particular value is null/blank. AppendTextCell(cellReference, cellValue, writer); } } else { // For text cells, just write the input data straight out to the Excel file. AppendTextCell(cellReference, cellValue, writer); } } writer.WriteEndElement(); // End of Row } writer.WriteEndElement(); // End of SheetData writer.WriteEndElement(); // End of worksheet writer.Close(); } private static void AppendHeaderTextCell(string cellReference, string cellStringValue, OpenXmlWriter writer) { // Add a new "text" Cell to the first row in our Excel worksheet // We set these cells to use "Style # 3", so they have a gray background color & white text. writer.WriteElement(new Cell { CellValue = new CellValue(cellStringValue), CellReference = cellReference, DataType = CellValues.String, StyleIndex = 3 }); } private static void AppendTextCell(string cellReference, string cellStringValue, OpenXmlWriter writer) { // Add a new "text" Cell to our Row #if DATA_CONTAINS_FORMULAE // If this item of data looks like a formula, let's store it in the Excel file as a formula rather than a string. if (cellStringValue.StartsWith("=")) { AppendFormulaCell(cellReference, cellStringValue, writer); return; } #endif // Add a new Excel Cell to our Row writer.WriteElement(new Cell { CellValue = new CellValue(cellStringValue), CellReference = cellReference, DataType = CellValues.String }); } private static void AppendDateCell(string cellReference, DateTime dateTimeValue, OpenXmlWriter writer) { // So, if our time element is blank, we'll assign style 2, but if there IS a time part, we'll apply style 1. var cellStringValue = dateTimeValue.ToOADate().ToString(CultureInfo.InvariantCulture); var bHasBlankTime = (dateTimeValue.Date == dateTimeValue); writer.WriteElement(new Cell { CellValue = new CellValue(cellStringValue), CellReference = cellReference, StyleIndex = UInt32Value.FromUInt32(bHasBlankTime ? (uint)2 : (uint)1), DataType = CellValues.Number // Use this, rather than CellValues.Date }); } private static void AppendNumericCell(string cellReference, string cellStringValue, bool bIncludeDecimalPlaces, OpenXmlWriter writer) { // Add a new numeric Excel Cell to our Row. var cellStyle = (uint)(bIncludeDecimalPlaces ? 5 : 4); writer.WriteElement(new Cell { CellValue = new CellValue(cellStringValue), CellReference = cellReference, StyleIndex = cellStyle, // Style #4 formats with 0 decimal places, style #5 formats with 2 decimal places DataType = CellValues.Number }); } private static string ReplaceHexadecimalSymbols(string txt) { // when a non-ASCII character will slip into the data trying to export, and this will cause an invalid Excel to be created. // This function removes such characters. var r = "[\x00-\x08\x0B\x0C\x0E-\x1F]"; return Regex.Replace(txt, r, "", RegexOptions.Compiled); } // Convert a zero-based column index into an Excel column reference (A, B, C.. Y, Z, AA, AB, AC... AY, AZ, BA, BB..) public static string GetExcelColumnName(int columnIndex) { // Convert a zero-based column index into an Excel column reference (A, B, C.. Y, Z, AA, AB, AC... AY, AZ, BA, BB..) // eg GetExcelColumnName(0) should return "A" // GetExcelColumnName(1) should return "B" // GetExcelColumnName(25) should return "Z" // GetExcelColumnName(26) should return "AA" // GetExcelColumnName(27) should return "AB" // GetExcelColumnName(701) should return "ZZ" // GetExcelColumnName(702) should return "AAA" // GetExcelColumnName(1999) should return "BXX" // ..etc.. var firstInt = columnIndex / 676; var secondInt = (columnIndex % 676) / 26; if (secondInt == 0) { secondInt = 26; firstInt -= 1; } var thirdInt = (columnIndex % 26); var firstChar = (char)('A' + firstInt - 1); var secondChar = (char)('A' + secondInt - 1); var thirdChar = (char)('A' + thirdInt); if (columnIndex < 26) return thirdChar.ToString(); return columnIndex < 702 ? $"{secondChar}{thirdChar}" : $"{firstChar}{secondChar}{thirdChar}"; } private static Stylesheet GenerateStyleSheet() { // If you want certain Excel cells to have a different Format, color, border, fonts, etc, then you need to define a "CellFormats" records containing // these attributes, then assign that style number to your cell. // // For example, we'll define "Style # 3" with the attributes we'd like for our header row (Row #1) on each worksheet, where the text is a bit bigger, // and is white text on a dark-gray background. // // NB: The NumberFormats from 0 to 163 are hardcoded in Excel (described in the following URL), and we'll define a couple of custom number formats below. // https://msdn.microsoft.com/en-us/library/documentformat.openxml.spreadsheet.numberingformat.aspx // http://lateral8.com/articles/2010/6/11/openxml-sdk-20-formatting-excel-values.aspx // uint iExcelIndex = 164; return new Stylesheet( new NumberingFormats( // new NumberingFormat() // Custom number format # 164: especially for date-times { NumberFormatId = UInt32Value.FromUInt32(iExcelIndex++), FormatCode = StringValue.FromString("dd/MMM/yyyy hh:mm:ss") }, new NumberingFormat() // Custom number format # 165: especially for date times (with a blank time) { NumberFormatId = UInt32Value.FromUInt32(iExcelIndex++), FormatCode = StringValue.FromString("dd/MMM/yyyy") } ), new Fonts( new Font( // Index 0 - The default font. new FontSize() { Val = 10 }, new Color() { Rgb = new HexBinaryValue() { Value = "000000" } }, new FontName() { Val = "Arial" }), new Font( // Index 1 - A 12px bold font, in white. new Bold(), new FontSize() { Val = 12 }, new Color() { Rgb = new HexBinaryValue() { Value = "FFFFFF" } }, new FontName() { Val = "Arial" }), new Font( // Index 2 - An Italic font. new Italic(), new FontSize() { Val = 10 }, new Color() { Rgb = new HexBinaryValue() { Value = "000000" } }, new FontName() { Val = "Times New Roman" }) ), new Fills( new Fill( // Index 0 - The default fill. new PatternFill() { PatternType = PatternValues.None }), new Fill( // Index 1 - The default fill of gray 125 (required) new PatternFill() { PatternType = PatternValues.Gray125 }), new Fill( // Index 2 - The yellow fill. new PatternFill( new ForegroundColor() { Rgb = new HexBinaryValue() { Value = "FFFFFF00" } } ) { PatternType = PatternValues.Solid }), new Fill( // Index 3 - Dark-gray fill. new PatternFill( new ForegroundColor() { Rgb = new HexBinaryValue() { Value = "FF404040" } } ) { PatternType = PatternValues.Solid }) ), new Borders( new Border( // Index 0 - The default border. new LeftBorder(), new RightBorder(), new TopBorder(), new BottomBorder(), new DiagonalBorder()), new Border( // Index 1 - Applies a Left, Right, Top, Bottom border to a cell new LeftBorder(new Color() { Auto = true }) { Style = BorderStyleValues.Thin }, new RightBorder(new Color() { Auto = true }) { Style = BorderStyleValues.Thin }, new TopBorder(new Color() { Auto = true }) { Style = BorderStyleValues.Thin }, new BottomBorder(new Color() { Auto = true }) { Style = BorderStyleValues.Thin }, new DiagonalBorder()) ), new CellFormats( new CellFormat() { FontId = 0, FillId = 0, BorderId = 0 }, // Style # 0 - The default cell style. If a cell does not have a style index applied it will use this style combination instead new CellFormat() { NumberFormatId = 164 }, // Style # 1 - DateTimes new CellFormat() { NumberFormatId = 165 }, // Style # 2 - Dates (with a blank time) new CellFormat(new Alignment() { Horizontal = HorizontalAlignmentValues.Center, Vertical = VerticalAlignmentValues.Center }) { FontId = 1, FillId = 3, BorderId = 0, ApplyFont = true, ApplyAlignment = true }, // Style # 3 - Header row new CellFormat() { NumberFormatId = 3 }, // Style # 4 - Number format: #,##0 new CellFormat() { NumberFormatId = 4 }, // Style # 5 - Number format: #,##0.00 new CellFormat() { FontId = 1, FillId = 0, BorderId = 0, ApplyFont = true }, // Style # 6 - Bold new CellFormat() { FontId = 2, FillId = 0, BorderId = 0, ApplyFont = true }, // Style # 7 - Italic new CellFormat() { FontId = 2, FillId = 0, BorderId = 0, ApplyFont = true }, // Style # 8 - Times Roman new CellFormat() { FontId = 0, FillId = 2, BorderId = 0, ApplyFill = true }, // Style # 9 - Yellow Fill new CellFormat( // Style # 10 - Alignment new Alignment() { Horizontal = HorizontalAlignmentValues.Center, Vertical = VerticalAlignmentValues.Center } ) { FontId = 0, FillId = 0, BorderId = 0, ApplyAlignment = true }, new CellFormat() { FontId = 0, FillId = 0, BorderId = 1, ApplyBorder = true } // Style # 11 - Border ) ); // return } public static DataTable ListToDataTable(List list) { var dt = new DataTable(); foreach (var info in typeof(T).GetProperties()) { dt.Columns.Add(new DataColumn(info.Name, GetNullableType(info.PropertyType))); } foreach (var t in list) { var row = dt.NewRow(); foreach (var info in typeof(T).GetProperties()) { if (!IsNullableType(info.PropertyType)) row[info.Name] = info.GetValue(t, null); else row[info.Name] = (info.GetValue(t, null) ?? DBNull.Value); } dt.Rows.Add(row); } return dt; } private static Type GetNullableType(Type t) { var returnType = t; if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable<>)) { returnType = Nullable.GetUnderlyingType(t); } return returnType; } private static bool IsNullableType(Type type) { return (type == typeof(string) || type.IsArray || (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))); }