Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 70 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,22 @@
# SharpExcel
SharpExcel is a .NET Standard 2.0+ library to simplify exporting, importing, and validate data provided using Microsoft Excel files (.xlsx, .xlsm).

The library is based on ClosedXml, and provides basic, but overridable formatting using a fluent API
SharpExcel is a powerful, easy-to-use .NET Standard 2.0 library designed to simplify the process of importing, exporting, styling, and validating Excel files. SharpExcel uses ClosedXml to handle reading and writing Excel files.

### Main focus
The library is focused on mapping a collection of C# models to a corresponding Excel file.

**SharpExcel makes sure that every Excel file you export, can also be re-imported and converted to the same data as was used to export it. This is useful for providing a template for a user or client to provide data to load into a program.**


### Validation
The library uses FluentValidation to validate imported data. This will generate a list of exactly which cells are invalid, and why.
We can even output a new Excel file, where all invalid cells have a red color, or any other defined style.

### Styling
SharpExcel also provides a fluent API to define styles. We can set default data and header styles and even override styles based on specific rules (for example: make a cell red when a number is below zero).

### Auto Dropdowns
Enum properties in your model will be automatically mapped into dropdown lists for a user to select.

---
## Install SharpExcel
Expand All @@ -19,25 +34,25 @@ There are a couple of simple steps to start using SharpExcel:

### Step 1: Define a data model

When defining a data model, we can use the ``[SharpExcelColumnDefinition]`` attribute to map Excel columns to model properties.
When defining a data model, we can use the ``[ExcelColumnDefinition]`` attribute to map Excel columns to model properties.
We can also use Data annotation attributes to generate validation errors when reading Excel files.

*In this example model we create a model for an employee:*

```csharp
public class EmployeeModel
{
[SharpExcelColumnDefinition(columnName: "ID", width: 45)]
[ExcelColumnDefinition(columnName: "ID", width: 45)]
public int Id { get; set; }

[SharpExcelColumnDefinition(columnName: "First Name", width: 30)]
[ExcelColumnDefinition(columnName: "First Name", width: 30)]
public string FirstName { get; set; } = null!;

[StringLength(12)]
[SharpExcelColumnDefinition(columnName: "Last Name", width: 50)]
[ExcelColumnDefinition(columnName: "Last Name", width: 50)]
public string LastName { get; set; } = null!;

[SharpExcelColumnDefinition(columnName: "Budget", width: 15)]
[ExcelColumnDefinition(columnName: "Budget", width: 15)]
public decimal Budget { get; set; }

//SharpExcel also supports enum values (these will be displayed as dropdowns in Excel)
Expand All @@ -53,17 +68,17 @@ public class EmployeeModel
In the simplest case we can register a synchronizer for the given model to the service collection.
This is a default implementation and can be used for simple imports/exports.
```csharp
builder.Services.AddSynchronizer<EmployeeModel>()
builder.Services.AddSharpExcelSynchronizer<EmployeeModel>()
```
Optionally, we can configure the synchronizer further:
```csharp
builder.Services.AddSynchronizer<TestExportModel>(options =>
builder.Services.AddSharpExcelSynchronizer<TestExportModel>(options =>
{
//apply default styling
options.WithDataStyle(SharpExcelCellStyleConstants.DefaultDataStyle);
options.WithDataStyle(ExcelCellStyleConstants.DefaultDataStyle);

//in this case we customize the styling for the header
options.WithHeaderStyle(new SharpExcelCellStyle()
options.WithHeaderStyle(new ExcelCellStyle()
.WithTextStyle(TextStyle.Bold)
.WithFontSize(18.0));
});
Expand All @@ -72,18 +87,18 @@ If we want to switch styling conditionally, styling rules can be added in the fo

*In this example, we want the text in the cell to be red when the budget is < 0*
```csharp
builder.Services.AddSynchronizer<EmployeeModel>(options =>
builder.Services.AddSharpExcelSynchronizer<EmployeeModel>(options =>
{
options.WithStylingRule(rule =>
{
//select property of model by name
rule.ForProperty(nameof(TestExportModel.Budget));
rule.ForProperty(nameof(EmployeeModel.Budget));
//provide a condition
rule.WithCondition(x => x.Budget < 0);
//color text red when condition is true
rule.WhenTrue(SharpExcelCellStyleConstants.DefaultDataStyle.WithTextColor(new(255, 100, 100)));
rule.WhenTrue(ExcelCellStyleConstants.DefaultDataStyle.WithTextColor(new(255, 100, 100)));
//color text green when condition is false
rule.WhenFalse(SharpExcelCellStyleConstants.DefaultDataStyle.WithTextColor(new(80, 160, 80)));
rule.WhenFalse(ExcelCellStyleConstants.DefaultDataStyle.WithTextColor(new(80, 160, 80)));
});
});
```
Expand All @@ -110,7 +125,7 @@ public class ApplicationService
*The following example shows how to write a collection of ``EmployeeModel`` to an excel file:*

```csharp
var arguments = new SharpExcelArguments()
var arguments = new ExcelArguments()
{
//sheet to read from
SheetName = "Employees",
Expand Down Expand Up @@ -143,7 +158,7 @@ The excel file must have a header row with the column names defined in the model
// in this case we load from a file, but this can also be a stream
using var workbook = new XLWorkbook("C:/Documents/filename.xslx");

var arguments = new SharpExcelArguments()
var arguments = new ExcelArguments()
{
//which sheet to read data from
SheetName = "Employees",
Expand All @@ -153,11 +168,46 @@ The excel file must have a header row with the column names defined in the model

await _synchronizer.ReadWorkbookAsync(arguments, workbook);
```
The loading of the excel file (and the XLWorkbook type) are provided by ClosedXml
For more information and documentation on these types visit [ClosedXml](https://github.com/ClosedXML/ClosedXML)

#### Validating Excel files
The loading of the Excel file (and the XLWorkbook type) are provided by ClosedXml
For more information and documentation on these types visit [ClosedXml](https://github.com/ClosedXML/ClosedXML)

The ``ReadWorkbookAsync`` method, returns the following model, where ``TModel`` is the model used for this SharpExcel Synchronizer:
```csharp
public class ExcelReadResult<TModel>
where TModel : class
{
//The data that has been read
public List<TModel> Records { get; set; } = new();

//Contains list of validation results, grouped by record
//these will contain the address of the cell, and the reason why validation has failed
public Dictionary<TModel, ExcelCellValidationResult> ValidationResults { get; set; } = new();
}
```

#### Returning a validated Excel file

We can also return the provided workbook, but with a different style applied to cells that are invalid.
SharpExcel will also generate annotations for these cells to show the user why they are not valid.

```csharp
using var errorCheckedWorkbook = await _synchronizer.ValidateAndAnnotateWorkbookAsync(excelArguments, workbook);
errorCheckedWorkbook.SaveAs(validationExportPath);
```

We can customize the Styling of these invalid cells during the initial setup of our SharpExcel Synchronizer.

```csharp
builder.Services.AddSharpExcelSynchronizer<TestExportModel>(options =>
{
//here we define the style of an errored cell.
//This is only applicable when we want to return a validated excel file.
//Any cells that have validation errors will have this style
options.WithErrorStyle(
ExcelCellStyleConstants.DefaultDataStyle
.WithTextColor(new ExcelColor(255, 100, 100))
.WithBackgroundColor(new ExcelColor(255, 100, 100, 70))
);
});
```
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ namespace SharpExcel.Models.Arguments;
/// <summary>
/// Arguments used for reading/writing excel files
/// </summary>
public class SharpExcelArguments
public class ExcelArguments
{
/// <summary>
/// Name of Excel worksheet to read/write
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,7 @@ namespace SharpExcel.Models.Attributes;
/// <param name="columnName">Name for the column in Excel</param>
/// <param name="width">column width to use (-1 means no value (default))</param>
/// <param name="format">optional format string (this will convert the value into a string in Excel)</param>
public class SharpExcelColumnDefinitionAttribute(
string columnName,
int width = -1,
string? format = null)
public class ExcelColumnDefinitionAttribute(string columnName, int width = -1, string? format = null)
: Attribute
{
public string DisplayName { get; set; } = columnName;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ public static ExporterOptions<TExportModel> GetDefaultOptions<TExportModel>()
where TExportModel : class, new()
{
return new ExporterOptions<TExportModel>()
.WithDataStyle(SharpExcelCellStyleConstants.DefaultDataStyle)
.WithHeaderStyle(SharpExcelCellStyleConstants.DefaultHeaderStyle)
.WithErrorStyle(SharpExcelCellStyleConstants.DefaultErrorStyle);
.WithDataStyle(ExcelCellStyleConstants.DefaultDataStyle)
.WithHeaderStyle(ExcelCellStyleConstants.DefaultHeaderStyle)
.WithErrorStyle(ExcelCellStyleConstants.DefaultErrorStyle);
}
}
6 changes: 3 additions & 3 deletions SharpExcel.Models/Configuration/ExporterOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public class ExporterOptions<TExportModel>
/// </summary>
/// <param name="style">style to use</param>
/// <returns></returns>
public ExporterOptions<TExportModel> WithHeaderStyle(SharpExcelCellStyle style)
public ExporterOptions<TExportModel> WithHeaderStyle(ExcelCellStyle style)
{
Styling.DefaultHeaderStyle = style;
return this;
Expand All @@ -31,7 +31,7 @@ public ExporterOptions<TExportModel> WithHeaderStyle(SharpExcelCellStyle style)
/// </summary>
/// <param name="style">style to use</param>
/// <returns></returns>
public ExporterOptions<TExportModel> WithDataStyle(SharpExcelCellStyle style)
public ExporterOptions<TExportModel> WithDataStyle(ExcelCellStyle style)
{
Styling.DefaultDataStyle = style;
return this;
Expand All @@ -42,7 +42,7 @@ public ExporterOptions<TExportModel> WithDataStyle(SharpExcelCellStyle style)
/// </summary>
/// <param name="style">style to use</param>
/// <returns></returns>
public ExporterOptions<TExportModel> WithErrorStyle(SharpExcelCellStyle style)
public ExporterOptions<TExportModel> WithErrorStyle(ExcelCellStyle style)
{
Styling.DefaultErrorStyle = style;
return this;
Expand Down
19 changes: 17 additions & 2 deletions SharpExcel.Models/Results/ExcelAddress.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,27 @@
namespace SharpExcel.Models.Results;

/// <summary>
/// Data structure
/// Data structure for addressing a single cell
/// </summary>
public struct ExcelAddress
public record struct ExcelAddress
{
/// <summary>
/// The row number
/// </summary>
public int RowNumber { get; set; }

/// <summary>
/// Column id
/// </summary>
public int ColumnId { get; set; }

/// <summary>
/// Column Name
/// </summary>
public string ColumnName { get; set; }

/// <summary>
/// Header name
/// </summary>
public string? HeaderName { get; set; }
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
namespace SharpExcel.Models.Styling.Colorization;
public readonly struct SharpExcelColor : IEquatable<SharpExcelColor>
public readonly struct ExcelColor : IEquatable<ExcelColor>
{
public SharpExcelColor(byte r, byte g, byte b, byte a = 255)
public ExcelColor(byte r, byte g, byte b, byte a = 255)
{
R = r;
G = g;
Expand Down Expand Up @@ -35,12 +35,12 @@ public byte A
set => _colorBytes[3] = value;
}

public SharpExcelColor WithAlpha(byte alpha)
public ExcelColor WithAlpha(byte alpha)
{
return new SharpExcelColor(_colorBytes[0], _colorBytes[1], _colorBytes[2], alpha);
return new ExcelColor(_colorBytes[0], _colorBytes[1], _colorBytes[2], alpha);
}

public bool Equals(SharpExcelColor other)
public bool Equals(ExcelColor other)
{
return _colorBytes.SequenceEqual(other._colorBytes);
}
Expand Down
30 changes: 30 additions & 0 deletions SharpExcel.Models/Styling/Colorization/ExcelColorConstants.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
namespace SharpExcel.Models.Styling.Colorization;

/// <summary>
/// Constant values for a set of colors
/// </summary>
public static class ExcelColorConstants
{
public static ExcelColor White = new(255, 255, 255);
public static ExcelColor Black = new(0, 0, 0);
public static ExcelColor Red = new(255, 0, 0);
public static ExcelColor Lime = new(0, 255, 0);
public static ExcelColor Blue = new(0, 0, 255);
public static ExcelColor Yellow = new(255, 255, 0);
public static ExcelColor Cyan = new(0, 255, 255);
public static ExcelColor Magenta = new(255, 0, 255);
public static ExcelColor Silver = new(192, 192, 192);
public static ExcelColor Gray = new(128, 128, 128);
public static ExcelColor Maroon = new(128, 0, 0);
public static ExcelColor Olive = new(128, 128, 0);
public static ExcelColor Green = new(0, 128, 0);
public static ExcelColor Purple = new(128, 0, 128);
public static ExcelColor Teal = new(0, 128, 128);
public static ExcelColor Navy = new(0, 0, 128);

//transparent
public static ExcelColor Transparent = new(0, 0, 0, 0);
public static ExcelColor TransparentWhite = new(255, 255, 255, 0);

}

30 changes: 0 additions & 30 deletions SharpExcel.Models/Styling/Colorization/ExcelSharpColorConstants.cs

This file was deleted.

17 changes: 17 additions & 0 deletions SharpExcel.Models/Styling/Constants/ExcelCellStyleConstants.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using SharpExcel.Models.Styling.Borders;
using SharpExcel.Models.Styling.Colorization;
using SharpExcel.Models.Styling.Text;

namespace SharpExcel.Models.Styling.Constants;

public static class ExcelCellStyleConstants
{
public static ExcelCellStyle DefaultHeaderStyle =
new(ExcelColorConstants.Black, ExcelColorConstants.Transparent, TextStyle.Bold, borders: BorderCollection.HeaderDefault);

public static ExcelCellStyle DefaultDataStyle =
new(ExcelColorConstants.Black, ExcelColorConstants.Transparent, TextStyle.None, borders: BorderCollection.DataDefault);

public static ExcelCellStyle DefaultErrorStyle =
new(new (80, 40, 40), new (255, 150, 150), TextStyle.None, borders: BorderCollection.DataDefault);
}

This file was deleted.

Loading
Loading