- README.md: full API docs, architecture, code samples, deployment guide - Syncfusion XlsIORenderer based conversion - ASP.NET Core 8.0 service - Code signing info: SPM.NewAPI.exe signed with Azure Trusted Signing
11 KiB
📄 Excel to PDF Export Service
ASP.NET Core API service xuất file Excel sang PDF sử dụng Syncfusion XlsIORenderer — tích hợp trong dự án SPM.NewAPI.
Mục lục
Tổng quan
ExcelExportServices (trong SPM.NewAPI) cung cấp chức năng:
- ✅ Chuyển đổi file Excel (
.xlsx) sang PDF với chất lượng cao - ✅ Tự động phát hiện vùng in (Print Area) hoặc dùng toàn bộ dữ liệu
- ✅ Fit-to-page layout (
FitToPagesWide = 1) - ✅ Auto-cleanup file sau 10 phút
- ✅ REST API endpoints để tích hợp frontend
Tech stack:
| Thành phần | Version |
|---|---|
| ASP.NET Core | 8.0 |
| Syncfusion.XlsIO.Net.Core | 21.1.37 |
| Syncfusion.Pdf.Net.Core | 21.1.37 |
| Syncfusion.XlsIORenderer.Net.Core | 21.1.37 |
Kiến trúc
HTTP Request
│
▼
ExcelToPdfController
│
├── GET /export-excel/{packageId} → ExcelExportServices.ExportDeliveryNoteToExcel()
│ └── Tạo file .xlsx từ template
│
├── GET /export-pdf/{packageId} → ExcelExportServices.ExportDeliveryNoteToPdf()
│ ├── Tạo file .xlsx
│ ├── ConvertExcelToPdf()
│ └── Lưu file, auto-delete sau 10 phút
│
└── POST /convert-excel-to-pdf → ExcelExportServices.ConvertExcelToPdf()
├── XlsIORenderer.ConvertToPDF()
└── Trả về MemoryStream
Cài đặt
1. Package Dependencies
Thêm vào SPM.NewAPI.csproj:
<PackageReference Include="Syncfusion.XlsIO.Net.Core" Version="21.1.37" />
<PackageReference Include="Syncfusion.Pdf.Net.Core" Version="21.1.37" />
<PackageReference Include="Syncfusion.XlsIORenderer.Net.Core" Version="21.1.37" />
2. Syncfusion License
Thêm vào Program.cs:
Syncfusion.Licensing.SyncfusionLicenseProvider.RegisterLicense("YOUR_LICENSE_KEY");
3. Service Registration (DI)
// Program.cs
builder.Services.AddScoped<IExcelExportServices, ExcelExportServices>();
4. Interface
public interface IExcelExportServices
{
MemoryStream ConvertExcelToPdf(string excelFilePath);
MemoryStream ConvertExcelToPdf(string excelFilePath, string printArea);
Task<string> ExportDeliveryNoteToPdf(Guid pkgpackageid, string TemplateType);
}
5. Implementation chính
public MemoryStream ConvertExcelToPdf(string excelFilePath, string printArea = null)
{
using var excelEngine = new ExcelEngine();
var application = excelEngine.Excel;
application.DefaultVersion = ExcelVersion.Xlsx;
using var fileStream = new FileStream(excelFilePath, FileMode.Open, FileAccess.Read);
var workbook = application.Workbooks.Open(fileStream);
var worksheet = workbook.Worksheets[0];
// Thiết lập layout
var settings = new XlsIORendererSettings
{
LayoutOptions = LayoutOptions.Automatic
};
// Fit to width
worksheet.PageSetup.FitToPagesWide = 1;
// Áp dụng print area nếu có
if (!string.IsNullOrEmpty(printArea))
worksheet.PageSetup.PrintArea = printArea;
// Convert
var renderer = new XlsIORenderer();
var pdfDocument = renderer.ConvertToPDF(workbook, settings);
var outputStream = new MemoryStream();
pdfDocument.Save(outputStream);
outputStream.Position = 0;
return outputStream;
}
API Endpoints
1. Export Excel
GET /api/ExcelToPdf/export-excel/{packageId}?templateType=Delivery_SB.xlsx
Authorization: Bearer {token}
Response (file download):
Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
Content-Disposition: attachment; filename="export_20250518_143022_abc123.xlsx"
2. Export PDF
GET /api/ExcelToPdf/export-pdf/{packageId}?templateType=Delivery_SB.xlsx
Authorization: Bearer {token}
Response:
{
"success": true,
"filePath": "/export/sb_20250518_143022_a1b2c3d4.pdf"
}
3. Convert Excel File sang PDF
POST /api/ExcelToPdf/convert-excel-to-pdf
Content-Type: application/json
Authorization: Bearer {token}
{
"excelFilePath": "C:\\path\\to\\file.xlsx",
"printArea": "B2:F15"
}
Request body:
| Field | Type | Bắt buộc | Mô tả |
|---|---|---|---|
excelFilePath |
string | ✅ | Đường dẫn tuyệt đối đến file Excel |
printArea |
string | ❌ | Vùng in, ví dụ "B2:F15". Bỏ qua để dùng auto-detect |
Response success:
{
"success": true,
"filePath": "/export/sb_20250518_143022_a1b2c3d4.pdf"
}
Response error:
{
"success": false,
"message": "File not found: C:\\path\\to\\file.xlsx"
}
Cách sử dụng
JavaScript / Fetch API
// Export PDF và mở trong tab mới
async function exportToPdf(packageId, templateType = 'Delivery_SB.xlsx') {
const response = await fetch(
`/api/ExcelToPdf/export-pdf/${packageId}?templateType=${templateType}`,
{ headers: { Authorization: `Bearer ${getToken()}` } }
);
const data = await response.json();
if (data.success) {
window.open(data.filePath, '_blank');
} else {
console.error('Export failed:', data.message);
alert(`Export thất bại: ${data.message}`);
}
}
Blazor Component
@inject IExcelExportServices ExcelExportServices
@inject NavigationManager NavigationManager
<button @onclick="() => ExportToPdf(packageId)">Export PDF</button>
@code {
[Parameter] public Guid PackageId { get; set; }
private async Task ExportToPdf(Guid packageId)
{
try
{
var pdfPath = await ExcelExportServices
.ExportDeliveryNoteToPdf(packageId, "Delivery_SB.xlsx");
NavigationManager.NavigateTo(pdfPath, true);
}
catch (Exception ex)
{
Console.WriteLine($"Export failed: {ex.Message}");
}
}
}
C# trực tiếp
// Inject service
private readonly IExcelExportServices _excelExportServices;
// Convert file có sẵn
using var pdfStream = _excelExportServices.ConvertExcelToPdf(
@"C:\templates\Delivery_SB.xlsx",
printArea: "A1:H50" // optional
);
// Lưu ra file
await using var fileStream = new FileStream(@"C:\output\delivery.pdf", FileMode.Create);
await pdfStream.CopyToAsync(fileStream);
// Hoặc trả về từ Controller
return File(pdfStream, "application/pdf", "delivery.pdf");
Code Signing
Binary SPM.NewAPI.exe được ký số bằng Azure Trusted Signing để đảm bảo tính toàn vẹn.
Thông tin chữ ký
| Thông số | Giá trị |
|---|---|
| Signer | CN=huanleducoutlook.onmicrosoft.com |
| Organization | huanleducoutlook.onmicrosoft.com |
| Trust Type | Private Trust |
| Timestamp Authority | http://timestamp.acs.microsoft.com |
Ký sau khi build
# Build project
dotnet publish "SPM.NewAPI.csproj" -c Release -o ./publish
# Ký binary
powershell -ExecutionPolicy Bypass -File "d:\Code\LibreOfffice\core\sign-app.ps1" `
-FilePath ".\publish\SPM.NewAPI.exe"
# Kiểm tra chữ ký
Get-AuthenticodeSignature ".\publish\SPM.NewAPI.exe" | Format-List Status, SignerCertificate
Build & Sign script tự động
# build-and-sign.ps1
param(
[string]$Configuration = "Release",
[string]$OutputPath = ".\publish"
)
$projectFile = "SPM.NewAPI.csproj"
$signScript = "d:\Code\LibreOfffice\core\sign-app.ps1"
Write-Host "[1/3] Building..."
dotnet publish $projectFile -c $Configuration -o $OutputPath
if ($LASTEXITCODE -ne 0) { throw "Build failed" }
Write-Host "[2/3] Signing..."
Get-ChildItem $OutputPath -Filter "*.exe" | ForEach-Object {
powershell -ExecutionPolicy Bypass -File $signScript -FilePath $_.FullName
if ($LASTEXITCODE -ne 0) { throw "Signing failed: $($_.Name)" }
}
Write-Host "[3/3] Done! Published to: $OutputPath"
Deployment
Publish
dotnet publish SPM.NewAPI.csproj \
-c Release \
-r win-x64 \
--self-contained false \
-o C:\inetpub\wwwroot\spm-api
IIS Setup
- Cài ASP.NET Core Runtime 8.x (Hosting Bundle)
- Tạo Application Pool: No Managed Code
- Trỏ Physical path đến thư mục publish
- Cài đặt quyền Read/Write cho thư mục
wwwroot/export/
appsettings.Production.json
{
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"AllowedHosts": "*",
"SyncfusionLicenseKey": "YOUR_LICENSE_KEY_HERE",
"ExportSettings": {
"OutputDirectory": "wwwroot/export",
"AutoDeleteMinutes": 10
}
}
File Management
- Output directory:
wwwroot/export/ - Naming format:
sb_yyyyMMdd_HHmmss_randomguid.pdf - Auto-delete: 10 phút sau khi tạo
- Cleanup job: Background service chạy mỗi 5 phút
Troubleshooting
❌ Syncfusion.XlsIORenderer.ConvertToPDF ném exception
Nguyên nhân: License key không hợp lệ hoặc chưa đăng ký.
Giải pháp:
// Đặt ở Program.cs trước khi app chạy
Syncfusion.Licensing.SyncfusionLicenseProvider.RegisterLicense("YOUR_KEY");
❌ PDF layout bị vỡ / tràn trang
Nguyên nhân: Print area không được set hoặc data quá nhiều cột.
Giải pháp:
worksheet.PageSetup.FitToPagesWide = 1;
worksheet.PageSetup.FitToPagesTall = 0; // auto height
worksheet.PageSetup.Orientation = ExcelPageOrientation.Landscape;
❌ File không tự động xóa
Nguyên nhân: Background service cleanup bị lỗi hoặc quyền truy cập file.
Giải pháp: Kiểm tra log và quyền của Application Pool trên thư mục wwwroot/export/.
❌ File not found khi convert
Nguyên nhân: Path sử dụng backslash trong JSON bị escape sai.
Giải pháp: Dùng double backslash \\ trong JSON hoặc forward slash /.