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
19 changes: 19 additions & 0 deletions SharpExcel.Models/Configuration/ExporterOptions.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using SharpExcel.Models.Data;
using SharpExcel.Models.Styling;
using SharpExcel.Models.Styling.Rules;

Expand All @@ -15,6 +16,11 @@ public class ExporterOptions<TExportModel>
/// </summary>
public StylingCollection<TExportModel> Styling { get; set; } = new();

/// <summary>
/// Targeting collection
/// </summary>
public TargetingCollection<TExportModel> Targeting { get; set; } = new();

/// <summary>
/// Fluent method to set default header style for this exporter
/// </summary>
Expand Down Expand Up @@ -60,4 +66,17 @@ public ExporterOptions<TExportModel> WithStylingRule(Action<StylingRule<TExportM
Styling.Rules.Add(stylingRule);
return this;
}

/// <summary>
/// Fluent method to add a styling rule for this exporter
/// </summary>
/// <param name="stylingRuleOptions">constructs the styling rule</param>
/// <returns></returns>
public ExporterOptions<TExportModel> WithTarget(Action<TargetingRule<TExportModel>> targetingRuleOptions)
{
var stylingRule = new TargetingRule<TExportModel>();
targetingRuleOptions(stylingRule);
Targeting.Rules.Add(stylingRule);
return this;
}
}
6 changes: 6 additions & 0 deletions SharpExcel.Models/Data/TargetingCollection.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace SharpExcel.Models.Data;

public class TargetingCollection<TExportModel>
{
public List<TargetingRule<TExportModel>> Rules { get; set; } = new();
}
76 changes: 76 additions & 0 deletions SharpExcel.Models/Data/TargetingRule.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
using System.ComponentModel.DataAnnotations;

namespace SharpExcel.Models.Data;

public interface ITargetingRule
{

}
public record TargetingRule<TRecord> : ITargetingRule
{
/// <summary>
/// REQUIRED: name of the sheet in the excel file
/// </summary>
[MinLength(1)]
public string SheetName { get; set; } = null!;

/// <summary>
/// Optional Row to start reading/writing from.
/// This is useful when you want to only affect part of a sheet.
/// Excel rows start as 1, so 1 is the first row
/// </summary>
public int? Row { get; set; }

/// <summary>
/// Optional Column to start reading/writing from.
/// This is useful when you want to only affect part of a sheet.
/// Excel columns start as 1, so 1 is the first row
/// </summary>
public int? Column { get; set; }

/// <summary>
/// Conditions to check if the rule should be applied.
/// </summary>
public Func<TRecord, bool>? RulePredicate { get; set; }

public TargetingRule<TRecord> WithCondition(Func<TRecord, bool> condition)
{
RulePredicate = condition;
//return this object so we can chain calls
return this;
}

/// <summary>
/// Sets the row to start reading/writing from.
/// </summary>
/// <param name="rowId"></param>
/// <returns></returns>
public TargetingRule<TRecord> WithStartRow(int rowId)
{
Row = rowId;
//return this object so we can chain calls
return this;
}

/// <summary>
/// Sets the column to start reading/writing from.
/// </summary>
public TargetingRule<TRecord> WithStartColumn(int columnId)
{
Column = columnId;
//return this object so we can chain calls
return this;
}

/// <summary>
/// Sets the sheet name to start reading/writing from.
/// </summary>
public TargetingRule<TRecord> WithSheetName(string sheetName)
{
SheetName = sheetName;
//return this object so we can chain calls
return this;
}
}


5 changes: 5 additions & 0 deletions SharpExcel.Models/Results/ExcelAddress.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,9 @@ public record struct ExcelAddress
/// Header name
/// </summary>
public string? HeaderName { get; set; }

/// <summary>
/// Sheet Name
/// </summary>
public string SheetName { get; set; }
}
21 changes: 21 additions & 0 deletions SharpExcel.Models/Results/ExcelReadResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,25 @@ public class ExcelReadResult<TModel>
public List<TModel> Records { get; set; } = new();

public Dictionary<TModel, ExcelCellValidationResult> ValidationResults { get; set; } = new();


}

public static class ExcelReadResultExtensions
{
public static void Append<TModel>(this ExcelReadResult<TModel> result, ExcelReadResult<TModel> other)
where TModel : class
{
result.Records.AddRange(other.Records);
foreach (var kvp in other.ValidationResults)
{
if (!result.ValidationResults.ContainsKey(kvp.Key))
{
result.ValidationResults.Add(kvp.Key, kvp.Value);
continue;
}
result.ValidationResults[kvp.Key] = kvp.Value;
}

}
}
16 changes: 16 additions & 0 deletions SharpExcel.Models/Styling/Constants/ExcelTargetingConstants.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using SharpExcel.Models.Data;

namespace SharpExcel.Models.Styling.Constants;

public class ExcelTargetingConstants<TModel>
where TModel : class
{
public static TargetingRule<TModel> DefaultTargetingRule = new TargetingRule<TModel>
{
SheetName = "Export",
Column = 1,
Row = 1,
RulePredicate = _ => true,
};

}
30 changes: 20 additions & 10 deletions SharpExcel.TestApplication/Program.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System.Globalization;
using SharpExcel.Models.Arguments;
using SharpExcel.Models.Results;
using SharpExcel.Models.Styling.Colorization;
using SharpExcel.TestApplication.TestData;
Expand Down Expand Up @@ -48,6 +47,23 @@
//can be omitted to use default style
rule.WhenFalse(ExcelCellStyleConstants.DefaultDataStyle.WithTextColor(new(80, 160, 80)));
});

options.WithTarget(rule =>
{
rule.WithCondition(x => x.Status != TestStatus.Fired);
rule.WithSheetName("Employees");
rule.WithStartColumn(3);
rule.WithStartRow(3);
});

options.WithTarget(rule =>
{
rule.WithCondition(x => x.Status == TestStatus.Fired);
rule.WithSheetName("Fired");
rule.WithStartRow(3);
rule.WithStartColumn(3);

});
});

using IHost host = builder.Build();
Expand All @@ -60,20 +76,14 @@ async Task RunApp(IServiceProvider services)
var exportPath = $"./OutputFolder/TestExport-{Guid.NewGuid()}.xlsx";
var validationExportPath = $"./OutputFolder/ErrorChecked-{Guid.NewGuid()}.xlsx";
var exportService = services.GetRequiredService<ISharpExcelSynchronizer<TestExportModel>>();

var excelArguments = new ExcelArguments()
{
SheetName = "Budgets",
CultureInfo = CultureInfo.CurrentCulture
};

using var workbook = await exportService.GenerateWorkbookAsync(excelArguments, TestDataProvider.GetTestData());
using var workbook = await exportService.GenerateWorkbookAsync(CultureInfo.CurrentCulture, TestDataProvider.GetTestData());
workbook.SaveAs(exportPath);

using var errorCheckedWorkbook = await exportService.ValidateAndAnnotateWorkbookAsync(excelArguments, workbook);
using var errorCheckedWorkbook = await exportService.ValidateAndAnnotateWorkbookAsync(CultureInfo.CurrentCulture, workbook);
errorCheckedWorkbook.SaveAs(validationExportPath);

var importedWorkbook = await exportService.ReadWorkbookAsync(excelArguments, workbook);
var importedWorkbook = await exportService.ReadWorkbookAsync(CultureInfo.CurrentCulture, workbook);

#region write_output
foreach (var dataItem in importedWorkbook.Records)
Expand Down
2 changes: 1 addition & 1 deletion SharpExcel.TestApplication/TestData/TestDataProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ public static List<TestExportModel> GetTestData()
new() { Id = 0, FirstName = "John", LastName = "Doe", Budget = 2400.34m, Email = "john.doe@example.com", TestDepartment = TestDepartment.Unknown, Status = TestStatus.Employed },
new() { Id = 1, FirstName = "Jane", LastName = "Doe", Budget = -200.42m, Email = "jane.doe@example.com", TestDepartment = TestDepartment.ValueB, Status = TestStatus.Fired },
new() { Id = 2, FirstName = "John", LastName = "Neutron", Budget = 0.0m, Email = null, TestDepartment = TestDepartment.ValueB, Status = TestStatus.Employed },
new() { Id = 3, FirstName = "Ash", LastName = "Ketchum", Budget = 69m, Email = "ash@example.com", TestDepartment = TestDepartment.ValueC, Status = TestStatus.Fired },
new() { Id = 3, FirstName = "Ash", LastName = "Ketchum", Budget = 69m, Email = null, TestDepartment = TestDepartment.ValueC, Status = TestStatus.Fired },
new() { Id = 4, FirstName = "Inspector", LastName = "Gadget", Budget = 1337m, Email = "gogogadget@example.com", TestDepartment = TestDepartment.ValueC, Status = TestStatus.Employed },
new() { Id = 5, FirstName = "Mindy", LastName = "", Budget = 2400.34m, Email = "mmouse@example.com", TestDepartment = TestDepartment.ValueA, Status = TestStatus.Employed },
new() { Id = 6, FirstName = "ThisIsLongerThan10", LastName = "Mouse", Budget = 2400.34m, Email = "mmouse@example.com", TestDepartment = TestDepartment.ValueA, Status = TestStatus.Employed },
Expand Down
14 changes: 7 additions & 7 deletions SharpExcel.Tests/ExcelImportTests.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
using System.Globalization;
using ClosedXML.Excel;
using Microsoft.Extensions.Options;
using SharpExcel.Tests.Shared;
using SharpExcel.Models.Arguments;
using SharpExcel.Models.Configuration.Constants;
using SharpExcel.Models.Styling.Constants;
using Shouldly;
using Xunit;

Expand All @@ -23,15 +24,15 @@ public ExcelImportTests()
[Fact]
public async Task CreateWorkbookTest()
{
var workbook = await _synchronizer.GenerateWorkbookAsync(new ExcelArguments(){ SheetName = "TestSheet"}, CreateTestData());
workbook.Worksheets.FirstOrDefault(x => x.Name == "TestSheet").ShouldNotBeNull();
var workbook = await _synchronizer.GenerateWorkbookAsync(CultureInfo.CurrentCulture, CreateTestData());
workbook.Worksheets.FirstOrDefault(x => x.Name == ExcelTargetingConstants<TestModel>.DefaultTargetingRule.SheetName).ShouldNotBeNull();

workbook.ShouldNotBeNull();
//there should be 2 worksheets, a visible one for the data, and a hidden one to pull data from for the enum dropdowns
workbook.Worksheets.Count.ShouldBe(2);

//main data worksheet
workbook.Worksheet(1).Name.ShouldBe("TestSheet");
workbook.Worksheet(1).Name.ShouldBe(ExcelTargetingConstants<TestModel>.DefaultTargetingRule.SheetName);
workbook.Worksheet(1).Visibility.ShouldBe(XLWorksheetVisibility.Visible);

//hidden worksheet for enum dropdowns
Expand All @@ -41,12 +42,11 @@ public async Task CreateWorkbookTest()
[Fact]
public async Task ReadWorkbookTest()
{
var args = new ExcelArguments() { SheetName = "TestSheet" };
//create test workbook
var workbook = await _synchronizer.GenerateWorkbookAsync( args, CreateTestData());
var workbook = await _synchronizer.GenerateWorkbookAsync(CultureInfo.InvariantCulture, CreateTestData());

//read workbook
var output = await _synchronizer.ReadWorkbookAsync(args, workbook);
var output = await _synchronizer.ReadWorkbookAsync(CultureInfo.InvariantCulture, workbook);


output.Records.Count.ShouldBe(2);
Expand Down
14 changes: 7 additions & 7 deletions SharpExcel/Abstraction/ISharpExcelSynchronizer.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
using ClosedXML.Excel;
using SharpExcel.Models.Arguments;
using System.Globalization;
using ClosedXML.Excel;
using SharpExcel.Models.Results;

namespace SharpExcel.Abstraction;
Expand All @@ -14,10 +14,10 @@ public interface ISharpExcelSynchronizer<TModel>
/// <summary>
/// Generates a workbook based on the provided data
/// </summary>
/// <param name="arguments">Collection of arguments</param>
/// <param name="cultureInfo"></param>
/// <param name="data">The data to generate the workbook from</param>
/// <returns></returns>
public Task<XLWorkbook> GenerateWorkbookAsync(ExcelArguments arguments, IEnumerable<TModel> data);
public Task<XLWorkbook> GenerateWorkbookAsync(CultureInfo cultureInfo, ICollection<TModel> data);

/// <summary>
/// Reads a workbook to convert it into the given model
Expand All @@ -26,13 +26,13 @@ public interface ISharpExcelSynchronizer<TModel>
/// <param name="workbook"></param>
/// <typeparam name="TModel"></typeparam>
/// <returns></returns>
public Task<ExcelReadResult<TModel>> ReadWorkbookAsync(ExcelArguments arguments, XLWorkbook workbook);
public Task<ExcelReadResult<TModel>> ReadWorkbookAsync(CultureInfo arguments, XLWorkbook workbook);

/// <summary>
/// Reads, then returns the supplied workbook, but highlights cells containing invalid data, using standard System.ComponentModel.DataAnnotations validation on the model
/// </summary>
/// <param name="arguments">Collection of arguments</param>
/// <param name="cultureInfo"></param>
/// <param name="workbook">The workbook</param>
/// <returns>The highlighted workbook</returns>
public Task<XLWorkbook> ValidateAndAnnotateWorkbookAsync(ExcelArguments arguments, XLWorkbook workbook);
public Task<XLWorkbook> ValidateAndAnnotateWorkbookAsync(CultureInfo cultureInfo, XLWorkbook workbook);
}
Loading