FluentSpreadsheets.ClosedXML
1.6.7
dotnet add package FluentSpreadsheets.ClosedXML --version 1.6.7
NuGet\Install-Package FluentSpreadsheets.ClosedXML -Version 1.6.7
<PackageReference Include="FluentSpreadsheets.ClosedXML" Version="1.6.7" />
paket add FluentSpreadsheets.ClosedXML --version 1.6.7
#r "nuget: FluentSpreadsheets.ClosedXML, 1.6.7"
// Install FluentSpreadsheets.ClosedXML as a Cake Addin #addin nuget:?package=FluentSpreadsheets.ClosedXML&version=1.6.7 // Install FluentSpreadsheets.ClosedXML as a Cake Tool #tool nuget:?package=FluentSpreadsheets.ClosedXML&version=1.6.7
FluentSpreadsheets
FluentSpreadsheets
FluentSpreadsheets.ClosedXML
FluentSpreadsheets.GoogleSheets
Overview
FluentSpreadsheets library consists of two APIs:
- Component API
An API that provides a set ofcomponents
, that can be used for building static UI with sheet cells as building blocks, as well as base logic for drawing defined component composition on the sheet. - Table API
An API that provides a set of abstractions to definetables
by usingrows
built fromcomponents
andcomponent sources
.
Examples
Component API
The base unit of component API is IComponent
interface, it provides a basic interface to interact with all components.
public interface IComponent
{
Size Size { get; }
void Accept(IComponentVisitor visitor);
}
There are many derived interfaces from IComponent
used to reflect on component type in IComponentVisitor
.
All component implementations are internal, so to create an instance of a component you need to use a static
class ComponentFactory
.
Hint
You can import static members of
ComponentFactory
for cleaner code.using static FluentSpreadsheets.ComponentFactory;
Components
Use .Label
to create a label component. You can pass a string
to it, or use a generic overload which will
call ToString
on the passed object (IFormattable
overload supported).
Label("Hello, World!");
Label(2.2, CultureInfo.InvariantCulture);
Containers
Use .VStack
& .HStack
to stack components vertically or horizontally, they will auto scale child components' width
and height respectively.
VStack
(
HStack
(
Label("Hello"),
Label(",")
),
Label("Stacks!")
)
The result will be something like this:
Stacks will automatically scale their children so they all will have an equal width/height and fill a rectangle.
Styles
Use extension methods to style components.
Styles are cascading! It means that styles applied to container will be inherited by its children (and overriden, if
needed). \
Cascading behaviour does not apply to styling of single component, if you will apply style A on a component, then style B, the component will have a style equal to style B applied to style A.
VStack
(
HStack
(
Label("Hello").WithContentAlignment(HorizontalAlignment.Trailing),
Label(",")
),
Label("Styles!").WithContentAlignment(HorizontalAlignment.Center, VerticalAlignment.Top)
).WithTrailingBorder(BorderType.Thin, Color.Black).WithBottomBorder(BorderType.Thin, Color.Black)
Components are immutable, when you apply a style to a component, it will return a new component with the style applied, the object you called a method on will not be changed.
The result will be something like this:
Resizing
Size values are accepted as relative multipliers to default platform's sizes (column width/row height). \
Output
Code above will only produce component composition stored as objects in memory. To render it on the sheet,
you need to use IComponentRenderer<T>
.
Now supported:
Excel output via "ClosedXML" library. (You will need to reference a FluentSpreadsheets.ClosedXML
NuGet package)
var workbook = new XLWorkbook();
var worksheet = workbook.AddWorksheet("Sample");
var helloComponent =
VStack
(
HStack
(
Label("Hello")
.WithContentAlignment(HorizontalAlignment.Trailing)
.WithTrailingBorder(BorderType.Thin, Color.Black),
Label(",")
),
Label("Styles!")
.WithContentAlignment(HorizontalAlignment.Center, VerticalAlignment.Top)
.WithTopBorder(BorderType.Thin, Color.Black)
.WithRowHeight(1.7)
).WithBottomBorder(BorderType.Thin, Color.Black).WithTrailingBorder(BorderType.Thin, Color.Black);
var renderer = new ClosedXmlComponentRenderer();
var renderCommand = new ClosedXmlRenderCommand(worksheet, helloComponent);
await renderer.RenderAsync(renderCommand);
workbook.SaveAs("sample.xlsx");
Google Sheets output via "Google Sheets API v4" library. (You will need to referencea FluentSpreadsheets.GoogleSheets
NuGet package)
var credential = GoogleCredential.FromFile("credentials.json");
var initializer = new BaseClientService.Initializer
{
HttpClientInitializer = credential
};
var service = new SheetsService(initializer);
var renderer = new GoogleSheetComponentRenderer(service);
var helloComponent =
VStack
(
HStack
(
Label("Hello")
.WithContentAlignment(HorizontalAlignment.Trailing)
.WithTrailingBorder(BorderType.Thin, Color.Black),
Label(",")
),
Label("Styles!")
.WithContentAlignment(HorizontalAlignment.Center, VerticalAlignment.Top)
.WithTopBorder(BorderType.Thin, Color.Black)
.WithRowHeight(1.7)
).WithBottomBorder(BorderType.Thin, Color.Black).WithTrailingBorder(BorderType.Thin, Color.Black);
const string spreadsheetId = "SampleSpreadsheetId";
const string title = "SampleTitle";
var renderCommandFactory = new RenderCommandFactory(service);
var renderCommand = await renderCommandFactory.CreateAsync(spreadsheetId, title, helloComponent);
await renderer.RenderAsync(renderCommand);
Table API
Table API is based on ITable<T>
interface, where T
is a type of model, that is used to render a table.
To define a table you need to create a class derived from RowTable<T>
and
implement IEnumerable<IRowComponent> RenderRows(T model)
method.
To customize rendered table override Customize
method in your table class.
public readonly record struct CartItem(string Name, decimal Price, int Quantity);
public readonly record struct CartTableModel(IReadOnlyCollection<CartItem> Items);
public class CartTable : RowTable<CartTableModel>, ITableCustomizer
{
protected override IEnumerable<IRowComponent> RenderRows(CartTableModel model)
{
yield return Row
(
Label("Product Name").WithColumnWidth(1.7),
Label("Price"),
Label("Quantity")
);
foreach (var item in model.Items)
{
yield return Row
(
Label(item.Name),
Label(item.Price, CultureInfo.InvariantCulture),
Label(item.Quantity)
);
}
}
public override IComponent Customize(IComponent component)
{
return component
.WithBottomBorder(BorderType.Thin, Color.Black)
.WithTrailingBorder(BorderType.Thin, Color.Black);
}
}
Use .Render
method on table instance to create a component from model.
var items = new CartItem[]
{
new CartItem("Water", 10, 10),
new CartItem("Bread", 20, 10),
new CartItem("Milk", 30, 10),
new CartItem("Eggs", 40, 10),
};
var model = new CartTableModel(items);
var table = new CartTable();
var tableComponent = table.Render(model);
If you want to customize already scaled component group, you can call a CustomizedWith
modifier on it.
(ex: add a common header for a header group), you can see it's usage in a
student points table example
ForEach(model.HeaderData.Labs, headerData => VStack
(
Label(headerData.Name),
HStack
(
Label("Min"),
Label("Max")
),
HStack
(
Label(headerData.MinPoints, CultureInfo.InvariantCulture),
Label(headerData.MaxPoints, CultureInfo.InvariantCulture)
)
)).CustomizedWith(x => VStack(Label("Labs"), x))
Index and IndexRange labels
As composing components result in component stretching and resizing, there is no deterministic way of addressing components using hard coded indices. Library provides label API, labels are computed in rendering process, so they contain indices and index ranges that are correct relative to stretched and resized components in sheet's grid.
You can get Index or IndexRange of certain component by accessing Index
or Range
property accordingly.
Inline labels
You can apply WithIndexLabel
or WithIndexRangeLabel
modifer which will give you out a label as an out
parameter.
As composing FluentSpreadsheets layouts done as continuous object creation rather then being part of delegate body,
components share same markup context and labels can be used in any components after defining them as out
parameter.
HStack
(
Label("Min"),
Label("Max")
).WithIndexLabel(out var stackLabel)
Label elevation
Even though defining inline labels is pretty handy, they are not applicable in cases, when you need to reference some components in other prior to their definitions. This is when "label elevation" process comes in.
Label elevation is implemented via label proxies, which are created using LabelProxy
static class
and it's Create
and CreateForRange
methods. They create proxies for index labels and index range labels accordingly.
WithIndexLabel
and WithIndexRangeLabel
modifiers have overloads which accept proxies.
var studentNameLabel = LabelProxy.CreateForRange();
Label("Student Name")
.WithColumnWidth(1.7)
.WithTextColor(Color.Red)
.WithTextWrapping()
.WithIndexRangeLabel(studentNameLabel)
To retrieve label from proxy, access it's Label
property
Label(_ => $"# - {studentNameLabel.Label.Range}")
Additionally, you can assign labels to proxies manually using AssignLabel
method.
Limitations
As labels are computed during rendering process, you cannot use them with components that eagerly compute it's content, because at the time of creating the component, label value is unknown.
For example Label(string)
method and string interpolation will result in UnsetLabelException
being thrown.
Label($"# - {studentNameLabel.Label.Range}")
Luckily, there are index aware overloads for these kind of components, which compute their value lazily, only when rendered onto a sheet.
Label(_ => $"# - {studentNameLabel.Label.Range}")
IndexLabel with stretched and resized components
When you apply index label on components that has size larger than (1, 1) after scaling, the top left index will be assigned to the index label
FluentSpreadsheets.ClosedXML
Configuration
Use service collection extension methods to configure ClosedXML driver.
services
.AddFluentSpreadsheets()
.AddClosedXml();
FluentSpreadsheets.GoogleSheets
Configuration
Use service collection extension methods to configure GoogleSheets driver.
services
.AddFluentSpreadsheets()
.AddGoogleSheets(options => options.UseBatching(batchingOptions => batchingOptions.SimultaneousRequestCount = 200);
Batching
When rendering different components on same spreadsheet you can use batching api to reduce api calls.
Use ISheetsServiceBatchScopeFactory
interface and it's CreateScope
method to define batching scope.
public async Task ExecuteRenders(ISheetsServiceBatchScopeFactory scopeFactory)
{
await using var scope = scopeFactory.CreateScope();
// some rendering logic here
}
While the scope is not disposed, all render requests will be batched, only on scope disposal all the necessary GoogleSheets API calls will be executed.
As GoogleSheets API only allow to batch requests by spreadsheet, using batching api will result in any efficiency increase only when you render multiple components on same spreadsheet's sheet(s).
Batching API calls for rendering on different spreadsheets will only result in deferred execution (you might gain some "performance" due to all request being executed simultaneously, reducing sequential wait time).
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. |
.NET Core | netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
.NET Standard | netstandard2.1 is compatible. |
MonoAndroid | monoandroid was computed. |
MonoMac | monomac was computed. |
MonoTouch | monotouch was computed. |
Tizen | tizen60 was computed. |
Xamarin.iOS | xamarinios was computed. |
Xamarin.Mac | xamarinmac was computed. |
Xamarin.TVOS | xamarintvos was computed. |
Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.1
- ClosedXML (>= 0.102.1)
- FluentSpreadsheets (>= 1.6.7)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last updated |
---|---|---|
1.6.7 | 259 | 11/12/2023 |
1.6.6 | 157 | 11/1/2023 |
1.6.5 | 137 | 10/12/2023 |
1.6.4 | 147 | 9/27/2023 |
1.6.3 | 138 | 9/27/2023 |
1.5.1 | 123 | 9/21/2023 |
1.4.4 | 319 | 3/22/2023 |
1.4.3 | 252 | 3/22/2023 |
1.4.2 | 232 | 3/22/2023 |
1.4.1 | 230 | 3/22/2023 |
1.4.0 | 232 | 3/21/2023 |
1.3.1 | 270 | 2/20/2023 |
1.3.0 | 379 | 11/24/2022 |
1.2.4 | 406 | 9/21/2022 |
1.2.3 | 417 | 9/21/2022 |
1.2.2 | 497 | 9/18/2022 |
1.2.1 | 478 | 9/14/2022 |
1.2.0 | 444 | 9/12/2022 |
1.1.0 | 432 | 9/11/2022 |
1.0.1 | 434 | 9/4/2022 |
1.0.0 | 409 | 8/30/2022 |
0.0.3-alpha | 201 | 8/6/2022 |
0.0.2-alpha | 191 | 8/5/2022 |
0.0.1-alpha | 194 | 7/31/2022 |
Added specific renderer