Building a Professional Report Export API in ASP.NET Core: Excel, PDF & Word Generation with Template Architecture

17 minute read

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

  1. Open Visual Studio 2022
  2. Click “Create a new project”
  3. Select “ASP.NET Core Web API”
  4. Name your project “ExportReportAPI”
  5. Choose “.NET 8.0” as the target framework
  6. 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>

  1. Create an images folder in your project root
  2. Add your company logo file (e.g., mahedeedotnet_log.png)
  3. The logo will be automatically included in the reports

Folder Structure:

ExportReportAPI/
├── images/
│   └── mahedeedotnet_log.png

Step 12: Run and Test the Application

  1. Build and Run the Application:
dotnet build
dotnet run
  1. 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
  2. Test Export Endpoints:
    • Excel Export: GET /api/employees/export/excel
    • PDF Export: GET /api/employees/export/pdf
    • Word Export: GET /api/employees/export/word
  3. 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.

Complete Source Code Available

Comments