Building a Professional Report Export API in ASP.NET Core: Excel, PDF & Word Generation with Template Architecture
Introduction
In modern enterprise applications, the ability to export data into professional reports is essential for business operations, compliance, and decision-making. Whether it’s generating employee reports, financial statements, or operational dashboards, having a robust and scalable report generation system is crucial for any business application.
A Report Export API serves as a centralized solution that can transform your application data into professionally formatted documents across multiple formats. This approach not only ensures consistency in branding and styling but also provides flexibility for different use cases and user preferences.
This comprehensive guide demonstrates how to build a robust Report Export API in ASP.NET Core using a template-based architecture that supports Excel, PDF, and Word formats with professional branding and enterprise-ready features.
Why Report Export APIs are Important
Report export functionality is critical in enterprise applications for several reasons:
- Business Intelligence: Enables stakeholders to analyze data offline and create presentations
- Compliance & Auditing: Provides documentation trails required by regulatory frameworks
- Data Sharing: Facilitates sharing of structured data across different teams and departments
- Professional Presentation: Creates polished, branded reports for client-facing communications
- Backup & Archival: Provides data snapshots for historical reference and backup purposes
- Integration: Enables data exchange with external systems and third-party applications
- User Experience: Gives users flexibility to consume data in their preferred format
What You’ll Learn
In this tutorial, you will learn how to:
- Design a scalable template-based architecture for report generation
- Implement professional Excel reports with EPPlus
- Create PDF documents with iTextSharp
- Generate Word documents with OpenXML
- Add company branding and professional styling
- Implement dependency injection for better testability
- Create reusable templates for different data types
- Build RESTful API endpoints for report downloads
- Handle multiple export formats efficiently
Tools and Technology Used
- ASP.NET Core 8.0: Core framework for building the Web API
- Entity Framework Core: Data access and in-memory database for demo
- EPPlus 7.5.1: Excel file generation and manipulation
- iTextSharp.LGPLv2.Core 3.4.22: PDF document generation
- DocumentFormat.OpenXml 3.2.0: Word document generation
- Swagger/OpenAPI: API documentation and testing
- Visual Studio 2022 or Visual Studio Code: Development environment
- C# 12: Programming language
- SQL Server (optional): Production database
Architecture Overview
Our Report Export API follows a clean, template-based architecture that separates concerns and ensures maintainability:
ExportReportAPI/
├── Controllers/
│ └── EmployeesController.cs # API endpoints
├── Data/
│ ├── ApplicationDbContext.cs # Database context
│ └── DatabaseSeeder.cs # Sample data
├── Generator/
│ ├── Interfaces/
│ │ ├── IReportTemplate.cs # Base template interface
│ │ ├── IExcelTemplate.cs # Excel template interface
│ │ ├── IPDFTemplate.cs # PDF template interface
│ │ └── IWordTemplate.cs # Word template interface
│ └── Templates/
│ ├── ExcelTemplate.cs # Excel implementation
│ ├── PDFTemplate.cs # PDF implementation
│ └── WordTemplate.cs # Word implementation
├── Models/
│ ├── Employee.cs # Data model
│ └── ExportResult.cs # Export result wrapper
├── Services/
│ ├── IExportService.cs # Export service interface
│ └── ExportService.cs # Export service implementation
├── images/
│ └── mahedeedotnet_log.png # Company logo
└── Program.cs # Application configuration
Key Features
- Template-Based Architecture: Reusable templates for different formats
- Professional Branding: Company logo, headers, and consistent styling
- Dependency Injection: Loosely coupled, testable components
- Multiple Export Formats: Excel, PDF, and Word support
- RESTful API Design: Clean, intuitive endpoints
- Enterprise-Ready: Professional formatting and error handling
Step 1: Create ASP.NET Core Web API Project
First, create a new ASP.NET Core Web API project:
Option 1: Using Visual Studio
- Open Visual Studio 2022
- Click “Create a new project”
- Select “ASP.NET Core Web API”
- Name your project “ExportReportAPI”
- Choose “.NET 8.0” as the target framework
- Enable “Use controllers” and “Enable OpenAPI support”
Option 2: Using .NET CLI
dotnet new webapi -n ExportReportAPI
cd ExportReportAPI
Step 2: Install Required NuGet Packages
Install the necessary NuGet packages for report generation:
<PackageReference Include="DocumentFormat.OpenXml" Version="3.2.0" />
<PackageReference Include="EPPlus" Version="7.5.1" />
<PackageReference Include="iTextSharp.LGPLv2.Core" Version="3.4.22" />
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="8.0.11" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
Using Package Manager Console:
Install-Package EPPlus -Version 7.5.1
Install-Package iTextSharp.LGPLv2.Core -Version 3.4.22
Install-Package DocumentFormat.OpenXml -Version 3.2.0
Install-Package Microsoft.EntityFrameworkCore.InMemory -Version 8.0.11
Using .NET CLI:
dotnet add package EPPlus --version 7.5.1
dotnet add package iTextSharp.LGPLv2.Core --version 3.4.22
dotnet add package DocumentFormat.OpenXml --version 3.2.0
dotnet add package Microsoft.EntityFrameworkCore.InMemory --version 8.0.11
Step 3: Create Data Models
Create the core data models for our application.
Models/Employee.cs
namespace ExportReportAPI.Models
{
public class Employee
{
public int Id { get; set; }
public string FullName { get; set; } = string.Empty;
public string Designation { get; set; } = string.Empty;
public string Department { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
public string Phone { get; set; } = string.Empty;
public decimal Salary { get; set; }
public DateTime JoinDate { get; set; }
}
}
Models/ExportResult.cs
namespace ExportReportAPI.Models
{
/// <summary>
/// Represents the result of an export operation
/// </summary>
public class ExportResult
{
public byte[] Data { get; set; } = Array.Empty<byte>();
public string ContentType { get; set; } = string.Empty;
public string FileName { get; set; } = string.Empty;
}
}
Step 4: Setup Database Context and Seeding
Data/ApplicationDbContext.cs
using ExportReportAPI.Models;
using Microsoft.EntityFrameworkCore;
namespace ExportReportAPI.Data
{
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options)
{
}
public DbSet<Employee> Employees { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// Configure Employee entity
modelBuilder.Entity<Employee>(entity =>
{
entity.HasKey(e => e.Id);
entity.Property(e => e.FullName).IsRequired().HasMaxLength(100);
entity.Property(e => e.Designation).IsRequired().HasMaxLength(50);
entity.Property(e => e.Department).IsRequired().HasMaxLength(50);
entity.Property(e => e.Email).IsRequired().HasMaxLength(100);
entity.Property(e => e.Phone).HasMaxLength(20);
entity.Property(e => e.Salary).HasPrecision(18, 2);
});
}
}
}
Data/DatabaseSeeder.cs
using ExportReportAPI.Models;
namespace ExportReportAPI.Data
{
public static class DatabaseSeeder
{
public static void SeedDatabase(ApplicationDbContext context)
{
if (context.Employees.Any())
{
return; // Database already seeded
}
var employees = new List<Employee>
{
new Employee
{
FullName = "John Doe",
Designation = "Software Engineer",
Department = "IT",
Email = "john.doe@company.com",
Phone = "+1-555-0101",
Salary = 75000,
JoinDate = DateTime.Parse("2022-01-15")
},
new Employee
{
FullName = "Jane Smith",
Designation = "Senior Developer",
Department = "IT",
Email = "jane.smith@company.com",
Phone = "+1-555-0102",
Salary = 85000,
JoinDate = DateTime.Parse("2021-03-10")
},
new Employee
{
FullName = "Michael Johnson",
Designation = "Project Manager",
Department = "PMO",
Email = "michael.johnson@company.com",
Phone = "+1-555-0103",
Salary = 95000,
JoinDate = DateTime.Parse("2020-07-22")
},
new Employee
{
FullName = "Sarah Wilson",
Designation = "UI/UX Designer",
Department = "Design",
Email = "sarah.wilson@company.com",
Phone = "+1-555-0104",
Salary = 70000,
JoinDate = DateTime.Parse("2022-09-05")
},
new Employee
{
FullName = "David Brown",
Designation = "DevOps Engineer",
Department = "IT",
Email = "david.brown@company.com",
Phone = "+1-555-0105",
Salary = 80000,
JoinDate = DateTime.Parse("2021-11-18")
}
};
context.Employees.AddRange(employees);
context.SaveChanges();
}
}
}
Step 5: Create Template Interfaces
Design the template interfaces that define the contract for each export format.
Generator/Interfaces/IReportTemplate.cs
using ExportReportAPI.Models;
namespace ExportReportAPI.Generator.Interfaces
{
/// <summary>
/// Base interface for all report templates
/// </summary>
public interface IReportTemplate<T>
{
Task<ExportResult> GenerateReportAsync(IEnumerable<T> data, string reportTitle = "Report", Dictionary<string, object>? additionalInfo = null);
}
}
Generator/Interfaces/IExcelTemplate.cs
using ExportReportAPI.Models;
namespace ExportReportAPI.Generator.Interfaces
{
/// <summary>
/// Interface for Excel report generation
/// </summary>
public interface IExcelTemplate : IReportTemplate<Employee>
{
Task<ExportResult> GenerateExcelReportAsync(IEnumerable<Employee> employees, string reportTitle = "Employee Report", Dictionary<string, object>? additionalInfo = null);
}
}
Generator/Interfaces/IPDFTemplate.cs
using ExportReportAPI.Models;
namespace ExportReportAPI.Generator.Interfaces
{
/// <summary>
/// Interface for PDF report generation
/// </summary>
public interface IPDFTemplate : IReportTemplate<Employee>
{
Task<ExportResult> GeneratePDFReportAsync(IEnumerable<Employee> employees, string reportTitle = "Employee Report", Dictionary<string, object>? additionalInfo = null);
}
}
Generator/Interfaces/IWordTemplate.cs
using ExportReportAPI.Models;
namespace ExportReportAPI.Generator.Interfaces
{
/// <summary>
/// Interface for Word document generation
/// </summary>
public interface IWordTemplate : IReportTemplate<Employee>
{
Task<ExportResult> GenerateWordReportAsync(IEnumerable<Employee> employees, string reportTitle = "Employee Report", Dictionary<string, object>? additionalInfo = null);
}
}
Step 6: Implement Excel Template
Create a professional Excel template with company branding and styling.
Generator/Templates/ExcelTemplate.cs
using ExportReportAPI.Generator.Interfaces;
using ExportReportAPI.Models;
using OfficeOpenXml;
namespace ExportReportAPI.Generator.Templates
{
/// <summary>
/// Professional Excel report template with company branding
/// </summary>
public class ExcelTemplate : IExcelTemplate
{
private readonly string _logoPath;
public ExcelTemplate()
{
_logoPath = Path.Combine(AppContext.BaseDirectory, "images", "mahedeedotnet_log.png");
}
public async Task<ExportResult> GenerateReportAsync(IEnumerable<Employee> data, string reportTitle = "Report", Dictionary<string, object>? additionalInfo = null)
{
return await GenerateExcelReportAsync(data, reportTitle, additionalInfo);
}
public async Task<ExportResult> GenerateExcelReportAsync(IEnumerable<Employee> employees, string reportTitle = "Employee Report", Dictionary<string, object>? additionalInfo = null)
{
using var package = new ExcelPackage();
var worksheet = package.Workbook.Worksheets.Add("Employees");
int currentRow = 1;
// Add company header section
currentRow = AddCompanyHeader(worksheet, currentRow);
// Add report title
currentRow = AddReportTitle(worksheet, reportTitle, currentRow);
// Add additional info if provided
if (additionalInfo != null && additionalInfo.Any())
{
currentRow = AddAdditionalInfo(worksheet, additionalInfo, currentRow);
}
// Add data table
currentRow = AddDataTable(worksheet, employees, currentRow);
// Add footer
AddFooter(worksheet, currentRow);
// Apply styling and formatting
ApplyWorksheetFormatting(worksheet);
var result = new ExportResult
{
Data = package.GetAsByteArray(),
ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
FileName = $"{reportTitle.Replace(" ", "_")}_{DateTime.Now:yyyyMMdd_HHmmss}.xlsx"
};
return await Task.FromResult(result);
}
private int AddCompanyHeader(ExcelWorksheet worksheet, int startRow)
{
// Add company logo
if (File.Exists(_logoPath))
{
var picture = worksheet.Drawings.AddPicture("CompanyLogo", new FileInfo(_logoPath));
picture.SetPosition(0, 0, 0, 0);
picture.SetSize(150, 75);
}
int currentRow = startRow + 4; // Leave space for logo
// Company name
worksheet.Cells[currentRow, 1].Value = "Mahedee.net";
worksheet.Cells[currentRow, 1].Style.Font.Bold = true;
worksheet.Cells[currentRow, 1].Style.Font.Size = 16;
worksheet.Cells[currentRow, 1, currentRow, 8].Merge = true;
worksheet.Cells[currentRow, 1].Style.HorizontalAlignment = OfficeOpenXml.Style.ExcelHorizontalAlignment.Center;
currentRow++;
// Company slogan
worksheet.Cells[currentRow, 1].Value = "Think Simple";
worksheet.Cells[currentRow, 1].Style.Font.Size = 12;
worksheet.Cells[currentRow, 1, currentRow, 8].Merge = true;
worksheet.Cells[currentRow, 1].Style.HorizontalAlignment = OfficeOpenXml.Style.ExcelHorizontalAlignment.Center;
currentRow++;
// Company address
worksheet.Cells[currentRow, 1].Value = "Toronto, ON, Canada";
worksheet.Cells[currentRow, 1].Style.Font.Size = 10;
worksheet.Cells[currentRow, 1, currentRow, 8].Merge = true;
worksheet.Cells[currentRow, 1].Style.HorizontalAlignment = OfficeOpenXml.Style.ExcelHorizontalAlignment.Center;
currentRow += 2;
return currentRow;
}
private int AddReportTitle(ExcelWorksheet worksheet, string title, int startRow)
{
worksheet.Cells[startRow, 1].Value = title;
worksheet.Cells[startRow, 1].Style.Font.Bold = true;
worksheet.Cells[startRow, 1].Style.Font.Size = 14;
worksheet.Cells[startRow, 1, startRow, 8].Merge = true;
worksheet.Cells[startRow, 1].Style.HorizontalAlignment = OfficeOpenXml.Style.ExcelHorizontalAlignment.Center;
return startRow + 2;
}
private int AddAdditionalInfo(ExcelWorksheet worksheet, Dictionary<string, object> additionalInfo, int startRow)
{
foreach (var info in additionalInfo)
{
worksheet.Cells[startRow, 1].Value = $"{info.Key}:";
worksheet.Cells[startRow, 1].Style.Font.Bold = true;
worksheet.Cells[startRow, 2].Value = info.Value?.ToString();
startRow++;
}
return startRow + 1;
}
private int AddDataTable(ExcelWorksheet worksheet, IEnumerable<Employee> employees, int startRow)
{
// Add headers
string[] headers = { "ID", "Full Name", "Designation", "Department", "Email", "Phone", "Salary", "Join Date" };
for (int i = 0; i < headers.Length; i++)
{
worksheet.Cells[startRow, i + 1].Value = headers[i];
}
// Style headers
using (var range = worksheet.Cells[startRow, 1, startRow, headers.Length])
{
range.Style.Font.Bold = true;
range.Style.Fill.PatternType = OfficeOpenXml.Style.ExcelFillStyle.Solid;
range.Style.Fill.BackgroundColor.SetColor(System.Drawing.Color.LightBlue);
range.Style.Border.BorderAround(OfficeOpenXml.Style.ExcelBorderStyle.Thin);
}
startRow++;
// Add data
var employeeList = employees.ToList();
for (int i = 0; i < employeeList.Count; i++)
{
var emp = employeeList[i];
worksheet.Cells[startRow + i, 1].Value = emp.Id;
worksheet.Cells[startRow + i, 2].Value = emp.FullName;
worksheet.Cells[startRow + i, 3].Value = emp.Designation;
worksheet.Cells[startRow + i, 4].Value = emp.Department;
worksheet.Cells[startRow + i, 5].Value = emp.Email;
worksheet.Cells[startRow + i, 6].Value = emp.Phone;
worksheet.Cells[startRow + i, 7].Value = emp.Salary;
worksheet.Cells[startRow + i, 7].Style.Numberformat.Format = "$#,##0.00";
worksheet.Cells[startRow + i, 8].Value = emp.JoinDate.ToString("yyyy-MM-dd");
// Add borders
using (var range = worksheet.Cells[startRow + i, 1, startRow + i, headers.Length])
{
range.Style.Border.BorderAround(OfficeOpenXml.Style.ExcelBorderStyle.Thin);
}
}
return startRow + employeeList.Count + 2;
}
private void AddFooter(ExcelWorksheet worksheet, int startRow)
{
worksheet.Cells[startRow, 1].Value = $"Generated on: {DateTime.Now:yyyy-MM-dd HH:mm}";
worksheet.Cells[startRow, 1, startRow, 8].Merge = true;
worksheet.Cells[startRow, 1].Style.Font.Size = 9;
worksheet.Cells[startRow, 1].Style.HorizontalAlignment = OfficeOpenXml.Style.ExcelHorizontalAlignment.Center;
worksheet.Cells[startRow, 1].Style.Font.Italic = true;
}
private void ApplyWorksheetFormatting(ExcelWorksheet worksheet)
{
// Auto-fit columns
worksheet.Cells.AutoFitColumns();
// Set minimum column widths
for (int col = 1; col <= 8; col++)
{
if (worksheet.Column(col).Width < 10)
{
worksheet.Column(col).Width = 10;
}
}
}
}
}
Step 7: Create Export Service
Create a service that orchestrates the different template implementations.
Services/IExportService.cs
using ExportReportAPI.Models;
namespace ExportReportAPI.Services
{
public interface IExportService
{
Task<ExportResult> ExportToExcelAsync(IEnumerable<Employee> employees, string reportTitle = "Employee Report", Dictionary<string, object>? additionalInfo = null);
Task<ExportResult> ExportToPdfAsync(IEnumerable<Employee> employees, string reportTitle = "Employee Report", Dictionary<string, object>? additionalInfo = null);
Task<ExportResult> ExportToWordAsync(IEnumerable<Employee> employees, string reportTitle = "Employee Report", Dictionary<string, object>? additionalInfo = null);
}
}
Services/ExportService.cs
using ExportReportAPI.Generator.Interfaces;
using ExportReportAPI.Models;
namespace ExportReportAPI.Services
{
/// <summary>
/// Main export service that coordinates different template implementations
/// </summary>
public class ExportService : IExportService
{
private readonly IExcelTemplate _excelTemplate;
private readonly IPDFTemplate _pdfTemplate;
private readonly IWordTemplate _wordTemplate;
public ExportService(
IExcelTemplate excelTemplate,
IPDFTemplate pdfTemplate,
IWordTemplate wordTemplate)
{
_excelTemplate = excelTemplate;
_pdfTemplate = pdfTemplate;
_wordTemplate = wordTemplate;
}
public async Task<ExportResult> ExportToExcelAsync(IEnumerable<Employee> employees, string reportTitle = "Employee Report", Dictionary<string, object>? additionalInfo = null)
{
return await _excelTemplate.GenerateExcelReportAsync(employees, reportTitle, additionalInfo);
}
public async Task<ExportResult> ExportToPdfAsync(IEnumerable<Employee> employees, string reportTitle = "Employee Report", Dictionary<string, object>? additionalInfo = null)
{
return await _pdfTemplate.GeneratePDFReportAsync(employees, reportTitle, additionalInfo);
}
public async Task<ExportResult> ExportToWordAsync(IEnumerable<Employee> employees, string reportTitle = "Employee Report", Dictionary<string, object>? additionalInfo = null)
{
return await _wordTemplate.GenerateWordReportAsync(employees, reportTitle, additionalInfo);
}
}
}
Step 8: Create API Controller
Implement the RESTful API endpoints for report generation.
Controllers/EmployeesController.cs
using ExportReportAPI.Data;
using ExportReportAPI.Models;
using ExportReportAPI.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace ExportReportAPI.Controllers
{
[ApiController]
[Route("api/[controller]")]
public class EmployeesController : ControllerBase
{
private readonly ApplicationDbContext _context;
private readonly IExportService _exportService;
public EmployeesController(ApplicationDbContext context, IExportService exportService)
{
_context = context;
_exportService = exportService;
}
/// <summary>
/// Get all employees
/// </summary>
/// <returns>List of employees</returns>
[HttpGet]
public async Task<ActionResult<IEnumerable<Employee>>> GetEmployees()
{
return await _context.Employees.ToListAsync();
}
/// <summary>
/// Get employee by ID
/// </summary>
/// <param name="id">Employee ID</param>
/// <returns>Employee details</returns>
[HttpGet("{id}")]
public async Task<ActionResult<Employee>> GetEmployee(int id)
{
var employee = await _context.Employees.FindAsync(id);
if (employee == null)
{
return NotFound();
}
return employee;
}
/// <summary>
/// Export employees to Excel format
/// </summary>
/// <returns>Excel file download</returns>
[HttpGet("export/excel")]
public async Task<IActionResult> ExportToExcel()
{
var employees = await _context.Employees.ToListAsync();
var additionalInfo = new Dictionary<string, object>
{
{ "Report Type", "Employee Directory" },
{ "Total Records", employees.Count },
{ "Generated By", "Export Report API" }
};
var result = await _exportService.ExportToExcelAsync(employees, "Employee Directory Report", additionalInfo);
return File(result.Data, result.ContentType, result.FileName);
}
/// <summary>
/// Export employees to PDF format
/// </summary>
/// <returns>PDF file download</returns>
[HttpGet("export/pdf")]
public async Task<IActionResult> ExportToPdf()
{
var employees = await _context.Employees.ToListAsync();
var additionalInfo = new Dictionary<string, object>
{
{ "Report Type", "Employee Directory" },
{ "Total Records", employees.Count },
{ "Generated By", "Export Report API" }
};
var result = await _exportService.ExportToPdfAsync(employees, "Employee Directory Report", additionalInfo);
return File(result.Data, result.ContentType, result.FileName);
}
/// <summary>
/// Export employees to Word format
/// </summary>
/// <returns>Word document file download</returns>
[HttpGet("export/word")]
public async Task<IActionResult> ExportToWord()
{
var employees = await _context.Employees.ToListAsync();
var additionalInfo = new Dictionary<string, object>
{
{ "Report Type", "Employee Directory" },
{ "Total Records", employees.Count },
{ "Generated By", "Export Report API" }
};
var result = await _exportService.ExportToWordAsync(employees, "Employee Directory Report", additionalInfo);
return File(result.Data, result.ContentType, result.FileName);
}
/// <summary>
/// Create a new employee
/// </summary>
/// <param name="employee">Employee data</param>
/// <returns>Created employee</returns>
[HttpPost]
public async Task<ActionResult<Employee>> PostEmployee(Employee employee)
{
_context.Employees.Add(employee);
await _context.SaveChangesAsync();
return CreatedAtAction("GetEmployee", new { id = employee.Id }, employee);
}
/// <summary>
/// Update an existing employee
/// </summary>
/// <param name="id">Employee ID</param>
/// <param name="employee">Updated employee data</param>
/// <returns>No content on success</returns>
[HttpPut("{id}")]
public async Task<IActionResult> PutEmployee(int id, Employee employee)
{
if (id != employee.Id)
{
return BadRequest();
}
_context.Entry(employee).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!EmployeeExists(id))
{
return NotFound();
}
else
{
throw;
}
}
return NoContent();
}
/// <summary>
/// Delete an employee
/// </summary>
/// <param name="id">Employee ID</param>
/// <returns>No content on success</returns>
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteEmployee(int id)
{
var employee = await _context.Employees.FindAsync(id);
if (employee == null)
{
return NotFound();
}
_context.Employees.Remove(employee);
await _context.SaveChangesAsync();
return NoContent();
}
private bool EmployeeExists(int id)
{
return _context.Employees.Any(e => e.Id == id);
}
}
}
Step 9: Configure Application Startup
Configure dependency injection and application settings.
Program.cs
using ExportReportAPI.Data;
using ExportReportAPI.Generator.Interfaces;
using ExportReportAPI.Generator.Templates;
using ExportReportAPI.Models;
using ExportReportAPI.Services;
using Microsoft.EntityFrameworkCore;
using System.Reflection;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseInMemoryDatabase("EmployeeDb"));
// Register template services
builder.Services.AddScoped<IExcelTemplate, ExcelTemplate>();
builder.Services.AddScoped<IPDFTemplate, PDFTemplate>();
builder.Services.AddScoped<IWordTemplate, WordTemplate>();
// Register main export service
builder.Services.AddScoped<IExportService, ExportService>();
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new() {
Title = "Export Report API",
Version = "v1",
Description = "ASP.NET Core Web API for exporting reports in multiple formats (Excel, PDF, Word)"
});
// Include XML comments for better documentation
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
if (File.Exists(xmlPath))
{
c.IncludeXmlComments(xmlPath);
}
});
// Configure EPPlus license
OfficeOpenXml.ExcelPackage.LicenseContext = OfficeOpenXml.LicenseContext.NonCommercial;
var app = builder.Build();
// Seed database with sample data
using (var scope = app.Services.CreateScope())
{
var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
DatabaseSeeder.SeedDatabase(context);
}
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "Export Report API V1");
c.RoutePrefix = string.Empty; // Set Swagger UI at the app's root
});
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
Step 10: Update Project Configuration
Update the project file to include proper settings and logo copying.
ExportReportAPI.csproj
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);1591</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="DocumentFormat.OpenXml" Version="3.2.0" />
<PackageReference Include="EPPlus" Version="7.5.1" />
<PackageReference Include="iTextSharp.LGPLv2.Core" Version="3.4.22" />
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="8.0.11" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
</ItemGroup>
<ItemGroup>
<Content Include="images\**">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project>
Step 11: Add Company Logo
- Create an
images
folder in your project root - Add your company logo file (e.g.,
mahedeedotnet_log.png
) - The logo will be automatically included in the reports
Folder Structure:
ExportReportAPI/
├── images/
│ └── mahedeedotnet_log.png
Step 12: Run and Test the Application
- Build and Run the Application:
dotnet build
dotnet run
- Access Swagger UI:
- Open your browser and navigate to
https://localhost:7xxx
(port will vary) - You’ll see the Swagger UI with all available endpoints
- Open your browser and navigate to
- Test Export Endpoints:
- Excel Export:
GET /api/employees/export/excel
- PDF Export:
GET /api/employees/export/pdf
- Word Export:
GET /api/employees/export/word
- Excel Export:
- Test with Sample Data:
- The application automatically seeds sample employee data
- You can also add/modify employees through the CRUD endpoints
Key Features Implemented
1. Template-Based Architecture
- Reusable Templates: Each format has its own dedicated template class
- Interface-Driven Design: Clear contracts for different export formats
- Dependency Injection: Loosely coupled components for better testability
2. Professional Branding
- Company Logo: Automatically embedded in all report formats
- Consistent Headers: Company name, slogan, and address
- Professional Styling: Colors, fonts, and layout consistency
3. Multi-Format Support
- Excel: Rich formatting with EPPlus
- PDF: Professional layout with iTextSharp
- Word: Structured documents with OpenXML
4. Enterprise Features
- Custom Titles: Dynamic report titles
- Additional Information: Support for metadata and extra details
- Timestamp Footers: Generation timestamps and page numbers
- Error Handling: Robust error handling and validation
5. API Design
- RESTful Endpoints: Clean, intuitive API design
- Swagger Documentation: Comprehensive API documentation
- File Downloads: Proper file response handling
Benefits of This Architecture
1. Maintainability
- Each report format has its own dedicated class
- Clear separation of concerns
- Easy to modify individual formats without affecting others
2. Reusability
- Templates can be reused for different data types
- Common branding and styling logic is encapsulated
- Easy to create new report types using existing templates
3. Testability
- Each template can be unit tested independently
- Mock dependencies easily for testing
- Clean interfaces make testing straightforward
4. Extensibility
- Easy to add new report formats (e.g., CSV, JSON)
- Simple to extend existing templates with new features
- Support for different data types beyond Employee
5. Performance
- Efficient memory usage with proper disposal
- Streaming for large datasets
- Asynchronous operations for better scalability
Advanced Usage Examples
Custom Report with Additional Information
[HttpGet("export/excel/custom")]
public async Task<IActionResult> ExportCustomExcel()
{
var employees = await _context.Employees
.Where(e => e.Department == "IT")
.ToListAsync();
var additionalInfo = new Dictionary<string, object>
{
{ "Department Filter", "IT Department Only" },
{ "Report Period", "Q4 2025" },
{ "Generated By", "John Doe" },
{ "Total Records", employees.Count },
{ "Average Salary", employees.Average(e => e.Salary).ToString("C") }
};
var result = await _exportService.ExportToExcelAsync(
employees,
"IT Department Employee Report",
additionalInfo
);
return File(result.Data, result.ContentType, result.FileName);
}
Direct Template Usage
public class CustomReportService
{
private readonly IExcelTemplate _excelTemplate;
public CustomReportService(IExcelTemplate excelTemplate)
{
_excelTemplate = excelTemplate;
}
public async Task<ExportResult> GenerateMonthlyReport(IEnumerable<Employee> employees)
{
var customInfo = new Dictionary<string, object>
{
{ "Report Type", "Monthly Employee Report" },
{ "Period", DateTime.Now.ToString("MMMM yyyy") },
{ "Department Count", employees.GroupBy(e => e.Department).Count() }
};
return await _excelTemplate.GenerateExcelReportAsync(
employees,
"Monthly Employee Report",
customInfo
);
}
}
Future Enhancements
The template-based architecture enables easy implementation of:
1. Additional Format Support
- CSV export for data analysis
- JSON export for API integration
- XML export for legacy systems
2. Advanced Features
- Charts and graphs integration
- Multiple worksheets/pages
- Dynamic template selection
- Custom report layouts
3. Enterprise Features
- Multi-language support
- Theme-based styling
- User-specific branding
- Report scheduling and automation
4. Performance Optimizations
- Streaming for large datasets
- Background processing
- Caching for frequently generated reports
- Compression for large files
Conclusion
This comprehensive guide demonstrated how to build a professional Report Export API in ASP.NET Core using a template-based architecture. The solution provides:
- Multiple Export Formats: Excel, PDF, and Word support
- Professional Branding: Consistent company branding across all formats
- Scalable Architecture: Template-based design for easy extension
- Enterprise-Ready Features: Professional styling, error handling, and performance optimization
- Clean API Design: RESTful endpoints with comprehensive documentation
The template-based approach ensures that your report generation system is maintainable, testable, and easily extensible for future requirements. Whether you’re building internal business applications or client-facing systems, this architecture provides a solid foundation for professional report generation.
The complete source code provides a working example that you can customize and extend based on your specific business requirements. The separation of concerns and dependency injection make it easy to add new features, modify existing templates, or integrate with different data sources.
Comments