Easily save/load data to/from Excel (XLSX) documents using strongly-typed C# classes.
- .NET 10.0 - This library targets .NET 10.0 only
- DocumentFormat.OpenXml 3.x - required from 3.1 onwards
The major.minor prefix comes from version.json and the patch number is the
Nerdbank.GitVersioning commit height. The prefix sat at 3.0 from 2020 to 2026 across 53
releases and is not used to signal API breaks - check the package dependencies and release
notes rather than inferring compatibility from the version alone.
The height restarts from zero whenever the prefix changes, which would have dropped the
patch number from 136 to 1 when the prefix moved to 3.1. versionHeightOffset in
version.json compensates for that, so numbering continues from where the 3.0 series
left off rather than restarting. Do not remove it: without it the next build would be
numbered below releases that are already published.
One such change is worth calling out, because the package metadata states it but the version number does not: only one DocumentFormat.OpenXml assembly can load per process, and from 3.1 SheetMagic requires OpenXml 3.x. An application that must stay on OpenXml 2.x should remain on 3.0.136 or earlier.
dotnet add package PanoramicData.SheetMagic- ? Strongly-typed - Work with your own C# classes
- ? Simple API - Easy to read and write XLSX files
- ? Multiple sheets - Add and read multiple worksheets
- ? Styling support - Apply table styles to your data
- ? Extended properties - Support for dynamic properties via
Extended<T> - ? Streams and files - Work with both
FileInfoandStreamobjects - ? Type safe - Full support for common .NET types including nullable types
usingPanoramicData.SheetMagic;// Define your classpublicclassThing{publicstringPropertyA{get;set;}publicintPropertyB{get;set;}}// Create some datavarthings=newList<Thing>{newThing{PropertyA="Value 1",PropertyB=1},newThing{PropertyA="Value 2",PropertyB=2},};// Write to Excel filevarfileInfo=newFileInfo($"Output {DateTime.UtcNow:yyyyMMddTHHmmss}Z.xlsx");usingvarworkbook=newMagicSpreadsheet(fileInfo);workbook.AddSheet(things);workbook.Save();usingPanoramicData.SheetMagic;// Read from Excel fileusingvarworkbook=newMagicSpreadsheet(fileInfo);workbook.Load();// Read from default worksheet (first sheet)varcars=workbook.GetList<Car>();// Read from a specific worksheet by namevaranimals=workbook.GetList<Animal>("Animals");// Write to a streamusingvarstream=newMemoryStream();using(varworkbook=newMagicSpreadsheet(stream)){workbook.AddSheet(data);workbook.Save();}// Read from a streamstream.Position=0;usingvarworkbook=newMagicSpreadsheet(stream);workbook.Load();varitems=workbook.GetList<MyClass>();usingvarworkbook=newMagicSpreadsheet(fileInfo);workbook.AddSheet(cars,"Cars");workbook.AddSheet(animals,"Animals");workbook.AddSheet(products,"Products");workbook.Save();varoptions=newAddSheetOptions{TableOptions=newTableOptions{Name="MyTable",DisplayName="MyTable1",XlsxTableStyle=XlsxTableStyle.TableStyleMedium2,ShowRowStripes=true,ShowColumnStripes=false,ShowFirstColumn=false,ShowLastColumn=false}};workbook.AddSheet(data,"StyledSheet",options);Conditional formatting is configured through AddSheetOptions.ConditionalFormats.
The object model is intentionally close to Excel's own configuration model:
ConditionalFormatselects one or more output columns, or all columns whenColumnNamesis omitted.ConditionalFormatRuledescribes one Excel rule such asCellIs,ContainsBlanks, orContainsErrors.ConditionalFormatStyledefines the differential format Excel applies when the rule matches.
ColumnNames must match the final header text written to Excel.
If PropertyHeaders is set, use those values.
Otherwise use the Description attribute value or the property name.
usingSystem.Drawing;usingPanoramicData.SheetMagic;varrows=new[]{newReportRow{Name=null,Description="Missing name",Score=null},newReportRow{Name="Bravo",Description="High score",Score=9},newReportRow{Name="Charlie",Description=null,Score=4}}.ToList();varoptions=newAddSheetOptions{ConditionalFormats=[newConditionalFormat{ColumnNames=["Name","Description"],Rules=[newConditionalFormatRule{RuleType=ConditionalFormatRuleType.ContainsBlanks,Style=newConditionalFormatStyle{BackgroundColor=Color.Red}}]},newConditionalFormat{ColumnNames=["Score"],Rules=[newConditionalFormatRule{RuleType=ConditionalFormatRuleType.ContainsBlanks,Style=newConditionalFormatStyle{BackgroundColor=Color.Red}},newConditionalFormatRule{RuleType=ConditionalFormatRuleType.CellIs,Operator=ConditionalFormatOperator.GreaterThan,Formula="5",Style=newConditionalFormatStyle{FontColor=Color.Green,FontWeight=FontWeight.Bold}}]},newConditionalFormat{Rules=[newConditionalFormatRule{RuleType=ConditionalFormatRuleType.ContainsErrors,Style=newConditionalFormatStyle{FontWeight=FontWeight.Bold}}]}]};usingvarworkbook=newMagicSpreadsheet(newFileInfo("ConditionalFormatting.xlsx"));workbook.AddSheet(rows,"Report",options);workbook.Save();publicsealedclassReportRow{publicstring?Name{get;set;}publicstring?Description{get;set;}publicint?Score{get;set;}}Supported rule types currently include:
CellIsExpressionContainsBlanksNotContainsBlanksContainsErrorsNotContainsErrorsContainsTextNotContainsTextBeginsWithEndsWithDuplicateValuesUniqueValuesTop10AboveAverage
Use the Description attribute to customize column headers:
usingSystem.ComponentModel;publicclassEmployee{publicintId{get;set;}[Description("Full Name")]publicstringName{get;set;}[Description("Hire Date")]publicDateTimeHireDate{get;set;}}// Include only specific propertiesvaroptions=newAddSheetOptions{IncludeProperties=new[]{"Name","Age","City"}};workbook.AddSheet(people,"Filtered",options);// Exclude specific propertiesvaroptions=newAddSheetOptions{ExcludeProperties=new[]{"InternalId","Password"}};workbook.AddSheet(users,"Public",options);Work with objects that have both strongly-typed and dynamic properties:
varextendedData=newList<Extended<MyClass>>{newExtended<MyClass>(newMyClass{Id=1,Name="Item 1"},newDictionary<string,object?>{{"DynamicProp1","Value1"},{"DynamicProp2",42}})};workbook.AddSheet(extendedData);workbook.Save();// Reading extended propertiesvarloadedData=workbook.GetExtendedList<MyClass>();foreach(variteminloadedData){Console.WriteLine($"{item.Item.Name}");foreach(varpropinitem.Properties){Console.WriteLine($" {prop.Key}: {prop.Value}");}}- Primitives:
int,long,short,uint,ulong,ushort - Floating point:
float,double,decimal - Boolean:
bool - Dates:
DateTime,DateTimeOffset - Strings:
string - Enums (stored as text)
- Lists:
List<string>(with configurable delimiter) - All nullable versions of the above
Configure behavior with the Options class:
varoptions=newOptions{StopProcessingOnFirstEmptyRow=true,IgnoreUnmappedProperties=true,EmptyRowInterpretedAsNull=false,LoadNullExtendedProperties=true,ListSeparator=";"};usingvarworkbook=newMagicSpreadsheet(fileInfo,options);- JObject Support: Direct
JObjectserialization is not yet supported. UseExtended<object>instead. - Nested Complex Objects: Properties of type
List<ComplexType>cannot be loaded from Excel (though they can be saved as delimited strings). - Large Integer Precision: Excel stores all numbers as doubles, so very large
Int64/UInt64values (nearMaxValue) may lose precision. - Special Values:
double.NaNandnullnullable types are stored as empty strings in Excel.
Contributions are welcome! Please feel free to submit a Pull Request.
See the LICENSE file for details.