Skip to content

Commit

Permalink
Merge pull request #60 from dimabarbul/52-improve-eppluswriter
Browse files Browse the repository at this point in the history
  • Loading branch information
dimabarbul authored Jul 22, 2023
2 parents 189bec7 + 570488e commit d124547
Show file tree
Hide file tree
Showing 17 changed files with 485 additions and 188 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
using System;
using System.Collections.Generic;
using System.IO;
using Bogus;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using XReports.Converter;
using XReports.Excel;
using XReports.Excel.PropertyHandlers;
using XReports.Excel.Writers;
using XReports.ReportCellProperties;
using XReports.SchemaBuilders;
using XReports.Table;

namespace XReports.Demos.Controllers.EpplusWriterExtensions;

public class CustomEpplusWriterOptionsController : Controller
{
private const int RecordsCount = 10;

public IActionResult Index()
{
return this.View();
}

public IActionResult Download()
{
IReportTable<ReportCell> reportTable = this.BuildReport();
IReportTable<ExcelReportCell> excelReportTable = this.ConvertToExcel(reportTable);

Stream excelStream = this.WriteExcelReportToStream(excelReportTable);
return this.File(excelStream, Constants.ContentTypeExcel, "CustomEpplusWriterOptions.xlsx");
}

private IReportTable<ReportCell> BuildReport()
{
ReportSchemaBuilder<Entity> reportBuilder = new();
reportBuilder.AddColumn("Name", e => e.Name);
reportBuilder.AddColumn("Score", e => e.Score)
.AddProperties(new DecimalPrecisionProperty(2));

IReportTable<ReportCell> reportTable = reportBuilder.BuildVerticalSchema().BuildReportTable(this.GetData());
return reportTable;
}

private IReportTable<ExcelReportCell> ConvertToExcel(IReportTable<ReportCell> reportTable)
{
ReportConverter<ExcelReportCell> excelConverter = new(new[]
{
new DecimalPrecisionPropertyExcelHandler(),
});

return excelConverter.Convert(reportTable);
}

private Stream WriteExcelReportToStream(IReportTable<ExcelReportCell> reportTable)
{
EpplusWriter writer = new(
Options.Create(new EpplusWriterOptions()
{
WorksheetName = "Scores",
StartColumn = 2,
StartRow = 2,
}),
Array.Empty<IEpplusFormatter>());

return writer.WriteToStream(reportTable);
}

private IEnumerable<Entity> GetData()
{
return new Faker<Entity>()
.RuleFor(e => e.Name, f => f.Name.FullName())
.RuleFor(e => e.Score, f => f.Random.Decimal(0, 100))
.Generate(RecordsCount);
}

private class Entity
{
public string Name { get; set; }

public decimal Score { get; set; }
}
}
28 changes: 28 additions & 0 deletions demos/XReports.Demos/Views/CustomEpplusWriterOptions/Index.cshtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
@{
ViewBag.Title = "Custom EpplusWriter Options";
Layout = "_DemoPageLayout";
}

<a asp-action="Download">Download</a>

@section Description
{
<p>By default EpplusWriter writes report to worksheet named "Data" starting at cell A1. This can be changed by using configuring EpplusWriterOptions.</p>
<p>Click Download link to see result.</p>
}

@section ReportTable
{
}

@section Code
{
EpplusWriter writer = new(
Options.Create(new EpplusWriterOptions()
{
WorksheetName = "Scores",
StartColumn = 2,
StartRow = 2,
}),
Array.Empty&lt;IEpplusFormatter&gt;());
}
1 change: 1 addition & 0 deletions demos/XReports.Demos/Views/Shared/_Layout.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
<a class="dropdown-item" asp-controller="ThreeColorHeatmapConditional" asp-action="Index">3-Color Heatmap (Conditional Formatting)</a>
<a class="dropdown-item" asp-controller="Border" asp-action="Index">Border</a>
<a class="dropdown-item" asp-controller="AutoFit" asp-action="Index">AutoFit</a>
<a class="dropdown-item" asp-controller="CustomEpplusWriterOptions" asp-action="Index">Custom EpplusWriter Options</a>
</div>
</li>
<li class="nav-item dropdown">
Expand Down
2 changes: 1 addition & 1 deletion docs/xreports/attribute-based-builder.md
Original file line number Diff line number Diff line change
Expand Up @@ -557,7 +557,7 @@ Report built by this model will contain TitleProperty with Title equal to "User

## .NET Core Integration

You need to call extension method AddAttributeBasedBuilder. This will register IAttributeBasedBuilder as singleton by default. Attribute handlers and post-builders can be registered in DI container, but they don't have to, even if they have dependencies that need to be resolved from service provider.
You need to call extension method AddAttributeBasedBuilder. This will register IAttributeBasedBuilder as singleton by default. Attribute handlers and post-builders can be registered in DI container, but they don't have to. If there are constructor dependencies in attribute handlers or post-builders, they will be resolved from service provider.

```c#
// You can pass configuration callback and service lifetime.
Expand Down
27 changes: 12 additions & 15 deletions docs/xreports/epplus-writer.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,35 +16,32 @@ class MyExcelWriter : EpplusWriter {}
// Registers derived class of EpplusWriter as implementation of IEpplusWriter.
// It may be useful if you want to extend/override EpplusWriter methods.
services.AddEpplusWriter<MyExcelWriter>();

interface IExtendedExcelWriter : IEpplusWriter {}
class ExtendedExcelWriter : EpplusWriter, IExtendedExcelWriter {}
// Registers derived class of EpplusWriter as implementation of IEpplusWriter
// and your interface inherited from IEpplusWriter.
// It may be useful if you want to add method(s) to EpplusWriter.
services.AddEpplusWriter<IExtendedExcelWriter, ExtendedExcelWriter>();
```

All of the forms accept 2 optional arguments:
All of the forms accept 3 optional arguments:
- configuration callback to configure EpplusWriter options
- service lifetime - it applies to EpplusWriter class and all formatters provided in configuration callback, by default it's scoped
- configuration callback to configure EpplusWriter formatters
- service lifetime - it applies to EpplusWriter class, by default it's scoped

Example:

```c#
// Registers EpplusWriter along with all formatters (implementors of
// IEpplusFormatter interface) from executing assembly as singletons.
// Registers EpplusWriter as singletons. Formatters are not registered in DI
// container.
services.AddEpplusWriter(
o => o.AddFromAssembly(Assembly.GetExecutingAssembly()),
ServiceLifetime.Singleton);
```

Formatters are not registered in DI container. If they are registered, they are resolved from the container. Also even if the formatter is not registered, but it has constructor dependencies which are registered, they will be resolved from the container.

## Public Methods

There are 2 public methods to export report:
There are following public methods to export report:

- **WriteToFile**: saves report as XLSX file
- **WriteToStream**: saves report to new in-memory stream or the one provided
- **WriteToWorksheet**: writes report to existing worksheet starting at specified position

## Extending EpplusWriter

Expand All @@ -55,7 +52,7 @@ EpplusWriter class contains number of virtual methods you can override.
General Flow:

- WriteToFile/WriteToStream
- WriteReportToWorksheet (row=1, column=1)
- WriteToWorksheet (row=1, column=1)
- WriteHeader
- WriteHeaderCell
- WriteCell
Expand All @@ -68,9 +65,9 @@ General Flow:
- FormatCell (for columns that has SameColumnFormatProperty)
- PostCreate

All methods in above list lower WriteReportToWorksheet can be overridden.
All methods in above list lower WriteToWorksheet can be overridden.

The writer has some configuration properties that can be overridden in inherited class:
The writer has some configuration properties that can be changed using EpplusWriter options:

- WorksheetName - "Data" by default
- StartColumn - 1 by default
Expand Down
49 changes: 17 additions & 32 deletions src/XReports/DependencyInjection/EpplusWriterDI.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Linq;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
using XReports.Excel.Writers;

namespace XReports.DependencyInjection
Expand All @@ -14,66 +15,50 @@ public static class EpplusWriterDI
/// Registers <see cref="IEpplusWriter"/> in <see cref="IServiceCollection"/>.
/// </summary>
/// <param name="services">Service collection to register classes in.</param>
/// <param name="options">Action to configure EpplusWriter options.</param>
/// <param name="configure">Action that configures formatters that will be used by <see cref="IEpplusWriter"/>.</param>
/// <param name="lifetime">Service lifetime of <see cref="IEpplusWriter"/>.</param>
/// <returns>Service collection.</returns>
public static IServiceCollection AddEpplusWriter(
this IServiceCollection services,
Action<EpplusWriterOptions> options = null,
Action<TypesCollection<IEpplusFormatter>> configure = null,
ServiceLifetime lifetime = ServiceLifetime.Scoped)
{
return services.AddEpplusWriter<IEpplusWriter, EpplusWriter>(configure, lifetime);
return services.AddEpplusWriter<EpplusWriter>(options, configure, lifetime);
}

/// <summary>
/// Registers custom implementation of <see cref="IEpplusWriter"/> in <see cref="IServiceCollection"/>.
/// </summary>
/// <param name="services">Service collection to register classes in.</param>
/// <param name="options">Action to configure EpplusWriter options.</param>
/// <param name="configure">Action that configures formatters that will be used by <see cref="IEpplusWriter"/>.</param>
/// <param name="lifetime">Service lifetime of <see cref="IEpplusWriter"/>.</param>
/// <typeparam name="TEpplusWriter">Type of custom implementation of <see cref="IEpplusWriter"/>.</typeparam>
/// <returns>Service collection.</returns>
public static IServiceCollection AddEpplusWriter<TEpplusWriter>(
this IServiceCollection services,
Action<EpplusWriterOptions> options = null,
Action<TypesCollection<IEpplusFormatter>> configure = null,
ServiceLifetime lifetime = ServiceLifetime.Scoped)
where TEpplusWriter : class, IEpplusWriter
{
return services.AddEpplusWriter<IEpplusWriter, TEpplusWriter>(configure, lifetime);
}
TypesCollection<IEpplusFormatter> formatters = new TypesCollection<IEpplusFormatter>();
configure?.Invoke(formatters);

/// <summary>
/// Registers custom implementation of <see cref="IEpplusWriter"/> with custom service type in <see cref="IServiceCollection"/>.
/// </summary>
/// <param name="services">Service collection to register classes in.</param>
/// <param name="configure">Action that configures formatters that will be used by <see cref="IEpplusWriter"/>.</param>
/// <param name="lifetime">Service lifetime of <see cref="IEpplusWriter"/>.</param>
/// <typeparam name="TIEpplusWriter">Type of custom service type under which custom implementation of <see cref="IEpplusWriter"/> should be registered.</typeparam>
/// <typeparam name="TEpplusWriter">Type of custom implementation of <see cref="IEpplusWriter"/>.</typeparam>
/// <returns>Service collection.</returns>
public static IServiceCollection AddEpplusWriter<TIEpplusWriter, TEpplusWriter>(
this IServiceCollection services,
Action<TypesCollection<IEpplusFormatter>> configure = null,
ServiceLifetime lifetime = ServiceLifetime.Scoped)
where TIEpplusWriter : class, IEpplusWriter
where TEpplusWriter : class, TIEpplusWriter
{
if (configure != null)
if (options != null)
{
TypesCollection<IEpplusFormatter> options = new TypesCollection<IEpplusFormatter>();
configure(options);
foreach (Type type in options.Types)
{
services.TryAddEnumerable(new ServiceDescriptor(
typeof(IEpplusFormatter),
type,
lifetime));
}
services.Configure(options);
}

services.Add(new ServiceDescriptor(
typeof(TIEpplusWriter),
typeof(TEpplusWriter),
typeof(IEpplusWriter),
sp => new EpplusWriter(
sp.GetService<IOptions<EpplusWriterOptions>>(),
formatters.Types
.Select(t =>
(IEpplusFormatter)ActivatorUtilities.GetServiceOrCreateInstance(sp, t))),
lifetime));

return services;
Expand Down
Loading

0 comments on commit d124547

Please sign in to comment.