
1. 關於 questpdf
questpdf 是一個開源的工具庫,可以在.net 或者.net core 中生成 pdf 文檔
它提供了一個布局引擎,設計時考慮到了完整的分頁支持以及靈活性要求!比市面上常見的 aspose 和 itextsharp 好用太多了!

2. 安裝
Install-Package QuestPDF

3. 例子
3.1簡單例子
生成 pdf 文檔一共分為三部分,header(頁眉),content(內容),footer(頁腳),下面是例子代碼:
Document.Create(container =>
{
container.Page(page =>
{
page.Size(PageSizes.A4);
page.Margin(2, Unit.Centimetre);
page.Background(Colors.White);
page.DefaultTextStyle(x => x.FontSize(20));
page.Header()
.Text("Hello PDF!")
.SemiBold().FontSize(36).FontColor(Colors.Blue.Medium);
page.Content()
.PaddingVertical(1, Unit.Centimetre)
.Column(x =>
{
x.Spacing(20);
x.Item().Text(Placeholders.LoremIpsum());
x.Item().Image(Placeholders.Image(200, 100));
});
page.Footer()
.AlignCenter()
.Text(x =>
{
x.Span("Page ");
x.CurrentPageNumber();
});
});
})
.GeneratePdf("hello.pdf");
上面代碼生成的 pdf 結果:

3.2模板生成
使用模板生成一共設計三個應用層的工作:
- 文檔 model(一組描述 pdf 文檔內容的類)
- 數據源(將域實體映射到文檔模型的層)
- 模板(描述如何可視化信息並將其轉換為 pdf 文件的表示層)
比如我們設計一個基本的發票信息 要設計一個購物清單,一個賣家買家的地址,以及發票編號等等 我們設計這樣的 3 個 model 類:
public class InvoiceModel
{
public int InvoiceNumber { get; set; }
public DateTime IssueDate { get; set; }
public DateTime DueDate { get; set; }
public Address SellerAddress { get; set; }
public Address CustomerAddress { get; set; }
public List<OrderItem> Items { get; set; }
public string Comments { get; set; }
}
public class OrderItem
{
public string Name { get; set; }
public decimal Price { get; set; }
public int Quantity { get; set; }
}
public class Address
{
public string CompanyName { get; set; }
public string Street { get; set; }
public string City { get; set; }
public string State { get; set; }
public object Email { get; set; }
public string Phone { get; set; }
}
model 定義好了之後我們就定義一些假數據來填充 pdf:
public static class InvoiceDocumentDataSource
{
private static Random Random = new Random();
public static InvoiceModel GetInvoiceDetails()
{
var items = Enumerable
.Range(1, 8)
.Select(i => GenerateRandomOrderItem())
.ToList();
return new InvoiceModel
{
InvoiceNumber = Random.Next(1_000, 10_000),
IssueDate = DateTime.Now,
DueDate = DateTime.Now + TimeSpan.FromDays(14),
SellerAddress = GenerateRandomAddress(),
CustomerAddress = GenerateRandomAddress(),
Items = items,
Comments ="测试备注"
};
}
private static OrderItem GenerateRandomOrderItem()
{
return new OrderItem
{
Name = "商品",
Price = (decimal)Math.Round(Random.NextDouble() * 100, 2),
Quantity = Random.Next(1, 10)
};
}
private static Address GenerateRandomAddress()
{
return new Address
{
CompanyName = "测试商店",
Street = "测试街道",
City = "测试城市",
State = "测试状态",
Email = "测试邮件",
Phone = "测试电话"
};
}
}
然後搭建我們的模板腳手架 我們要使用模板腳手架, 就要定義一個實現 idocument 接口的新類開始。
該接口包含兩個方法:
- DocumentMetadata GetMetadata();
- void Compose(IDocumentContainer container);
第一個是模板文檔的一些基礎信息,第二個是模板的容器,基於這些原則我們設計一個模板層類:
public class InvoiceDocument : IDocument
{
public InvoiceModel Model { get; }
public InvoiceDocument(InvoiceModel model)
{
Model = model;
}
public DocumentMetadata GetMetadata() => DocumentMetadata.Default;
public void Compose(IDocumentContainer container)
{
container
.Page(page =>
{
page.PageColor(Colors.Red.Lighten1);
page.Size(PageSizes.A4);
page.Margin(10);//外边距
page.Header().Height(100).Background(Colors.LightBlue.Lighten1);
page.Content().Background(Colors.Grey.Lighten3);
page.Footer().Height(50).Background(Colors.Grey.Lighten1);
});
}
}
pdf 的 page 頁面總是有三個元素:頁眉,頁腳,內容。查看一下我們生成的文檔:

到目前為止,我們已經搭建了一個非常簡單的頁面,其中每個部分都有不同的顏色或大小。
接下來我們來填充他的頁眉,我們把數據源整理好了之後,就可以調用 element 方法填充:
public void Compose(IDocumentContainer container)
{
container
.Page(page =>
{
page.PageColor(Colors.Red.Lighten1);
page.Size(PageSizes.A4);
page.Margin(10);//外边距
page.Header().Height(100).Background(Colors.LightBlue.Lighten1).Element(ComposeHeader);
page.Content().Background(Colors.Grey.Lighten3);
page.Footer().Height(50).Background(Colors.Grey.Lighten1);
});
}
void ComposeHeader(IContainer container)
{
var titleStyle = TextStyle.Default.FontSize(20).SemiBold().FontColor(Colors.Blue.Medium);
container.Row(row =>
{
row.RelativeItem().Column(column =>
{
column.Item().Text($"发票 #{Model.InvoiceNumber}").FontFamily("simhei").Style(titleStyle);
column.Item().Text(text =>
{
text.Span("发行日期: ").SemiBold().FontFamily("simhei");
text.Span($"{Model.IssueDate:d}").FontFamily("simhei");
});
column.Item().Text(text =>
{
text.Span("支付日期: ").FontFamily("simhei").SemiBold();
text.Span($"{Model.DueDate:d}").FontFamily("simhei");
});
})
;
});
}

最後我們來實現內容:
public void Compose(IDocumentContainer container)
{
container
.Page(page =>
{
page.PageColor(Colors.Red.Lighten1);
page.Size(PageSizes.A4);
page.Margin(10);//外边距
page.Header().Height(100).Background(Colors.LightBlue.Lighten1).Element(ComposeHeader);
page.Content().Background(Colors.Grey.Lighten3).Element(ComposeContent);
page.Footer().Height(50).Background(Colors.Grey.Lighten1);
});
}
void ComposeHeader(IContainer container)
{
var titleStyle = TextStyle.Default.FontSize(20).SemiBold().FontColor(Colors.Blue.Medium);
container.Row(row =>
{
row.RelativeItem().Column(column =>
{
column.Item().Text($"发票 #{Model.InvoiceNumber}").FontFamily("simhei").Style(titleStyle);
column.Item().Text(text =>
{
text.Span("发行日期: ").SemiBold().FontFamily("simhei");
text.Span($"{Model.IssueDate:d}").FontFamily("simhei");
});
column.Item().Text(text =>
{
text.Span("支付日期: ").FontFamily("simhei").SemiBold();
text.Span($"{Model.DueDate:d}").FontFamily("simhei");
});
})
;
});
}
void ComposeContent(IContainer container)
{
container.Table(table =>
{
// step 1
table.ColumnsDefinition(columns =>
{
columns.ConstantColumn(25);
columns.RelativeColumn(3);
columns.RelativeColumn();
columns.RelativeColumn();
columns.RelativeColumn();
});
// step 2
table.Header(header =>
{
header.Cell().Text("#").FontFamily("simhei");
header.Cell().Text("商品").FontFamily("simhei");
header.Cell().AlignRight().Text("价格").FontFamily("simhei");
header.Cell().AlignRight().Text("数量").FontFamily("simhei");
header.Cell().AlignRight().Text("总价").FontFamily("simhei");
header.Cell().ColumnSpan(5)
.PaddingVertical(5).BorderBottom(1).BorderColor(Colors.Black);
});
// step 3
foreach (var item in Model.Items)
{
table.Cell().Element(CellStyle).Text(Model.Items.IndexOf(item) + 1).FontFamily("simhei");
table.Cell().Element(CellStyle).Text(item.Name).FontFamily("simhei");
table.Cell().Element(CellStyle).AlignRight().Text($"{item.Price}$").FontFamily("simhei");
table.Cell().Element(CellStyle).AlignRight().Text(item.Quantity).FontFamily("simhei");
table.Cell().Element(CellStyle).AlignRight().Text($"{item.Price * item.Quantity}$").FontFamily("simhei");
static IContainer CellStyle(IContainer container)
{
return container.BorderBottom(1).BorderColor(Colors.Grey.Lighten2).PaddingVertical(5);
}
}
});
}
在這些準備工作做完了之後我們就可以生成 pdf 文檔了:
var filePath = "invoice.pdf";
var model = InvoiceDocumentDataSource.GetInvoiceDetails();
var document = new InvoiceDocument(model);
document.GeneratePdf(filePath);

4. 總結
當然還有很多好玩的功能,今天就給大家講個概念,讓大家對這個東西有個印象,後面我會繼續輸出該庫的相關功能。如果你們對該庫感興趣,可以持續關注我!微信公眾號【黑哥聊 dotnet】