ASP.NET的PDF檢視器:如何在.NET 10中顯示PDF
手動報價生成的問題
每小時銷售代表花在將項目從CRM複製到Word模板上的時間就是他們沒在銷售的時間。 對於一個有十個代表運行二十個活躍機會的團隊來說,這段時間累積起來,結果甚至不可靠。 定價層次應用錯誤,折扣欄位保留在上個季度的利率上,舊模板版本的條款語言未經審查地滑入發給客戶的文件中。
時間線問題加劇了準確性問題。 一位在週二下午要求報價並在週三晚上收到報價的潛在客戶會花一天時間懷疑交易是否真實。 等PDF文件到達時,他們的注意力可能已經轉向回應更快的競爭對手。
現有的CPQ工具解決了其中的一部分,但其企業級價格和實施時間表不適合只需要從現有機會資料中獲取乾淨PDF文件的團隊。 電子郵件上傳遞的電子表格或純文字的價格表代替格式化的提案向潛在客戶表明公司的銷售流程是即興創作的。 而且當潛在客戶要求修訂時,版本混淆增加,沒有附加在CRM記錄上的唯一真實來源的多次迭代作為電子郵件附件流動。
情況不同:一個B2B軟體公司從Salesforce提取授權報價,一個製造公司從ERP項目生成部件和報價,一個機構從內部估算中生成項目範圍文件,一個批發分銷商為零售購買者建立批量折扣報價。 每個案例中的基本摩擦都是相同的。
解決方案:直接從機會資料生成報價
IronPDF是一個強大的.NET PDF程式庫,允許.NET應用程式直接從CRM機會資料生成帶有品牌的PDF報價和提案。 當代表標記交易為準備報價或在內部工具中點擊按鈕時,應用程式從CRM或資料庫中提取項目、定價、客戶詳細資訊和條款,填充HTML模板或HTML內容字串,然後ChromePdfRenderer從該HTML中生成C# PDF。
報價被電子郵件發送給潛在客戶,發送的文件自動記錄回CRM記錄中,無需手動格式化,無需Word模板,無需每個文件的SaaS費用。 渲染運行在現有.NET應用程式中,作為一個單NuGet包。 模板由您的團隊擁有和版本化,更新在一個地方,無需將文件重新分發給銷售團隊,在未來的每個報價中得到反映。
實際使用的運作方式
1. 一個觸發器啟動報價
管道開始於代表在CRM UI中點擊"生成報價",機會自動進入特定的管道階段,或外部自動化平台對報價端點進行API呼叫。 所有三個入口點提供相同的生成流程,觸發機制不會改變接下來發生的事情。
應用程式查詢CRM或資料庫以獲取報價所需的一切:潛在公司名稱,聯絡人姓名和電子郵件,帶有SKU、描述、數量、單價、應用折扣百分比和行總計的項目; 小計,稅金計算和總計; 付款條款,報價有效期,和代表新增到機會中的任何自訂備註。
2. HTML模板定義報價佈局
模板處理報價的完整視覺結構:公司標誌和標題,包含潛在客戶詳細資訊的"準備給"塊,描述、數量、單價、折扣和行總計的列的項目表,小計和總計總結,條款和條件部分,以及包含代表姓名、頭銜和直接聯繫方式的頁尾。
通過在C#處理程式中迭代機會的項目並在將完整模板傳遞給IronPDF之前構建HTML行,來構建項目表。 這將邏輯保持在應用層而不是模板本身中。
範例HTML文件模板

3. ChromePdfRenderer生成報價PDF文件
IronPDF的強大渲染引擎使HTML轉PDF轉換變得簡單,通常只需幾行程式碼即可完成。
using IronPdf;
var renderer = new ChromePdfRenderer();
renderer.RenderingOptions.PaperSize = IronPdf.Rendering.PdfPaperSize.Letter;
renderer.RenderingOptions.MarginTop = 20;
renderer.RenderingOptions.MarginBottom = 20;
var lineItemRows = string.Concat(opportunity.LineItems.Select(item => $@"
<tr>
<td>{item.Description}</td>
<td style='text-align:center'>{item.Quantity}</td>
<td style='text-align:right'>{item.UnitPrice:C}</td>
<td style='text-align:center'>{item.DiscountPct}%</td>
<td style='text-align:right'>{item.LineTotal:C}</td>
</tr>"));
string html = $@"
<h1>Quote — {opportunity.ProspectCompany}</h1>
<p>Prepared for: {opportunity.ContactName} <{opportunity.ContactEmail}></p>
<p>Valid until: {opportunity.QuoteExpiry:MMMM d, yyyy} | Terms: {opportunity.PaymentTerms}</p>
<table border='1' cellpadding='6' width='100%'>
<thead><tr><th>Description</th><th>Qty</th><th>Unit Price</th><th>Discount</th><th>Total</th></tr></thead>
<tbody>{lineItemRows}</tbody>
</table>
<p style='text-align:right'><strong>Subtotal:</strong> {opportunity.Subtotal:C}</p>
<p style='text-align:right'><strong>Tax ({opportunity.TaxRatePct}%):</strong> {opportunity.TaxAmount:C}</p>
<p style='text-align:right'><strong>Grand Total:</strong> {opportunity.GrandTotal:C}</p>
<hr/><p style='font-size:11px'>{opportunity.TermsAndConditions}</p>";
PdfDocument quote = renderer.RenderHtmlAsPdf(html);
using IronPdf;
var renderer = new ChromePdfRenderer();
renderer.RenderingOptions.PaperSize = IronPdf.Rendering.PdfPaperSize.Letter;
renderer.RenderingOptions.MarginTop = 20;
renderer.RenderingOptions.MarginBottom = 20;
var lineItemRows = string.Concat(opportunity.LineItems.Select(item => $@"
<tr>
<td>{item.Description}</td>
<td style='text-align:center'>{item.Quantity}</td>
<td style='text-align:right'>{item.UnitPrice:C}</td>
<td style='text-align:center'>{item.DiscountPct}%</td>
<td style='text-align:right'>{item.LineTotal:C}</td>
</tr>"));
string html = $@"
<h1>Quote — {opportunity.ProspectCompany}</h1>
<p>Prepared for: {opportunity.ContactName} <{opportunity.ContactEmail}></p>
<p>Valid until: {opportunity.QuoteExpiry:MMMM d, yyyy} | Terms: {opportunity.PaymentTerms}</p>
<table border='1' cellpadding='6' width='100%'>
<thead><tr><th>Description</th><th>Qty</th><th>Unit Price</th><th>Discount</th><th>Total</th></tr></thead>
<tbody>{lineItemRows}</tbody>
</table>
<p style='text-align:right'><strong>Subtotal:</strong> {opportunity.Subtotal:C}</p>
<p style='text-align:right'><strong>Tax ({opportunity.TaxRatePct}%):</strong> {opportunity.TaxAmount:C}</p>
<p style='text-align:right'><strong>Grand Total:</strong> {opportunity.GrandTotal:C}</p>
<hr/><p style='font-size:11px'>{opportunity.TermsAndConditions}</p>";
PdfDocument quote = renderer.RenderHtmlAsPdf(html);
Imports IronPdf
Dim renderer As New ChromePdfRenderer()
renderer.RenderingOptions.PaperSize = IronPdf.Rendering.PdfPaperSize.Letter
renderer.RenderingOptions.MarginTop = 20
renderer.RenderingOptions.MarginBottom = 20
Dim lineItemRows As String = String.Concat(opportunity.LineItems.Select(Function(item) $"
<tr>
<td>{item.Description}</td>
<td style='text-align:center'>{item.Quantity}</td>
<td style='text-align:right'>{item.UnitPrice:C}</td>
<td style='text-align:center'>{item.DiscountPct}%</td>
<td style='text-align:right'>{item.LineTotal:C}</td>
</tr>"))
Dim html As String = $"
<h1>Quote — {opportunity.ProspectCompany}</h1>
<p>Prepared for: {opportunity.ContactName} <{opportunity.ContactEmail}></p>
<p>Valid until: {opportunity.QuoteExpiry:MMMM d, yyyy} | Terms: {opportunity.PaymentTerms}</p>
<table border='1' cellpadding='6' width='100%'>
<thead><tr><th>Description</th><th>Qty</th><th>Unit Price</th><th>Discount</th><th>Total</th></tr></thead>
<tbody>{lineItemRows}</tbody>
</table>
<p style='text-align:right'><strong>Subtotal:</strong> {opportunity.Subtotal:C}</p>
<p style='text-align:right'><strong>Tax ({opportunity.TaxRatePct}%):</strong> {opportunity.TaxAmount:C}</p>
<p style='text-align:right'><strong>Grand Total:</strong> {opportunity.GrandTotal:C}</p>
<hr/><p style='font-size:11px'>{opportunity.TermsAndConditions}</p>"
Dim quote As PdfDocument = renderer.RenderHtmlAsPdf(html)
生成的PDF文件
4. 報價已交付並記錄
using System.Net.Mail;
using System.IO;
// Save versioned copy to document storage
string outputPath = $"quotes/{opportunity.Id}/v{opportunity.QuoteVersion}.pdf";
quote.SaveAs(outputPath);
// Email to prospect
using var stream = new MemoryStream(quote.BinaryData);
using var attachment = new Attachment(stream,
$"Quote-{opportunity.ProspectCompany}-v{opportunity.QuoteVersion}.pdf",
"application/pdf");
var message = new MailMessage("sales@yourcompany.com", opportunity.ContactEmail)
{
Subject = $"Your Quote from {opportunity.RepName} — {opportunity.ProspectCompany}",
Body = $"Hi {opportunity.ContactName},\n\nPlease find your quote attached. It is valid until {opportunity.QuoteExpiry:MMMM d, yyyy}.\n\n{opportunity.RepName}"
};
message.Attachments.Add(attachment);
using var smtp = new SmtpClient("smtp.yourprovider.com");
await smtp.SendMailAsync(message);
// Log reference back to CRM
await _crmService.LogQuoteSentAsync(opportunity.Id, outputPath, DateTime.UtcNow);
using System.Net.Mail;
using System.IO;
// Save versioned copy to document storage
string outputPath = $"quotes/{opportunity.Id}/v{opportunity.QuoteVersion}.pdf";
quote.SaveAs(outputPath);
// Email to prospect
using var stream = new MemoryStream(quote.BinaryData);
using var attachment = new Attachment(stream,
$"Quote-{opportunity.ProspectCompany}-v{opportunity.QuoteVersion}.pdf",
"application/pdf");
var message = new MailMessage("sales@yourcompany.com", opportunity.ContactEmail)
{
Subject = $"Your Quote from {opportunity.RepName} — {opportunity.ProspectCompany}",
Body = $"Hi {opportunity.ContactName},\n\nPlease find your quote attached. It is valid until {opportunity.QuoteExpiry:MMMM d, yyyy}.\n\n{opportunity.RepName}"
};
message.Attachments.Add(attachment);
using var smtp = new SmtpClient("smtp.yourprovider.com");
await smtp.SendMailAsync(message);
// Log reference back to CRM
await _crmService.LogQuoteSentAsync(opportunity.Id, outputPath, DateTime.UtcNow);
Imports System.Net.Mail
Imports System.IO
' Save versioned copy to document storage
Dim outputPath As String = $"quotes/{opportunity.Id}/v{opportunity.QuoteVersion}.pdf"
quote.SaveAs(outputPath)
' Email to prospect
Using stream As New MemoryStream(quote.BinaryData)
Using attachment As New Attachment(stream, $"Quote-{opportunity.ProspectCompany}-v{opportunity.QuoteVersion}.pdf", "application/pdf")
Dim message As New MailMessage("sales@yourcompany.com", opportunity.ContactEmail) With {
.Subject = $"Your Quote from {opportunity.RepName} — {opportunity.ProspectCompany}",
.Body = $"Hi {opportunity.ContactName}," & vbCrLf & vbCrLf & $"Please find your quote attached. It is valid until {opportunity.QuoteExpiry:MMMM d, yyyy}." & vbCrLf & vbCrLf & $"{opportunity.RepName}"
}
message.Attachments.Add(attachment)
Using smtp As New SmtpClient("smtp.yourprovider.com")
Await smtp.SendMailAsync(message)
End Using
End Using
End Using
' Log reference back to CRM
Await _crmService.LogQuoteSentAsync(opportunity.Id, outputPath, DateTime.UtcNow)
附PDF的發送郵件範例
在文件路徑和文件名中加入一個版本號,賦予每次修改其自己的永久記錄。 當潛在客戶要求更改時,代表在CRM中更新機會,應用程式生成增加版本的報價,新報價被發送,完整歷史被保留在文件儲存中並記錄到CRM記錄中。
真實世界的好處
迅速達到潛在客戶。報價在代表啟動後幾秒鐘內生成並發送電子郵件。 沒有格式化延遲,沒有文件留在草稿夾中,沒有從代表決定報價到潛在客戶收到其間的滯後。
定價準確性。項目、折扣層次和總計直接從CRM機會中提取。 沒有手動抄寫步驟,也沒有代表應用上季度價格或錯誤折扣計劃的風險。
品牌一致性。公司發送的每個報價都帶有相同的標誌、佈局、條款語言和頁底,不管是由哪個代表生成的或從哪一個管道階段生成的。 沒有流落在個人筆記本電腦上的獨行Word模板。
版本控制。每個生成的報價都儲存其機會ID、版本號和發送時的時間戳。 CRM記錄讓銷售經理可以全面掌握報價的內容、物件和時間,而無需翻找電子郵件執行緒。
模板重用。當法律團隊更新條款語言或品牌團隊刷新標誌時,一個HTML文件更改,未來的每個報價都能反映它。 不需要將Word模板重新分發給銷售團隊。
沒有每個文件的成本。渲染在.NET應用程式內部處理。 沒有CPQ訂閱、没有文件生成API計量,且没有隨著交易量增長的成本模型。
結論
CRM機會資料與潛在客戶收件箱中的專業PDF之間的差距比看起來小,這是一個模板、一個渲染呼叫和一個電子郵件發送,所有這些都由銷售工作流中的單個操作觸發。 去除手動格式化步驟不僅僅是節省時間;它消除了可能損失交易的一類錯誤。
那個管道在現有.NET應用程式中是容易構建和維護的。 IronPDF在ironpdf.com上處理C#中PDF生成的完整生命周期,從渲染HTML模板到儲存、流式傳輸和操作文件。 這個免費30天試用給您足夠的時間以您的CRM資料和銷售工作流為基礎構建和測試完整的報價生成流程。




