Using native AOT
Starting with FlexCel 7.23, we support .NET Native AOT, when using .NET 9.0. Native AOT (Ahead-of-Time) compilation allows you to compile your .NET applications into fully self-contained native executables, improving startup time and reducing runtime dependencies. However, there are some important considerations when using FlexCel with Native AOT.
Supported Features in FlexCel under .NET Native AOT
1. Excel File Manipulation:
- Creating, reading, and modifying Excel files is fully supported.
- Classes like XlsFile, and methods such as SetCellValue, GetCellValue, and Save work seamlessly under Native AOT.
2. PDF, HTML, SVG and Image Rendering:
- Rendering Excel files to PDF or images is fully supported.
- Classes like FlexCelPdfExport and FlexCelImgExport function as expected.
3. Core FlexCel Features:
- All non-report-related functionality, including working with formulas, charts, and cell formatting, is supported.
Partially Supported Features: Reporting
The FlexCel reporting engine is not fully supported under .NET Native AOT. Reports rely on runtime reflection, which may not work properly in Native AOT because of its aggressive trimming and because runtime-code generation is unavailable.
Issues with Reports
The reporting engine works with two kinds of data sources. DataSets and IQueryable objects like for example a List
DataSets do not completely support AOT. Simple stuff will work, but for example, filtering by a column might not. This is also constantly changing: What doesn't work today might work tomorrow with newer .NET releases. So we can't provide a list of what works and what doesn't, but simple stuff should surely work. LINQ is also not fully supported in AOT, and we also use Reflection to get the names of the fields. Still, most simple reports using LINQ will work, but there is an important thing to take into account: When using AOT, the linker aggressively removes classes and methods that aren't used. As FlexCelReport reads the properties via reflection, the classes you pass to FlexCelReport.AddTable must be annotated with the DynamicallyAccessedMembers attribute. For example: Besides the issues with the two data sources we support, there are also a couple of things like sorting or filtering in the template, that won't work with AOT. Due to the dynamic nature of the reports, we can't know if a certain template is compatible or not with AOT before running the report, but if you keep them simple and annotate the classes you pass to AddTable, they should work. FlexCelReport uses warnings to let you know its limitations. Look at the example on this page for more information. 1. Use .NET 9 or Later: 2. Check Reports: 3. Test Your Application: In this section, we will construct a Native AOT app using reports from scratch, and analyze what is happening. 1. Create a new .NET console app, and name it native-aot. 2. In the nuget package manager, add FlexCel. 3. Type the following code into Program.cs: 4. Create a file in Excel: 5. Make sure 6. At the time of this writing, you can't run the app from Visual Studio. So open a console window, Among others, you should see the following warnings: This is us telling you that the reports might not work as expected. FlexCelReports is in itself a "compiler" that compiles and run a template written in Excel, so we can't give you more detailed warnings than this. We don't know which features your template will use. And the only way to find out, is to run the report and see if it works. If a report doesn't work, it will just crash. So there is no need to test for bugs in the generated files: If the report runs, the results are correct. To see all the warnings, you need to compile from the command line, using So let's try it! If everything went according to the plan, the app should run without issues. Done? Not really. There is some more stuff worth investigating.
If you remember, we told you above to annotate your classes with DynamicallyAccessedMembers. But in this example we didn't, and the report run correctly. How did this happen? We specifically used a property, "UpperName" which isn't used anywhere, and so it should have been trimmed: Still, it worked. What happens is that we called FlexCelReport.AddTable, the method itself is annotated with DynamicallyAccessedMembers, and so the trimmer knows that it shouldn't remove the property. If we removed that attribute from AddTable, we would get the following error: But we can't always trust the code analyzer to figure it out by itself. In a more complex setting, for example if your code is in an assembly that is called by another one, the code analyzer might fail to discover that the property shouldn't be trimmed. So to be safe make sure to always annotate the classes you pass to FlexCelReport with DynamicallyAccessedMembers The code below is how we should have written the app:
Important
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods
| DynamicallyAccessedMemberTypes.PublicProperties)]
class Customer
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
...
Report.AddTable(CustomerList); //Customer is annotated to preserve both private and public properties.
Note
Best Practices for Using FlexCel with .NET Native AOT
Example
using FlexCel.Report;
var Customers = new List<Customer>
{
new() { Name = "Microsoft", Address = "1 Microsoft Way, Redmond, WA 98052, US" },
new() { Name = "Apple", Address = "Apple One Apple Park Way Cupertino, CA 95014" },
new() { Name = "Google", Address = "1600 Amphitheatre Parkway in Mountain View, California" }
};
var report = new FlexCelReport(true);
report.AddTable("customers", Customers);
report.Run(Path.Combine(AppContext.BaseDirectory, @"..\..\..\..\..\template.xlsx"),
Path.Combine(AppContext.BaseDirectory, @"..\..\..\..\..\result.xlsx"));
class Customer
{
public required string Name { get; set; }
public string UpperName => Name.ToUpper();
public required string Address { get; set; }
}
<#customers.uppername>
<customers.address>
__Customers__
and select =Sheet1!$A$1 in the "Refers to" field.PublishAOT
is true in your project options:<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<PublishAot>true</PublishAot>
</PropertyGroup>
</Project>
cd <folder where Programs.cs is>
and type dotnet publish -r win-x64 -c Release .\native-aot.csproj
warning IL2026: Using member 'FlexCel.Report.FlexCelReport.FlexCelReport(Boolean)' which has 'RequiresUnreferencedCodeAttribute' can break functionality when trimming application code. FlexCelReport is not fully compatible with Native AOT. See https://doc.tmssoftware.com/flexcel/net/tips/native-aot.html
warning IL3050: Using member 'FlexCel.Report.FlexCelReport.FlexCelReport(Boolean)' which has 'RequiresDynamicCodeAttribute' can break functionality when AOT compiling. FlexCelReport is not fully compatible with Native AOT. See https://doc.tmssoftware.com/flexcel/net/tips/native-aot.html.
Note
Important
dotnet publish
. While Visual Studio
will show you some warnings, you can never be sure you are seeing them all.Unhandled exception. FlexCel.Core.FlexCelCoreException: Error in cell Sheet1!A1: "Column "uppername" does not exist on table "customers""
---> FlexCel.Core.FlexCelCoreException: Column "uppername" does not exist on table "customers"
at FlexCel.Core.FlxMessages.ThrowException(FlxErr, Object[]) + 0x3b
at FlexCel.Report.TSectionDataSet.Create(ExcelFile, TStackData, Int32, TOneCellValue, TRichString, TBand, Int32, FlexCelReport, Boolean) + 0x6c3
using FlexCel.Report;
using System.Diagnostics.CodeAnalysis;
var Customers = new List<Customer>
{
new() { Name = "Microsoft", Address = "1 Microsoft Way, Redmond, WA 98052, US" },
new() { Name = "Apple", Address = "Apple One Apple Park Way Cupertino, CA 95014" },
new() { Name = "Google", Address = "1600 Amphitheatre Parkway in Mountain View, California" }
};
var report = new FlexCelReport(true);
report.AddTable("customers", Customers);
report.Run(Path.Combine(AppContext.BaseDirectory, @"..\..\..\..\..\template.xlsx"),
Path.Combine(AppContext.BaseDirectory, @"..\..\..\..\..\result.xlsx"));
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods
| DynamicallyAccessedMemberTypes.PublicProperties)]
class Customer
{
public required string Name { get; set; }
public string UpperName => Name.ToUpper();
public required string Address { get; set; }
}