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:
@@ -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)
|
||||
Reference in New Issue
Block a user