Live Razor Page Samples
Excel Workbook Consolidation
This sample shows how to utilize the IRange.Copy method to dynamically copy and consolidate data from multiple workbooks. It also demonstrates using a Tag Halper to render the contents of a range, or downloading the resulting workbook.
Supporting Files
The following files are utilized by this sample:
using System;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace Website.Pages.Support.Samples.RazorPages.Reporting
{
public partial class ExcelWorkbookConsolidationModel : PageModel
{
[BindProperty]
public string Region { get; set; }
// This range will be used to populate the HTML table.
public SpreadsheetGear.IRange DataRange { get; set; }
public void OnGet() { }
public void OnPostRenderInTable()
{
if (Region == null)
return;
// Gets either the consolidated or regional workbook.
SpreadsheetGear.IWorkbook workbook = GetSalesReportWorkbook();
if (Region == "All")
{
// Consolidated workbook will have a single sheet and used range will contain all the report data.
DataRange = workbook.Worksheets[0].UsedRange;
}
else
{
// For a regional workbook, get the range for the "YearSales" defined name.
DataRange = workbook.Names["YearSales"].RefersToRange;
}
}
public FileResult OnPostDownloadWorkbook()
{
SpreadsheetGear.IWorkbook workbook = GetSalesReportWorkbook();
// Save workbook to stream using the Open XML (*.xlsx) file format compatible with Excel 2007 and later.
System.IO.Stream workbookStream = workbook.SaveToStream(SpreadsheetGear.FileFormat.OpenXMLWorkbook);
// Reset position to beginning of stream.
workbookStream.Seek(0, System.IO.SeekOrigin.Begin);
// Stream the Excel workbook to the client.
var contentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
var fileName = $"Sales-{Region}.xlsx";
return File(workbookStream, contentType, fileName);
}
private SpreadsheetGear.IWorkbook GetSalesReportWorkbook()
{
if (Region == "All")
{
// Get a new workbook with all regions consolidated.
return GetWorkbookConsolidated();
}
else
{
// Get a workbook for the specified region
return GetWorkbookForRegion(Region);
}
}
private SpreadsheetGear.IWorkbook GetWorkbookForRegion(String region)
{
// Get the filename from the region name
string filename;
switch (region)
{
default:
case "North":
filename = "spicenorth.xlsx";
break;
case "South":
filename = "spicesouth.xlsx";
break;
case "East":
filename = "spiceeast.xlsx";
break;
case "West":
filename = "spicewest.xlsx";
break;
}
// Return a workbook from the filename
return SpreadsheetGear.Factory.GetWorkbook("files/" + filename);
}
private SpreadsheetGear.IWorkbook GetWorkbookConsolidated()
{
// Create a new workbook and name the first sheet
SpreadsheetGear.IWorkbook workbook = SpreadsheetGear.Factory.GetWorkbook();
SpreadsheetGear.IWorksheet worksheet = workbook.Worksheets[0];
worksheet.Name = "Total Sales";
// Copy all region templates to the new worksheet
CopyRegion(worksheet, "North", SpreadsheetGear.PasteOperation.None);
CopyRegion(worksheet, "South", SpreadsheetGear.PasteOperation.Add);
CopyRegion(worksheet, "East", SpreadsheetGear.PasteOperation.Add);
CopyRegion(worksheet, "West", SpreadsheetGear.PasteOperation.Add);
// Auto size all worksheet columns which contain data
worksheet.UsedRange.Columns.AutoFit();
return workbook;
}
private void CopyRegion(SpreadsheetGear.IWorksheet dstWorksheet, String region, SpreadsheetGear.PasteOperation pasteOperation)
{
// Open the specified region workbook and get the source range
SpreadsheetGear.IWorkbook srcWorkbook = GetWorkbookForRegion(region);
SpreadsheetGear.IRange srcRange = srcWorkbook.Names["YearSales"].RefersToRange;
// Set up our destination range to match the size of the source range
String address = srcRange.Address;
SpreadsheetGear.IRange dstRange = dstWorksheet.Cells[address];
// Copy the source range values and formats to the destination range
// We have to call copy twice here since there is currently no PasteType
// which does values and all formats together. This is a limitation in
// the Excel API, but probably should be added to the SpreadsheetGear API.
srcRange.Copy(dstRange,
SpreadsheetGear.PasteType.Values,
pasteOperation, true, false);
srcRange.Copy(dstRange,
SpreadsheetGear.PasteType.Formats,
pasteOperation, true, false);
}
}
}
@page
@model ExcelWorkbookConsolidationModel
<div class="row">
<div class="col-sm-12 col-md-4 mb-3">
<div class="card">
<h2 class="card-header">Choose a region</h2>
<div class="card-body">
<!-- Input Form -->
<form asp-page="ExcelWorkbookConsolidation" asp-page-handler="RenderInTable" method="post" id="sales-form">
<label>Sales by Region</label>
<div class="form-check">
<input type="radio" asp-for="Region" class="form-check-input" value="North" id="region-north">
<label for="region-north" class="form-check-label">North</label>
</div>
<div class="form-check">
<input type="radio" asp-for="Region" class="form-check-input" value="South" id="region-south">
<label for="region-south" class="form-check-label">South</label>
</div>
<div class="form-check">
<input type="radio" asp-for="Region" class="form-check-input" value="East" id="region-east">
<label for="region-east" class="form-check-label">East</label>
</div>
<div class="form-check">
<input type="radio" asp-for="Region" class="form-check-input" value="West" id="region-west">
<label for="region-west" class="form-check-label">West</label>
</div>
<div class="form-check">
<input type="radio" asp-for="Region" class="form-check-input" value="All" id="region-all">
<label for="region-all" class="form-check-all">All</label>
</div>
<div class="text-center mt-4">
<button formaction="@Url.Page("ExcelWorkbookConsolidation", "DownloadWorkbook")" disabled="@(Model.Region == null)" class="btn btn-primary" title="@(Model.Region == null ? "Select Region to download workbook" : "Download region data to workbook")"><i class="fas fa-arrow-alt-to-bottom"></i> Download Workbook</button>
</div>
</form>
</div>
</div>
</div>
<div class="col-sm-12 col-md-8 mb-3">
<range-to-table range="@Model.DataRange" first-row-is-header="true"></range-to-table>
</div>
</div>
@section Scripts
{
<script>
$(function () {
// Automatically post form to refresh table when a radio button is clicked.
let $form = $("#sales-form");
$form.find("input[type=radio]").change(function () {
$form.submit();
});
});
</script>
}
using System.Collections.Generic;
using System.Text;
using Microsoft.AspNetCore.Razor.TagHelpers;
namespace Website.Pages.Support.Samples.RazorPages
{
/// <summary>
/// Simple Tag Helper to convert a range of cells into an HTML table.
/// </summary>
[HtmlTargetElement("range-to-table")]
public class RangeToTableTagHelper : TagHelper
{
/// <summary>
/// The Range to be converted to an HTML table.
/// </summary>
public SpreadsheetGear.IRange Range { get; set; }
/// <summary>
/// Specifies whether to treat the first row in Range as a header row
/// </summary>
public bool FirstRowIsHeader { get; set; } = true;
public override void Process(TagHelperContext context, TagHelperOutput output)
{
// Can't generate anything if no range was provided.
if (Range == null)
{
output.TagName = "div";
output.Content.SetHtmlContent(@"<div class='alert alert-info'><i>No range data available.</i></div>");
return;
}
// Set output tag to <table> and add some styling courtesy of Bootstrap.
output.TagName = "table";
output.Attributes.Add("class", "table table-striped table-bordered table-hover table-sm");
// Store table in a StringBuilder as we construct it for faster processing of string concatenation.
StringBuilder sb = new StringBuilder();
// If FirstRowIsHeader we'll treat the first row as a header row and remaining rows as normal rows in the
// dataRange.
SpreadsheetGear.IRange dataRange = Range;
if (FirstRowIsHeader)
{
// Get reference to first row.
SpreadsheetGear.IRange headerRow = dataRange[0, 0, 0, dataRange.ColumnCount - 1];
sb.Append("<thead class='table-dark'><tr>");
// Loop over and render each cell in the "headerRow" range.
foreach (SpreadsheetGear.IRange cell in headerRow)
{
// Get some basic formatting from this cell.
string classes = GetClassAttribute(cell);
// Using IRange.Text here, which will return the formatted value of a cell.
sb.Append("<th" + (classes.Length > 0 ? $" class='{classes}'" : "") + ">").Append(cell.Text).Append("</th>");
}
sb.Append("</tr></thead>");
// Since the top row is a header row, we should remove it from dataRange, for which the
// IRange.Subtract(...) method is a perfect tool to do so.
dataRange = dataRange.Subtract(headerRow);
}
sb.Append("<tbody>");
// Could be null if we treated the first row as a header row and there were no additional rows.
if (dataRange != null)
{
// Calling dataRange.Rows returns the same range as dataRange, but in a form that allows
// it to be looped over in row "chunks", such as A1:C1, A2:C2, A3:C3 in the range A1:C3.
foreach (SpreadsheetGear.IRange row in dataRange.Rows)
{
sb.Append("<tr>");
// Similarly calling IRange.Columns will allow each column in the provided range to be
// iterated over (in this case each column of one row will consist of just a single cell).
foreach (SpreadsheetGear.IRange cell in row.Columns)
{
// Get some basic formatting from this cell.
string classes = GetClassAttribute(cell);
// Again, using IRange.Text here, which will return the formatted value of a cell.
sb.Append("<td" + (classes.Length > 0 ? $" class='{classes}'" : "") + ">").Append(cell.Text).Append("</td>");
}
sb.Append("</tr>");
}
}
else
{
sb.Append($"<tr><td colspan='{Range.ColumnCount}' class='text-center text-muted'>No Data Available</td></tr>");
}
sb.Append("</tbody>");
// Set output of tag helper to the HTML table.
output.Content.SetHtmlContent(sb.ToString());
}
/// <summary>
/// A very basic routine to add formatting to HTML output based on the corresponding IRange cell's formatting.
/// </summary>
/// <param name="cell">A single cell, for which formatting classes will be based off.</param>
/// <returns>A string such as ' class="..."' if formatting options are found, otherwise an empty string.</returns>
private static string GetClassAttribute(SpreadsheetGear.IRange cell)
{
List<string> classes = [];
// Horizontal Alignment
if (cell.HorizontalAlignment == SpreadsheetGear.HAlign.Center)
classes.Add("text-center");
else if (cell.HorizontalAlignment == SpreadsheetGear.HAlign.Right)
classes.Add("text-end");
// Basic font formatting
if (cell.Font.Bold)
classes.Add("fw-bold");
if (cell.Font.Italic)
classes.Add("fst-italic");
if (cell.Font.Underline != SpreadsheetGear.UnderlineStyle.None)
classes.Add("text-underline");
// If desired, this could be expanded to include other formatting, such as other font settings, cell
// interior color, etc.
if (classes.Count > 0)
return string.Join(' ', classes);
return "";
}
}
}