Generar archivos Excel desde backend sigue siendo un caso muy habitual en aplicaciones empresariales: reportes de facturación, listados, exportaciones para administración, conciliaciones, etc.
En este tutorial vas a construir una API REST en .NET que genera archivos .xlsx con EPPlus, usando una estructura sencilla y mantenible.
Actualizado: 20 de febrero de 2026.
Qué vas a construir
Al terminar tendrás:
- Una API ASP.NET Core que expone endpoints
POSTpara generar reportes Excel. - Una capa de servicio (
IExcelService) que encapsula toda la lógica de EPPlus. - Formato de salida en base64 (
data:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;base64,...) listo para frontend. - Una base para crecer a múltiples plantillas de reporte.
Proyecto de ejemplo en GitHub
Si prefieres partir de una implementación ya hecha, aquí tienes el repositorio:
https://github.com/davidcantonnadales/excel-net-api-demo
Requisitos previos
- .NET SDK instalado (en este tutorial usamos
net9.0). - Conocimientos básicos de C# y ASP.NET Core.
- Editor: Visual Studio, Rider o VS Code.
curlo Postman para probar endpoints.
Aviso importante sobre licencia de EPPlus
Antes de escribir código, este punto es crítico:
- El propio equipo de EPPlus indica que desde EPPlus 5 cambiaron de LGPL a Polyform Noncommercial 1.0.0.
- En escenarios comerciales/empresariales, necesitas licencia comercial de EPPlus.
- En este tutorial se usa
EPPlus 6.2.4, por lo que este aviso aplica totalmente.
Resumen práctico:
- Proyecto personal o no comercial: puedes usar contexto/licencia no comercial.
- Proyecto comercial: compra y configura licencia comercial antes de producción.
Nota de versión:
- En
EPPlus 6.2.4se usaExcelPackage.LicenseContext. - En ramas más nuevas (EPPlus 8+), el proyecto oficial marca ese enfoque como obsoleto y usa nueva API de licencia.
Más abajo te dejo fuentes oficiales para validarlo.
Paso 1: Crear la solución y el proyecto Web API
mkdir ExcelApiDemo
cd ExcelApiDemo
dotnet new sln -n ExcelApiDemo
dotnet new webapi -n ExcelApiDemo.Api
dotnet sln add ExcelApiDemo.Api/ExcelApiDemo.Api.csproj
Paso 2: Instalar paquetes NuGet
cd ExcelApiDemo.Api
dotnet add package EPPlus --version 6.2.4
dotnet add package Newtonsoft.Json --version 13.0.3
dotnet add package Swashbuckle.AspNetCore --version 9.0.3
Si quieres validar el .csproj, debería verse parecido a esto:
<ItemGroup>
<PackageReference Include="EPPlus" Version="6.2.4" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="9.0.3" />
</ItemGroup>
Paso 3: Definir modelos de entrada genéricos
Crea un archivo Domain/Reports/ExcelReportRequest.cs:
using Newtonsoft.Json;
namespace ExcelApiDemo.Api.Domain.Reports;
public class ReportItem
{
[JsonProperty("name")]
public string? Name { get; set; }
[JsonProperty("quantity")]
public int Quantity { get; set; }
[JsonProperty("unitPrice")]
public decimal UnitPrice { get; set; }
}
public class ExcelReportRequest
{
[JsonProperty("customerName")]
public string? CustomerName { get; set; }
[JsonProperty("reportDate")]
public string? ReportDate { get; set; }
[JsonProperty("reportNumber")]
public string? ReportNumber { get; set; }
[JsonProperty("items")]
public List<ReportItem> Items { get; set; } = new();
}
La idea es separar claramente:
- Metadatos del reporte (cliente, fecha, número).
- Líneas de detalle (
items).
Paso 4: Crear el contrato del servicio
Crea Domain/Common/IExcelService.cs:
using ExcelApiDemo.Api.Domain.Reports;
namespace ExcelApiDemo.Api.Domain.Common;
public interface IExcelService
{
Task<string> ExportReportAsync(ExcelReportRequest data, string sheetName = "Reporte");
}
Este contrato evita que tu controlador conozca detalles de EPPlus.
Paso 5: Implementar el servicio con EPPlus
Crea Services/ExcelService.cs:
using ExcelApiDemo.Api.Domain.Common;
using ExcelApiDemo.Api.Domain.Reports;
using OfficeOpenXml;
using OfficeOpenXml.Style;
using System.Drawing;
namespace ExcelApiDemo.Api.Services;
public class ExcelService : IExcelService
{
public async Task<string> ExportReportAsync(ExcelReportRequest data, string sheetName = "Reporte")
{
// Para EPPlus v5-v7:
// Uso no comercial. En comercial, configura la licencia adecuada.
ExcelPackage.LicenseContext = LicenseContext.NonCommercial;
using var package = new ExcelPackage();
package.Workbook.Properties.Author = "ExcelApiDemo";
package.Workbook.Worksheets.Add(sheetName);
var ws = package.Workbook.Worksheets[0];
ws.Cells.Style.Font.Size = 11;
ws.Cells.Style.Font.Name = "Calibri";
ws.Cells[1, 1].Value = "REPORTE DE PRODUCTOS";
ws.Cells[2, 1].Value = "Cliente";
ws.Cells[2, 2].Value = data.CustomerName;
ws.Cells[3, 1].Value = "Fecha";
ws.Cells[3, 2].Value = data.ReportDate;
ws.Cells[4, 1].Value = "Nº Reporte";
ws.Cells[4, 2].Value = data.ReportNumber;
var headers = new List<string> { "Producto", "Cantidad", "Precio Unitario", "Total" };
var headerRow = 6;
var col = 1;
foreach (var h in headers)
{
var cell = ws.Cells[headerRow, col];
cell.Value = h.ToUpperInvariant();
cell.Style.Font.Bold = true;
cell.Style.Fill.PatternType = ExcelFillStyle.Solid;
cell.Style.Fill.BackgroundColor.SetColor(Color.LightBlue);
cell.Style.Border.BorderAround(ExcelBorderStyle.Thin);
col++;
}
var row = headerRow + 1;
decimal grandTotal = 0;
foreach (var item in data.Items.OrderBy(x => x.Name))
{
var lineTotal = item.Quantity * item.UnitPrice;
grandTotal += lineTotal;
ws.Cells[row, 1].Value = item.Name;
ws.Cells[row, 2].Value = item.Quantity;
ws.Cells[row, 3].Value = item.UnitPrice;
ws.Cells[row, 4].Value = lineTotal;
row++;
}
ws.Cells[row, 3].Value = "TOTAL";
ws.Cells[row, 3].Style.Font.Bold = true;
ws.Cells[row, 4].Value = grandTotal;
ws.Cells[row, 4].Style.Font.Bold = true;
ws.Cells[1, 1, row, headers.Count].AutoFitColumns();
ws.Cells[headerRow, 1, row - 1, headers.Count].AutoFilter = true;
var bytes = await package.GetAsByteArrayAsync();
return $"data:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;base64,{Convert.ToBase64String(bytes)}";
}
}
Puntos clave del servicio:
- Configura licencia de EPPlus al inicio.
- Mantén estilos y columnas en una sección clara.
- Calcula totales en backend para no depender de frontend.
- Devuelve un
data URIcuando quieras que el cliente descargue directamente.
Paso 6: Crear el controlador REST
Crea Controllers/ReportsController.cs:
using ExcelApiDemo.Api.Domain.Common;
using ExcelApiDemo.Api.Domain.Reports;
using Microsoft.AspNetCore.Mvc;
using System.ComponentModel.DataAnnotations;
namespace ExcelApiDemo.Api.Controllers;
[ApiController]
[Route("reports")]
public class ReportsController : ControllerBase
{
private readonly IExcelService _excelService;
public ReportsController(IExcelService excelService)
{
_excelService = excelService;
}
[HttpPost("excel")]
public async Task<IActionResult> GenerateExcel(
[FromBody][Required] ExcelReportRequest body)
{
var fileBase64 = await _excelService.ExportReportAsync(body);
return Ok(fileBase64);
}
[HttpGet("ping")]
public IActionResult Ping() => Ok(new { ok = true, at = DateTime.UtcNow });
}
Este patrón te da una API limpia:
- El controlador valida y orquesta.
- El servicio genera el Excel.
- El dominio define contratos de entrada/salida.
Paso 7: Registrar servicios en Program.cs
using ExcelApiDemo.Api.Domain.Common;
using ExcelApiDemo.Api.Services;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddScoped<IExcelService, ExcelService>();
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
Paso 8: Ejecutar y probar la API
Compilar:
dotnet restore
dotnet build -c Release
Ejecutar:
dotnet run
Probar health check:
curl http://localhost:5199/reports/ping
Probar generación Excel:
curl -X POST http://localhost:5199/reports/excel \
-H "Content-Type: application/json" \
-d '{
"customerName": "Cliente Demo",
"reportDate": "2026-02-20",
"reportNumber": "RPT-001",
"items": [
{ "name": "Producto A", "quantity": 2, "unitPrice": 10.50 },
{ "name": "Producto B", "quantity": 1, "unitPrice": 25.00 }
]
}'
Recibirás un string base64 largo con prefijo data:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;base64,.
Paso 9: Opciones de salida (base64 vs archivo binario)
En este enfoque se devuelve base64 porque simplifica ciertos frontends web.
Pero también puedes devolver archivo directamente:
return File(bytes,
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"reporte.xlsx");
Cuándo usar cada opción:
- Base64: útil para apps SPA que descargan desde JavaScript.
- Binario (
FileResult): más eficiente en tamaño y memoria para archivos grandes.
Paso 10: Buenas prácticas para producción
- Valida DTOs con
DataAnnotationsy validaciones de negocio. - Añade
CancellationTokenen servicios y controladores. - Controla tamaño máximo de payload para evitar abusos.
- Evita
AutoFitColumnsen reportes enormes (puede impactar rendimiento). - Versiona endpoints (
/v1/reports/excel) cuando el contrato crezca. - Agrega logging estructurado para trazabilidad de exportaciones.
- Define una política clara de licencia EPPlus antes de desplegar.
Cómo adaptar esta estructura a tu proyecto
El mapeo recomendado es:
Program.cs: DI, middleware y Swagger.Controllers/ReportsController.cs: endpoints de exportación.Services/ExcelService.cs: lógica principal de creación del workbook.Domain/Common/IExcelService.cs: contrato de servicio.
Para mantenerlo genérico:
- Renombrar DTOs y endpoints a términos neutrales (
Report,Summary, etc.). - Mantener estructura por capas y contrato de servicio.
- Mover reglas específicas de negocio a otra capa, fuera de la generación Excel.
FAQ rápida
¿EPPlus es gratis?
Depende del uso. Desde la versión 5 cambió de licencia y para uso comercial necesitas licencia comercial.
¿Puedo seguir con EPPlus 4 para evitar coste?
No es buena estrategia a largo plazo: te quedas atrás en soporte y mejoras. Mejor decidir licencia correctamente o evaluar alternativas.
¿Alternativas a EPPlus?
ClosedXML o NPOI son opciones comunes, pero cambia API, rendimiento y capacidades. Evalúa según tus necesidades de formato y licencia.
Fuentes oficiales sobre licencia EPPlus
- EPPlus Licensing (sitio oficial): https://epplussoftware.com/en/LicenseOverview
- NuGet EPPlus (
READMEy metadatos de licencia): https://www.nuget.org/packages/EPPlus - Wiki oficial EPPlus – Getting Started (notas de licencia): https://github.com/EPPlusSoftware/EPPlus/wiki/Getting-Started
Conclusión
Con esta base puedes crear una API .NET robusta para generar Excel y escalar a múltiples reportes, sin acoplarla a una lógica de negocio concreta.
El punto no negociable es la licencia: deja decidido desde el inicio si tu escenario es no comercial o comercial para evitar problemas legales al pasar a producción.
Repositorio de referencia: https://github.com/davidcantonnadales/excel-net-api-demo
Share this content: