푸터 콘텐츠로 바로가기
IRONPDF 사용하기

ASP.NET용 PDF 뷰어: .NET 10에서 PDF 표시하는 방법

수동 견적 생성의 문제점

IronPDF 홈페이지 영업 사원이 CRM에서 Word 템플릿으로 항목을 복사하는 데 한 시간을 소비하면, 해당 한 시간은 판매에 투입되지 않는 시간입니다. 각 팀원 10명이 20개의 활성 기회를 운영하면 그 시간은 쌓여가고 출력 결과는 신뢰할 수 없습니다. 가격 책정 계층이 잘못 적용되고, 할인 필드는 지난 분기의 금리로 유지되며, 오래된 템플릿 버전의 계약서 문구가 고객에게 검토되지 않고 전송될 문서에 미끄러 들어갑니다.

일정 문제는 정확도 문제와 복합되었습니다. 화요일 오후에 견적을 요청하고 수요일 저녁에 받는 고객은 하루 동안 거래가 진짜인지 궁금해합니다. PDF 파일이 도착했을 때쯤에는 아마도 더 빨리 응답한 경쟁자로 관심이 옮겨졌을 수도 있습니다.

기존의 CPQ 도구는 이 문제 부분을 해결하지만, 기업 가격과 구현 일정은 데이터를 활용하여 깔끔한 PDF 파일만 필요한 팀에게 맞지 않습니다. 포맷된 제안 대신 스프레드시트나 평문 가격 목록을 이메일로 보내는 것은 고객에게 회사의 영업 프로세스가 즉흥적으로 이루어졌음을 신호합니다. 잠재 고객이 수정 요청을 할 때, 버전의 혼란이 곱해져, 여러 버전이 이메일 첨부 파일로 떠다니게 되어 CRM 기록에 진정한 단일 출처가 연결되지 않습니다.

시나리오는 다양합니다: Salesforce에서 라이선스 견적을 가져오는 B2B 소프트웨어 회사, ERP 항목에서 파트 및 가격 제안을 생성하는 제조 회사, 내부 견적으로부터 프로젝트 범위 문서를 생성하는 에이전시, 도매 유통업체가 소매 구매자를 위한 대량 할인 제안을 생성합니다. 모든 경우에 근본적인 마찰은 동일합니다.

해결책: 기회 데이터에서 직접 견적 생성

IronPDF는 .NET 애플리케이션이 CRM 기회 데이터에서 브랜드 PDF 견적 및 제안을 직접 생성할 수 있도록 하는 강력한 .NET PDF 라이브러리입니다. 담당자가 거래를 견적 준비 완료로 표시하거나 내부 도구에서 버튼을 클릭하면 애플리케이션이 CRM 또는 데이터베이스에서 항목, 가격, 고객 세부 정보 및 약관을 가져와 HTML 템플릿 또는 HTML 콘텐츠 문자열을 채우고 ChromePdfRenderer가 해당 HTML에서 C# PDF를 생성합니다 from that HTML.

견적서는 잠재 고객에게 이메일로 전송되고 수동 포맷팅 없이, Word 템플릿 없이, 개별 문서 SaaS 요금 없이 전송된 문서가 CRM 레코드에 자동으로 기록됩니다. 렌더링은 기존 .NET 애플리케이션 내에서 단일 NuGet 패키지로 실행됩니다. 템플릿은 귀하의 팀에 의해 소유되고 버전 관리되며, 한 곳에서 업데이트되어 파일을 영업팀에게 다시 배포하지 않고도 모든 후속 견적에 반영됩니다.

실제 적용 사례

1. 트리거가 견적을 시작합니다

파이프라인은 담당자가 CRM UI에서 "견적 생성"을 클릭하거나 기회가 특정 파이프라인 단계에 자동으로 진입하거나 외부 자동화 플랫폼이 API 호출을 통해 견적 엔드포인트에 액세스할 때 시작됩니다. 이 세 가지 진입 지점은 동일한 생성 흐름을 작동시키며, 트리거 메커니즘은 다음에 일어나는 일을 변경하지 않습니다.

애플리케이션은 CRM 또는 데이터베이스에 문의하여 견적에 필요한 모든 항목을 가져옵니다: 잠재 고객 회사 이름, 연락처 이름 및 이메일, SKU, 설명, 수량, 단가, 적용된 할인 비율, 각 항목의 합계; 부분 합계, 세금 계산 및 총 합계; 결제 조건, 견적 유효 기간, 그리고 담당자가 기회에 추가한 맞춤형 메모입니다.

2. HTML 템플릿이 견적 레이아웃을 정의합니다

템플릿은 견적의 전체 시각적 구조를 담당합니다: 회사 로고 및 헤더, 잠재 고객의 세부 정보가 포함된 "대상" 블록, 설명, 수량, 단가, 할인, 각 항목의 합계에 대한 열이 있는 항목표, 부분 합계 및 총 합계 요약, 약관 및 조건 섹션, 그리고 담당자의 이름, 직책 및 직접 연락처가 포함된 바닥글.

항목표는 C# 핸들러에서 기회의 항목을 반복하여 생성되며, IronPDF에 완전한 템플릿을 전달하기 전에 HTML 행을 구성합니다. 이렇게 하면 로직이 템플릿 자체가 아닌 애플리케이션 레이어에 남게 됩니다.

예시 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} &lt;{opportunity.ContactEmail}&gt;</p>
    <p>Valid until: {opportunity.QuoteExpiry:MMMM d, yyyy} &nbsp;|&nbsp; 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} &lt;{opportunity.ContactEmail}&gt;</p>
    <p>Valid until: {opportunity.QuoteExpiry:MMMM d, yyyy} &nbsp;|&nbsp; 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} &lt;{opportunity.ContactEmail}&gt;</p>
    <p>Valid until: {opportunity.QuoteExpiry:MMMM d, yyyy} &nbsp;|&nbsp; 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)
$vbLabelText   $csharpLabel

생성된 PDF 문서

IronPDF 예제 출력

템플릿에 회사 로고와 폰트 파일을 베이스64 데이터 URI로 포함하거나 RenderHtmlAsPdf()의 두 번째 매개변수로 BaseUrlPath를 설정하여 IronPDF가 애플리케이션이 배포된 위치에 관계없이 관련 자산 경로를 해석할 수 있도록 합니다.

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)
$vbLabelText   $csharpLabel

첨부된 PDF가 있는 전송된 이메일 예시

첨부된 PDF 견적이 있는 이메일 파일 경로와 파일 이름 모두에 버전 번호가 있어 모든 수정본에 고유한 영구 기록이 부여됩니다. 잠재 고객이 변경을 요청하면 담당자는 CRM에서 기회를 업데이트하고 애플리케이션은 버전이 증가된 상태로 다시 생성되며, 새로운 견적서가 전송되고 전체 기록은 문서 저장소에 유지되며 CRM 레코드에 기록됩니다.

실제 이점

잠재 고객으로의 신속한 전환. 견적서는 담당자가 시작한 후 몇 초 만에 생성 및 이메일로 전송됩니다. 포맷 지연, 초안 폴더에 있는 문서, 담당자가 견적을 결정한 후 잠재 고객이 받기 전까지의 지연이 없습니다.

가격 정확성. 품목, 할인 단계 및 총액은 CRM 기회의 데이터를 직접 가져옵니다. 수기 전사 단계가 없고 대리인이 지난 분기 가격 또는 잘못된 할인 스케줄을 적용할 위험이 없습니다.

브랜드 일관성. 회사가 보내는 모든 견적서는 로고, 레이아웃, 약관 언어 및 푸터가 대리인이 생성했는지 또는 어느 파이프라인 단계에서 생성되었든 동일합니다. 개인 노트북에 떠도는 무단 Word 템플릿은 없습니다.

버전 추적. 생성된 각 견적서는 그 기회의 ID, 버전 번호, 전송 시간을 포함하여 저장됩니다. CRM 로그는 영업 관리자에게 무엇이, 누구에게, 언제 인용되었는지를 이메일 스레드를 통해 확인할 필요 없이 명확히 보여줍니다.

템플릿 재사용. 법무팀이 약관 언어를 업데이트하거나 브랜드 팀이 로고를 새로고침할 때, 하나의 HTML 파일만 변경하면 모든 미래의 견적서는 이를 반영합니다. 영업팀에게 Word 템플릿의 재배포는 없습니다.

문서당 비용 없음. 렌더링은 .NET 애플리케이션 내에서 프로세스로 실행됩니다. CPQ 구독, 문서 생성 API 측정 없음, 거래량에 따라 확장되지 않는 비용 모델 없음.

마무리

CRM 기회 데이터와 잠재 고객의 받은 편지함의 전문 PDF 사이의 간격은 보기보다 작으며, 이는 템플릿, 렌더 콜, 이메일 발송으로 모두 영업 워크플로 내의 한 번의 동작에 의해 트리거됩니다. 수동 포맷 단계 제거는 시간을 절약할 뿐만 아니라 거래에 비용이 드는 오류 범주를 제거합니다.

그 파이프라인은 기존 .NET 애플리케이션 내에서 구축하고 유지하기가 간단합니다. IronPDF는 C#에서 ironpdf.com에서 PDF 생성을 위한 전체 수명 주기를 처리하며, HTML 템플릿 렌더링부터 문서 저장, 스트리밍 및 조작까지 가능합니다. 무료 30일 체험을 통해 전체 견적 생성 파이프라인을 자체 CRM 데이터와 영업 워크플로에 맞춰 구축하고 테스트할 충분한 시간이 주어집니다.

Curtis Chau
기술 문서 작성자

커티스 차우는 칼턴 대학교에서 컴퓨터 과학 학사 학위를 취득했으며, Node.js, TypeScript, JavaScript, React를 전문으로 하는 프론트엔드 개발자입니다. 직관적이고 미적으로 뛰어난 사용자 인터페이스를 만드는 데 열정을 가진 그는 최신 프레임워크를 활용하고, 잘 구성되고 시각적으로 매력적인 매뉴얼을 제작하는 것을 즐깁니다.

커티스는 개발 분야 외에도 사물 인터넷(IoT)에 깊은 관심을 가지고 있으며, 하드웨어와 소프트웨어를 통합하는 혁신적인 방법을 연구합니다. 여가 시간에는 게임을 즐기거나 디스코드 봇을 만들면서 기술에 대한 애정과 창의성을 결합합니다.

아이언 서포트 팀

저희는 주 5일, 24시간 온라인으로 운영합니다.
채팅
이메일
전화해