feat: initial commit - Excel to PDF Export Service documentation

- 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
This commit is contained in:
huanld
2026-05-18 14:54:09 +07:00
commit 27fb40ee26
+422
View File
@@ -0,0 +1,422 @@
# 📄 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](#tổng-quan)
- [Kiến trúc](#kiến-trúc)
- [Cài đặt](#cài-đặt)
- [API Endpoints](#api-endpoints)
- [Cách sử dụng](#cách-sử-dụng)
- [Code Signing](#code-signing)
- [Deployment](#deployment)
- [Troubleshooting](#troubleshooting)
---
## 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`:
```xml
<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`:
```csharp
Syncfusion.Licensing.SyncfusionLicenseProvider.RegisterLicense("YOUR_LICENSE_KEY");
```
### 3. Service Registration (DI)
```csharp
// Program.cs
builder.Services.AddScoped<IExcelExportServices, ExcelExportServices>();
```
### 4. Interface
```csharp
public interface IExcelExportServices
{
MemoryStream ConvertExcelToPdf(string excelFilePath);
MemoryStream ConvertExcelToPdf(string excelFilePath, string printArea);
Task<string> ExportDeliveryNoteToPdf(Guid pkgpackageid, string TemplateType);
}
```
### 5. Implementation chính
```csharp
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
```http
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
```http
GET /api/ExcelToPdf/export-pdf/{packageId}?templateType=Delivery_SB.xlsx
Authorization: Bearer {token}
```
**Response**:
```json
{
"success": true,
"filePath": "/export/sb_20250518_143022_a1b2c3d4.pdf"
}
```
---
### 3. Convert Excel File sang PDF
```http
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**:
```json
{
"success": true,
"filePath": "/export/sb_20250518_143022_a1b2c3d4.pdf"
}
```
**Response error**:
```json
{
"success": false,
"message": "File not found: C:\\path\\to\\file.xlsx"
}
```
---
## Cách sử dụng
### JavaScript / Fetch API
```javascript
// 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
```csharp
@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
```csharp
// 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
```powershell
# 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
```powershell
# 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
```powershell
dotnet publish SPM.NewAPI.csproj \
-c Release \
-r win-x64 \
--self-contained false \
-o C:\inetpub\wwwroot\spm-api
```
### IIS Setup
1. Cài **ASP.NET Core Runtime 8.x** (Hosting Bundle)
2. Tạo Application Pool: **No Managed Code**
3. Trỏ Physical path đến thư mục publish
4. Cài đặt quyền Read/Write cho thư mục `wwwroot/export/`
### appsettings.Production.json
```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**:
```csharp
// Đặ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**:
```csharp
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 `/`.
---
## Tham khảo
- [Syncfusion XlsIORenderer Docs](https://help.syncfusion.com/file-formats/xlsio/excel-to-pdf-conversion)
- [Syncfusion Excel to PDF Print Area](https://help.syncfusion.com/file-formats/xlsio/excel-to-pdf-conversion#print-area)
- [ASP.NET Core File Results](https://docs.microsoft.com/en-us/aspnet/core/web-api/action-return-types)
- [Azure Trusted Signing](https://learn.microsoft.com/en-us/azure/trusted-signing/) — xem repo [signing-tool](https://gittea.softs.business/huanld/signing-tool)