Generate Reports in C# Like 크리스탈 리포트 (.NET 10)

This article was translated from English: Does it need improvement?
Translated
View the article in English

IronPDF를 사용한 C# .NET 기반 HTML-to-PDF 보고서 생성은 크리스탈 리포트의 독점적인 디자이너를 표준 HTML, CSS 및 Razor 템플릿 으로 대체하여 .NET 개발자가 이미 보유한 웹 개발 기술을 활용하여 데이터 기반 비즈니스 보고서를 구축할 수 있도록 합니다. 여기에는 동적 테이블, JavaScript 기반 차트 , 조건부 서식, 다중 문서 일괄 처리 및 .NET이 실행되는 모든 환경에 대한 크로스 플랫폼 배포에 대한 완벽한 지원이 포함됩니다.

요약: 빠른 시작 가이드

이 튜토리얼에서는 기본 템플릿부터 일괄 처리 및 예약 생성에 이르기까지 크리스탈 리포트를 C# .NET의 HTML-to-PDF 보고서 생성 기능으로 대체하는 방법을 다룹니다.

  • 이 제품은 크리스탈 리포트를 대체하거나 새로운 보고 시스템을 처음부터 구축하는 .NET 개발자를 위한 것입니다 .
  • 개발할 내용: 세 가지 보고서(판매 송장, 직원 목록, 재고 보고서)의 완벽한 구현과 더불어 Chart.js 시각화, 브랜드 헤더/푸터, 목차 생성, 하위 보고서 병합 및 병렬 배치 처리 기능을 개발합니다.
  • 지원 환경: .NET 10, .NET 8 LTS, .NET Framework 4.6.2 이상, .NET Standard 2.0. Windows 전용 COM 종속성은 필요하지 않습니다.
  • 이 접근 방식을 사용하는 경우: 크리스탈 리포트가 .NET Core를 지원하지 않거나, Windows에 종속되거나, 복잡한 라이선스 체계로 인해 병목 현상이 발생할 때 사용합니다.
  • 기술적으로 중요한 이유: HTML/CSS는 모든 플랫폼에서 동일하게 렌더링되고, CI/CD와 통합되며, 차트를 위해 JavaScript를 실행합니다. 또한, 특정 디자이너나 문서당 비용 없이 모든 기능을 이용할 수 있습니다.

코드 예제를 따라하려면 NuGet을 통해 IronPDF를 설치하세요(Install-Package IronPdf). 단 몇 줄의 코드로 첫 번째 보고서를 생성해 보세요.

Nuget Icon지금 바로 NuGet을 사용하여 PDF 만들기를 시작하세요.

  1. NuGet 패키지 관리자를 사용하여 IronPDF를 설치하세요.

    PM > Install-Package IronPdf

  2. 다음 코드 조각을 복사하여 실행하세요.

    // Install-Package IronPdf
    var pdf = new IronPdf.ChromePdfRenderer()
        .RenderHtmlAsPdf("<h1>Sales Report</h1><table><tr><td>Q1</td><td>$50,000</td></tr></table>")
        .SaveAs("sales-report.pdf");
  3. 실제 운영 환경에서 테스트할 수 있도록 배포하세요.

    지금 바로 무료 체험판을 통해 프로젝트에서 IronPDF를 사용해 보세요.
    arrow pointer

IronPDF를 구매하거나 30일 무료 체험판에 가입한 후, 애플리케이션 시작 부분에 라이선스 키를 입력하세요.

IronPdf.License.LicenseKey = "KEY";
IronPdf.License.LicenseKey = "KEY";
$vbLabelText   $csharpLabel

지금 바로 무료 체험판을 통해 IronPDF을 프로젝트에서 사용해 보세요.

첫 번째 단계:
green arrow pointer
!{--010011000100100101000010010100100100000101010010010110010101111101001110010101010001110100010101010100010111110100100101001110010100110101010001000001010011000100110001001100010111110100001001001100010011110100001101001011--}

목차

요약: 빠른 시작 가이드

C# Report Generator: HTML Templates to PDF

HTML을 PDF로 변환하는 과정은 선형적인 아키텍처 파이프라인을 기반으로 합니다. 독자적인 파일 형식 대신, 애플리케이션은 표준 데이터 모델을 사용하여 Razor 뷰 또는 HTML 템플릿을 채웁니다. 이렇게 생성된 HTML 문자열은 IronPDF와 같은 렌더링 엔진으로 전달되어 시각적 출력을 PDF 문서로 생성합니다. 이 접근 방식은 보고서 디자인을 호스팅 환경에서 분리하여 .NET을 지원하는 모든 플랫폼에서 동일한 코드를 실행할 수 있도록 합니다.

이 워크플로는 표준 웹 개발과 동일합니다. 프런트엔드 개발자는 CSS를 사용하여 레이아웃을 만들고 모든 브라우저에서 즉시 미리 볼 수 있습니다. 그런 다음 백엔드 개발자는 C#을 사용하여 데이터를 바인딩합니다. 이러한 분리를 통해 팀은 기존 버전 관리, 코드 검토 및 지속적 배포 프로세스를 애플리케이션의 나머지 부분과 마찬가지로 보고서에도 사용할 수 있습니다.

HTML을 사용하면 크리스탈 리포트에서 사용할 수 없는 기능(대화형 차트, 반응형 테이블, 일관된 브랜딩을 위한 공유 스타일 등)을 활용할 수 있습니다.

.NET 애플리케이션에서 크리스탈 리포트를 다른 것으로 교체해야 하는 이유는 무엇일까요?

크리스탈 리포트에서 벗어나려는 움직임은 단 하나의 치명적인 문제나 SAP의 갑작스러운 포기 때문이 아닙니다. 오히려 여러 가지 문제점들이 누적되어 새로운 프로젝트에 크리스탈 리포트를 도입하는 것이 점점 더 어려워지고, 기존 솔루션에서 유지 관리 또한 더욱 까다로워지고 있습니다. 이러한 문제점을 파악하면 많은 팀이 대안을 찾는 이유와 대체 옵션을 평가할 때 가장 중요한 기준이 무엇인지 명확해집니다.

.NET 8 또는 .NET Core는 지원하지 않습니다.

크리스탈 리포트는 .NET Core 또는 .NET 5-10을 지원하지 않습니다. SAP는 포럼에서 향후 지원을 추가할 계획이 없다고 밝혔습니다. SDK는 COM 구성 요소를 사용하는데, 이는 크로스 플랫폼 .NET과 호환되지 않습니다. 최신 .NET 지원을 위해서는 전면적인 재작성이 필요하지만, SAP는 이를 거부했습니다.

결과적으로 현재 .NET 버전을 기반으로 새로운 애플리케이션을 개발하는 팀은 크리스탈 리포트를 사용할 수 없습니다. .NET 8 또는 .NET 10을 표준으로 사용하는 조직은 이를 통합할 수 없습니다. 기존 애플리케이션의 경우, 최신 .NET 런타임으로 업그레이드하려면 먼저 보고 시스템을 교체해야 합니다.

복잡한 라이선스 및 숨겨진 비용

크리스탈 리포트 라이선스는 디자이너 라이선스, 런타임 라이선스, 서버 배포 및 임베디드 사용을 구분합니다. 데스크톱, 웹 및 터미널 서비스에 따라 규칙이 다릅니다. 한 환경에서 규정을 준수하려면 다른 환경에서는 추가 라이선스가 필요할 수 있습니다. 배포 후 문제가 발생하면 예상치 못한 비용이 발생합니다. 많은 조직은 이러한 불확실성이 더 큰 손해를 초래하는 것보다 라이선스 체계가 명확한 솔루션으로 전환하는 것이 낫다고 판단합니다.

윈도우 전용 플랫폼 종속

크리스탈 리포트는 기존 .NET Framework가 설치된 Windows에서만 실행됩니다. 이러한 애플리케이션은 Linux 컨테이너, Linux용 Azure App Service, AWS Lambda 또는 Google Cloud Run에 배포할 수 없습니다. 조직들이 컨테이너 기반, 플랫폼에 구애받지 않는, 서버리스 시스템을 사용함에 따라 이러한 제약 조건들이 더욱 중요해집니다.

마이크로서비스를 구축하는 개발팀은 추가적인 어려움에 직면합니다. 만약 9개의 서비스가 경량 Linux 컨테이너에서 실행되지만, 그중 하나가 크리스탈 리포트 사용을 위해 Windows가 필요하다면 배포가 더 복잡해집니다. Windows 컨테이너 이미지, Windows 호환 호스팅 및 별도의 배포 설정이 필요합니다. 보고 서비스가 예외적인 사례가 되어 표준화를 저해합니다.

Set Up a C# Report Generator in .NET 10

IronPDF를 시작하는 것은 간단합니다. 다른 .NET 종속성처럼 NuGet을 통해 라이브러리를 설치하세요. 별도의 소프트웨어를 다운로드하거나 운영 서버에 별도의 런타임 설치 프로그램을 설치할 필요가 없습니다.

템플릿 접근 방식을 선택하세요: Razor, HTML 또는 하이브리드

IronPDF는 보고서 템플릿을 구축하는 세 가지 고유한 접근 방식을 지원합니다. 각 접근 방식은 팀 구성, 프로젝트 요구 사항 및 장기 유지 관리 고려 사항에 따라 특정한 이점을 제공합니다.

Razor Views는 .NET 생태계에서 이미 작업 중인 팀에게 가장 풍부한 개발 경험을 제공합니다. Visual Studio 및 VS Code에서 완벽한 IntelliSense 지원, 컴파일 시간 검사, C#의 for 루프, 조건문, null 처리 및 문자열 서식 지정의 모든 기능을 갖춘 강력한 형식의 모델을 사용할 수 있습니다. Razor의 구문은 ASP.NET Core 애플리케이션을 개발해 본 사람들에게 친숙하므로 다른 생태계의 템플릿 엔진과 관련된 학습 곡선을 없애줍니다. 템플릿은 다른 소스 파일과 함께 프로젝트에 존재하며, 리팩토링 작업에 참여하고, 일반 빌드 프로세스의 일부로 컴파일됩니다.

문자열 보간을 사용한 일반 HTML은 간단한 보고서나 템플릿을 .NET 코드와 완전히 분리하여 유지하려는 팀에 적합합니다. HTML 템플릿은 어셈블리에 컴파일된 임베디드 리소스, 애플리케이션과 함께 배포되는 외부 파일, 또는 런타임에 데이터베이스나 콘텐츠 관리 시스템에서 가져오는 방식으로 저장될 수 있습니다. 기본적인 데이터 바인딩은 단일 값의 경우 string.Replace()를 사용하고, 고급 시나리오의 경우 Scriban이나 Fluid와 같은 경량 템플릿 라이브러리를 사용합니다. 이 접근 방식은 휴대성을 극대화하여 디자이너가 .NET 도구를 설치하지 않고도 텍스트 편집기와 웹 브라우저만 사용하여 템플릿을 편집할 수 있도록 합니다.

하이브리드 접근 방식은 유연성이 요구되는 시나리오를 위해 두 가지 기술을 결합합니다. 예를 들어, Razor 뷰를 렌더링하여 기본 HTML 구조를 생성한 다음, 뷰 모델에 깔끔하게 들어맞지 않는 동적 요소에 대해 추가적인 문자열 대체 작업을 통해 후처리할 수 있습니다. 또는 개발자가 아닌 사람이 디자인한 HTML 템플릿을 로드하고, Razor 부분 뷰를 사용하여 복잡하고 데이터 기반인 섹션만 렌더링한 다음 모든 것을 결합할 수 있습니다. HTML을 PDF로 변환하는 과정은 HTML 소스에 구애받지 않으므로, 각 보고서의 필요에 따라 다양한 방식을 조합하여 사용할 수 있습니다.

이러한 옵션들을 고려할 때, 이 튜토리얼에서는 일반적인 비즈니스 보고 시나리오에 가장 적합한 타입 안정성, 유지 관리 용이성 및 풍부한 기능을 제공하는 Razor 뷰에 주로 초점을 맞춥니다. 향후 일반 HTML 템플릿을 다뤄야 할 필요가 생길 경우, 두 방법 모두 HTML 문자열을 생성하므로 해당 기술은 직접적으로 활용될 수 있습니다.

Build a Data-Driven PDF Report in C#

이 섹션에서는 매출 송장 보고서의 작성 과정을 처음부터 끝까지 자세히 보여줍니다. 이 예제는 모든 보고서에 사용되는 핵심 패턴을 다룹니다. 즉, 데이터를 구조화하는 모델을 정의하고, 데이터를 형식화된 HTML로 변환하는 Razor 템플릿을 생성하고, 해당 템플릿을 HTML 문자열로 렌더링하고, 마지막으로 HTML을 보기, 이메일 전송 또는 보관에 적합한 PDF 문서로 변환하는 것입니다.

HTML/CSS 보고서 템플릿을 생성하세요.

첫 번째 단계는 데이터 모델을 정의하는 것입니다. 실제 청구서에는 고객 정보, 품목별 설명 및 가격, 계산된 총액, 세금 처리, 그리고 회사 브랜딩 요소가 필요합니다. 모델 클래스는 이러한 그룹화를 반영하도록 구성되어야 합니다.

// Invoice data model with customer, company, and line item details
public class InvoiceModel
{
    public string InvoiceNumber { get; set; } = string.Empty;
    public DateTime InvoiceDate { get; set; }
    public DateTime DueDate { get; set; }

    public CompanyInfo Company { get; set; } = new();
    public CustomerInfo Customer { get; set; } = new();
    public List<LineItem> Items { get; set; } = new();

    // Computed totals - business logic stays in the model
    public decimal Subtotal => Items.Sum(x => x.Total);
    public decimal TaxRate { get; set; } = 0.08m;
    public decimal TaxAmount => Subtotal * TaxRate;
    public decimal GrandTotal => Subtotal + TaxAmount;
}

// Company details for invoice header
public class CompanyInfo
{
    public string Name { get; set; } = string.Empty;
    public string Address { get; set; } = string.Empty;
    public string City { get; set; } = string.Empty;
    public string Phone { get; set; } = string.Empty;
    public string Email { get; set; } = string.Empty;
    public string LogoPath { get; set; } = string.Empty;
}

// Customer billing information
public class CustomerInfo
{
    public string Name { get; set; } = string.Empty;
    public string Address { get; set; } = string.Empty;
    public string City { get; set; } = string.Empty;
    public string Email { get; set; } = string.Empty;
}

// Individual invoice line item
public class LineItem
{
    public string Description { get; set; } = string.Empty;
    public int Quantity { get; set; }
    public decimal UnitPrice { get; set; }
    public decimal Total => Quantity * UnitPrice;
}
// Invoice data model with customer, company, and line item details
public class InvoiceModel
{
    public string InvoiceNumber { get; set; } = string.Empty;
    public DateTime InvoiceDate { get; set; }
    public DateTime DueDate { get; set; }

    public CompanyInfo Company { get; set; } = new();
    public CustomerInfo Customer { get; set; } = new();
    public List<LineItem> Items { get; set; } = new();

    // Computed totals - business logic stays in the model
    public decimal Subtotal => Items.Sum(x => x.Total);
    public decimal TaxRate { get; set; } = 0.08m;
    public decimal TaxAmount => Subtotal * TaxRate;
    public decimal GrandTotal => Subtotal + TaxAmount;
}

// Company details for invoice header
public class CompanyInfo
{
    public string Name { get; set; } = string.Empty;
    public string Address { get; set; } = string.Empty;
    public string City { get; set; } = string.Empty;
    public string Phone { get; set; } = string.Empty;
    public string Email { get; set; } = string.Empty;
    public string LogoPath { get; set; } = string.Empty;
}

// Customer billing information
public class CustomerInfo
{
    public string Name { get; set; } = string.Empty;
    public string Address { get; set; } = string.Empty;
    public string City { get; set; } = string.Empty;
    public string Email { get; set; } = string.Empty;
}

// Individual invoice line item
public class LineItem
{
    public string Description { get; set; } = string.Empty;
    public int Quantity { get; set; }
    public decimal UnitPrice { get; set; }
    public decimal Total => Quantity * UnitPrice;
}
$vbLabelText   $csharpLabel

소계, 세액 및 총계에 대한 계산된 속성이 모델에 포함됩니다. 이러한 계산은 템플릿이 아닌 모델에 포함되어야 하며, 이를 통해 Razor 뷰는 프레젠테이션에 집중하고 모델은 비즈니스 로직을 처리하게 됩니다. 이러한 분리 덕분에 단위 테스트가 간편해지며, HTML을 렌더링하지 않고도 계산 결과를 검증할 수 있습니다.

이제 이 모델을 전문적인 형식의 송장으로 변환하는 Razor 뷰를 생성하세요. 이 뷰를 Views 폴더에 InvoiceTemplate.cshtml라는 이름으로 저장하세요.

@model InvoiceModel
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <style>
        /* Reset and base styles */
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body { font-family: 'Segoe UI', Arial, sans-serif; font-size: 12px; color: #333; line-height: 1.5; }
        .invoice-container { max-width: 800px; margin: 0 auto; padding: 40px; }

        /* Header with company info and invoice title */
        .header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 40px; padding-bottom: 20px; border-bottom: 2px solid #3498db; }
        .company-info h1 { font-size: 24px; color: #2c3e50; margin-bottom: 10px; }
        .company-info p { color: #7f8c8d; font-size: 11px; }
        .invoice-title { text-align: right; }
        .invoice-title h2 { font-size: 32px; color: #3498db; margin-bottom: 10px; }
        .invoice-title p { font-size: 12px; color: #7f8c8d; }

        /* Address blocks */
        .addresses { display: flex; justify-content: space-between; margin-bottom: 30px; }
        .address-block { width: 45%; }
        .address-block h3 { font-size: 11px; text-transform: uppercase; color: #95a5a6; margin-bottom: 8px; letter-spacing: 1px; }
        .address-block p { font-size: 12px; }

        /* Line items table */
        .items-table { width: 100%; border-collapse: collapse; margin-bottom: 30px; }
        .items-table th { background-color: #3498db; color: white; padding: 12px; text-align: left; font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; }
        .items-table th:last-child, .items-table td:last-child { text-align: right; }
        .items-table td { padding: 12px; border-bottom: 1px solid #ecf0f1; }
        .items-table tr:nth-child(even) { background-color: #f9f9f9; }

        /* Totals section */
        .totals { float: right; width: 300px; }
        .totals-row { display: flex; justify-content: space-between; padding: 8px 0; border-bottom: 1px solid #ecf0f1; }
        .totals-row.grand-total { border-bottom: none; border-top: 2px solid #3498db; font-size: 16px; font-weight: bold; color: #2c3e50; padding-top: 12px; }

        /* Footer */
        .footer { clear: both; margin-top: 60px; padding-top: 20px; border-top: 1px solid #ecf0f1; text-align: center; color: #95a5a6; font-size: 10px; }
    </style>
</head>
<body>
    <div class="invoice-container">

        <div class="header">
            <div class="company-info">
                <h1>@Model.Company.Name</h1>
                <p>@Model.Company.Address</p>
                <p>@Model.Company.City</p>
                <p>@Model.Company.Phone | @Model.Company.Email</p>
            </div>
            <div class="invoice-title">
                <h2>INVOICE</h2>
                <p>Invoice #: @Model.InvoiceNumber</p>
                <p>Date: @Model.InvoiceDate.ToString("MMMM dd, yyyy")</p>
                <p>Due Date: @Model.DueDate.ToString("MMMM dd, yyyy")</p>
            </div>
        </div>

        <div class="addresses">
            <div class="address-block">
                <h3>Bill To</h3>
                <p>@Model.Customer.Name</p>
                <p>@Model.Customer.Address</p>
                <p>@Model.Customer.City</p>
                <p>@Model.Customer.Email</p>
            </div>
        </div>

        <table class="items-table">
            <thead>
                <tr><th>Description</th><th>Quantity</th><th>Unit Price</th><th>Total</th></tr>
            </thead>
            <tbody>
                @foreach (var item in Model.Items)
                {
                    <tr>
                        <td>@item.Description</td>
                        <td>@item.Quantity</td>
                        <td>@item.UnitPrice.ToString("C")</td>
                        <td>@item.Total.ToString("C")</td>
                    </tr>
                }
            </tbody>
        </table>

        <div class="totals">
            <div class="totals-row"><span>Subtotal:</span><span>@Model.Subtotal.ToString("C")</span></div>
            <div class="totals-row"><span>Tax (@(Model.TaxRate * 100)%):</span><span>@Model.TaxAmount.ToString("C")</span></div>
            <div class="totals-row grand-total"><span>Total Due:</span><span>@Model.GrandTotal.ToString("C")</span></div>
        </div>

        <div class="footer">
            <p>Thank you for your business!</p>
            <p>Payment is due within 30 days. Please include invoice number with your payment.</p>
        </div>
    </div>
</body>
</html>
@model InvoiceModel
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <style>
        /* Reset and base styles */
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body { font-family: 'Segoe UI', Arial, sans-serif; font-size: 12px; color: #333; line-height: 1.5; }
        .invoice-container { max-width: 800px; margin: 0 auto; padding: 40px; }

        /* Header with company info and invoice title */
        .header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 40px; padding-bottom: 20px; border-bottom: 2px solid #3498db; }
        .company-info h1 { font-size: 24px; color: #2c3e50; margin-bottom: 10px; }
        .company-info p { color: #7f8c8d; font-size: 11px; }
        .invoice-title { text-align: right; }
        .invoice-title h2 { font-size: 32px; color: #3498db; margin-bottom: 10px; }
        .invoice-title p { font-size: 12px; color: #7f8c8d; }

        /* Address blocks */
        .addresses { display: flex; justify-content: space-between; margin-bottom: 30px; }
        .address-block { width: 45%; }
        .address-block h3 { font-size: 11px; text-transform: uppercase; color: #95a5a6; margin-bottom: 8px; letter-spacing: 1px; }
        .address-block p { font-size: 12px; }

        /* Line items table */
        .items-table { width: 100%; border-collapse: collapse; margin-bottom: 30px; }
        .items-table th { background-color: #3498db; color: white; padding: 12px; text-align: left; font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; }
        .items-table th:last-child, .items-table td:last-child { text-align: right; }
        .items-table td { padding: 12px; border-bottom: 1px solid #ecf0f1; }
        .items-table tr:nth-child(even) { background-color: #f9f9f9; }

        /* Totals section */
        .totals { float: right; width: 300px; }
        .totals-row { display: flex; justify-content: space-between; padding: 8px 0; border-bottom: 1px solid #ecf0f1; }
        .totals-row.grand-total { border-bottom: none; border-top: 2px solid #3498db; font-size: 16px; font-weight: bold; color: #2c3e50; padding-top: 12px; }

        /* Footer */
        .footer { clear: both; margin-top: 60px; padding-top: 20px; border-top: 1px solid #ecf0f1; text-align: center; color: #95a5a6; font-size: 10px; }
    </style>
</head>
<body>
    <div class="invoice-container">

        <div class="header">
            <div class="company-info">
                <h1>@Model.Company.Name</h1>
                <p>@Model.Company.Address</p>
                <p>@Model.Company.City</p>
                <p>@Model.Company.Phone | @Model.Company.Email</p>
            </div>
            <div class="invoice-title">
                <h2>INVOICE</h2>
                <p>Invoice #: @Model.InvoiceNumber</p>
                <p>Date: @Model.InvoiceDate.ToString("MMMM dd, yyyy")</p>
                <p>Due Date: @Model.DueDate.ToString("MMMM dd, yyyy")</p>
            </div>
        </div>

        <div class="addresses">
            <div class="address-block">
                <h3>Bill To</h3>
                <p>@Model.Customer.Name</p>
                <p>@Model.Customer.Address</p>
                <p>@Model.Customer.City</p>
                <p>@Model.Customer.Email</p>
            </div>
        </div>

        <table class="items-table">
            <thead>
                <tr><th>Description</th><th>Quantity</th><th>Unit Price</th><th>Total</th></tr>
            </thead>
            <tbody>
                @foreach (var item in Model.Items)
                {
                    <tr>
                        <td>@item.Description</td>
                        <td>@item.Quantity</td>
                        <td>@item.UnitPrice.ToString("C")</td>
                        <td>@item.Total.ToString("C")</td>
                    </tr>
                }
            </tbody>
        </table>

        <div class="totals">
            <div class="totals-row"><span>Subtotal:</span><span>@Model.Subtotal.ToString("C")</span></div>
            <div class="totals-row"><span>Tax (@(Model.TaxRate * 100)%):</span><span>@Model.TaxAmount.ToString("C")</span></div>
            <div class="totals-row grand-total"><span>Total Due:</span><span>@Model.GrandTotal.ToString("C")</span></div>
        </div>

        <div class="footer">
            <p>Thank you for your business!</p>
            <p>Payment is due within 30 days. Please include invoice number with your payment.</p>
        </div>
    </div>
</body>
</html>
HTML

이 템플릿에 포함된 CSS는 색상, 글꼴, 간격 및 표 서식과 같은 모든 시각적 스타일을 처리합니다. IronPDF는 flexbox, 그리드 레이아웃, CSS 변수와 같은 최신 CSS 기능도 지원합니다. 렌더링된 PDF는 Chrome의 인쇄 미리보기와 정확히 일치하므로 디버깅이 간편합니다. PDF에 오류가 있는 경우 브라우저에서 HTML을 열고 개발자 도구를 사용하여 스타일을 검사하고 조정할 수 있습니다.

템플릿에 데이터 바인딩

모델과 템플릿이 준비되면 IronPDF의 ChromePdfRenderer를 통해 이들을 연결하여 PDF를 렌더링해야 합니다. 핵심 단계는 Razor 뷰를 HTML 문자열로 변환한 다음, 해당 문자열을 렌더러에 전달하는 것입니다.

using IronPdf;

// Service class for generating invoice PDFs from Razor views
public class InvoiceReportService
{
    private readonly IRazorViewEngine _razorViewEngine;
    private readonly ITempDataProvider _tempDataProvider;
    private readonly IServiceProvider _serviceProvider;

    public InvoiceReportService(
        IRazorViewEngine razorViewEngine,
        ITempDataProvider tempDataProvider,
        IServiceProvider serviceProvider)
    {
        _razorViewEngine = razorViewEngine;
        _tempDataProvider = tempDataProvider;
        _serviceProvider = serviceProvider;
    }

    // Generate PDF from invoice model
    public async Task<byte[]> GenerateInvoicePdfAsync(InvoiceModel invoice)
    {
        // Render Razor view to HTML string
        string html = await RenderViewToStringAsync("InvoiceTemplate", invoice);

        // Configure PDF renderer with margins and paper size
        var renderer = new ChromePdfRenderer();
        renderer.RenderingOptions.MarginTop = 10;
        renderer.RenderingOptions.MarginBottom = 10;
        renderer.RenderingOptions.MarginLeft = 10;
        renderer.RenderingOptions.MarginRight = 10;
        renderer.RenderingOptions.PaperSize = IronPdf.Rendering.PdfPaperSize.Letter;

        // Convert HTML to PDF and return bytes
        var pdfDocument = renderer.RenderHtmlAsPdf(html);
        return pdfDocument.BinaryData;
    }

    // Helper method to render a Razor view to string
    private async Task<string> RenderViewToStringAsync<TModel>(string viewName, TModel model)
    {
        var httpContext = new DefaultHttpContext { RequestServices = _serviceProvider };
        var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());

        using var stringWriter = new StringWriter();
        var viewResult = _razorViewEngine.FindView(actionContext, viewName, false);

        if (!viewResult.Success)
            throw new InvalidOperationException($"View '{viewName}' not found.");

        var viewDictionary = new ViewDataDictionary<TModel>(
            new EmptyModelMetadataProvider(), new ModelStateDictionary()) { Model = model };

        var viewContext = new ViewContext(actionContext, viewResult.View, viewDictionary,
            new TempDataDictionary(actionContext.HttpContext, _tempDataProvider),
            stringWriter, new HtmlHelperOptions());

        await viewResult.View.RenderAsync(viewContext);
        return stringWriter.ToString();
    }
}
using IronPdf;

// Service class for generating invoice PDFs from Razor views
public class InvoiceReportService
{
    private readonly IRazorViewEngine _razorViewEngine;
    private readonly ITempDataProvider _tempDataProvider;
    private readonly IServiceProvider _serviceProvider;

    public InvoiceReportService(
        IRazorViewEngine razorViewEngine,
        ITempDataProvider tempDataProvider,
        IServiceProvider serviceProvider)
    {
        _razorViewEngine = razorViewEngine;
        _tempDataProvider = tempDataProvider;
        _serviceProvider = serviceProvider;
    }

    // Generate PDF from invoice model
    public async Task<byte[]> GenerateInvoicePdfAsync(InvoiceModel invoice)
    {
        // Render Razor view to HTML string
        string html = await RenderViewToStringAsync("InvoiceTemplate", invoice);

        // Configure PDF renderer with margins and paper size
        var renderer = new ChromePdfRenderer();
        renderer.RenderingOptions.MarginTop = 10;
        renderer.RenderingOptions.MarginBottom = 10;
        renderer.RenderingOptions.MarginLeft = 10;
        renderer.RenderingOptions.MarginRight = 10;
        renderer.RenderingOptions.PaperSize = IronPdf.Rendering.PdfPaperSize.Letter;

        // Convert HTML to PDF and return bytes
        var pdfDocument = renderer.RenderHtmlAsPdf(html);
        return pdfDocument.BinaryData;
    }

    // Helper method to render a Razor view to string
    private async Task<string> RenderViewToStringAsync<TModel>(string viewName, TModel model)
    {
        var httpContext = new DefaultHttpContext { RequestServices = _serviceProvider };
        var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());

        using var stringWriter = new StringWriter();
        var viewResult = _razorViewEngine.FindView(actionContext, viewName, false);

        if (!viewResult.Success)
            throw new InvalidOperationException($"View '{viewName}' not found.");

        var viewDictionary = new ViewDataDictionary<TModel>(
            new EmptyModelMetadataProvider(), new ModelStateDictionary()) { Model = model };

        var viewContext = new ViewContext(actionContext, viewResult.View, viewDictionary,
            new TempDataDictionary(actionContext.HttpContext, _tempDataProvider),
            stringWriter, new HtmlHelperOptions());

        await viewResult.View.RenderAsync(viewContext);
        return stringWriter.ToString();
    }
}
$vbLabelText   $csharpLabel

콘솔 앱이나 백그라운드 서비스처럼 전체 ASP.NET Core MVC 설정이 필요하지 않은 간단한 시나리오에서는 HTML 문자열과 문자열 보간, 그리고 동적 부분에는 StringBuilder를 사용할 수 있습니다.

샘플 출력

머리글, 바닥글 및 페이지 번호 추가

전문 보고서는 일반적으로 모든 페이지에 걸쳐 일관된 머리글과 바닥글을 포함하며, 회사 브랜드, 문서 제목, 작성 날짜 및 페이지 번호를 표시합니다. IronPDF는 이러한 요소를 구현하기 위한 두 가지 접근 방식을 제공합니다. 최소한의 서식만 필요한 간단한 콘텐츠에는 텍스트 기반 헤더를, 로고 및 사용자 지정 레이아웃을 포함한 완벽한 스타일 제어를 위해서는 HTML 헤더를 사용할 수 있습니다.

텍스트 기반 헤더는 기본적인 정보를 표시하는 데 적합하며, 추가적인 HTML 파싱이 필요하지 않으므로 렌더링 속도가 더 빠릅니다.

:path=/static-assets/pdf/content-code-examples/tutorials/crystal-reports-alternative-csharp/text-headers-footers.cs
using IronPdf;
using IronSoftware.Drawing;

// Configure text-based headers and footers
var renderer = new ChromePdfRenderer();

// Set starting page number
renderer.RenderingOptions.FirstPageNumber = 1;

// Add centered header with divider line
renderer.RenderingOptions.TextHeader = new TextHeaderFooter
{
    CenterText = "CONFIDENTIAL - Internal Use Only",
    DrawDividerLine = true,
    Font = FontTypes.Arial,
    FontSize = 10
};

// Add footer with date on left, page numbers on right
renderer.RenderingOptions.TextFooter = new TextHeaderFooter
{
    LeftText = "{date} {time}",
    RightText = "Page {page} of {total-pages}",
    DrawDividerLine = true,
    Font = FontTypes.Arial,
    FontSize = 9
};

// Set margins to accommodate header/footer
renderer.RenderingOptions.MarginTop = 25;
renderer.RenderingOptions.MarginBottom = 20;
$vbLabelText   $csharpLabel

병합 가능한 필드에는 현재 페이지 번호의 경우 {page}, 문서의 총 페이지 수의 경우 {total-pages}, 생성 타임스탬프의 경우 {date}{time}, 웹 페이지에서 렌더링하는 경우 소스 URL의 경우 {url}, 문서 제목의 경우 {html-title}{pdf-title}가 포함됩니다. 이러한 자리 표시자는 렌더링 중에 자동으로 대체됩니다.

로고, 사용자 지정 글꼴 또는 복잡한 다단 레이아웃이 포함된 헤더의 경우, CSS 스타일링을 완벽하게 지원하는 HTML 헤더를 사용하십시오.

:path=/static-assets/pdf/content-code-examples/tutorials/crystal-reports-alternative-csharp/html-headers-footers.cs
using IronPdf;
using System;

var renderer = new ChromePdfRenderer();

// Configure HTML header with logo and custom layout
renderer.RenderingOptions.HtmlHeader = new HtmlHeaderFooter
{
    MaxHeight = 30,
    HtmlFragment = @"
<div style='display: flex; justify-content: space-between; align-items: center;
            width: 100%; font-family: Arial; font-size: 10px; color: #666;'>
    <img src='logo.png' style='height: 25px;'>
    <span>Company Name Inc.</span>
    <span>Invoice Report</span>
</div>",
    BaseUrl = new Uri(@"C:\assets\images\").AbsoluteUri
};

// Configure HTML footer with page info and generation date
renderer.RenderingOptions.HtmlFooter = new HtmlHeaderFooter
{
    MaxHeight = 20,
    HtmlFragment = @"
<div style='text-align: center; font-size: 9px; color: #999;
            border-top: 1px solid #ddd; padding-top: 5px;'>
    Page {page} of {total-pages} | Generated on {date}
</div>",
    DrawDividerLine = false
};
$vbLabelText   $csharpLabel

샘플 출력

동적 테이블 및 반복 섹션 생성

보고서에는 여러 페이지에 걸쳐 있는 데이터 모음을 표시해야 하는 경우가 많습니다. Razor의 반복문 구조는 컬렉션을 순회하고 각 항목에 대해 테이블 행이나 카드 요소를 생성함으로써 이를 자연스럽게 처리합니다.

다음은 부서별 섹션으로 데이터를 그룹화하여 보여주는 전체 직원 명부 예시입니다.

// Employee directory data models
public class EmployeeDirectoryModel
{
    public List<Department> Departments { get; set; } = new();
    public DateTime GeneratedDate { get; set; } = DateTime.Now;
}

// Department grouping with manager info
public class Department
{
    public string Name { get; set; } = string.Empty;
    public string ManagerName { get; set; } = string.Empty;
    public List<Employee> Employees { get; set; } = new();
}

// Individual employee details
public class Employee
{
    public string Name { get; set; } = string.Empty;
    public string Title { get; set; } = string.Empty;
    public string Email { get; set; } = string.Empty;
    public string Phone { get; set; } = string.Empty;
    public string PhotoUrl { get; set; } = string.Empty;
    public DateTime HireDate { get; set; }
}
// Employee directory data models
public class EmployeeDirectoryModel
{
    public List<Department> Departments { get; set; } = new();
    public DateTime GeneratedDate { get; set; } = DateTime.Now;
}

// Department grouping with manager info
public class Department
{
    public string Name { get; set; } = string.Empty;
    public string ManagerName { get; set; } = string.Empty;
    public List<Employee> Employees { get; set; } = new();
}

// Individual employee details
public class Employee
{
    public string Name { get; set; } = string.Empty;
    public string Title { get; set; } = string.Empty;
    public string Email { get; set; } = string.Empty;
    public string Phone { get; set; } = string.Empty;
    public string PhotoUrl { get; set; } = string.Empty;
    public DateTime HireDate { get; set; }
}
$vbLabelText   $csharpLabel

부서 클래스의 CSS 속성 page-break-inside: avoid는 PDF 렌더링 프로그램에게 가능한 경우 부서 섹션을 한 페이지에 함께 유지하도록 지시합니다. 만약 어떤 부서의 콘텐츠로 인해 섹션 중간에 페이지 나누기가 발생할 경우, 렌더링 프로그램은 해당 섹션 전체를 다음 페이지로 이동시킵니다. 선택자 .department:not(:first-child)page-break-before: always를 함께 사용하면 첫 번째 부서 이후의 각 부서가 새 페이지에서 시작되도록 하여 디렉토리 전체에 깔끔한 섹션 구분을 만듭니다.

샘플 출력

Advanced C# Report Generation With IronPDF

비즈니스 보고서는 정적인 표와 텍스트 이상의 기능을 요구하는 경우가 많습니다. 차트는 표 형식으로 이해하기 어려운 추세를 시각화합니다. 조건부 서식은 조치가 필요한 항목에 주의를 집중시킵니다. 하위 보고서는 여러 출처의 데이터를 결합하여 일관성 있는 문서로 만듭니다. 이 섹션에서는 IronPDF의 Chromium 렌더링 엔진을 사용하여 이러한 각 기능을 구현하는 방법을 설명합니다.

PDF 보고서에 차트 및 그래프 추가

JavaScript는 렌더링 중에 실행되므로 클라이언트 측 차트 라이브러리를 사용하여 보고서에 직접 시각화를 생성할 수 있습니다. 차트는 페이지의 일부로 래스터화되어 최종 PDF에 화면에 표시되는 것과 똑같이 나타납니다. Chart.js는 대부분의 보고 요구 사항에 대해 단순성, 기능 및 문서화 측면에서 탁월한 균형을 제공합니다.

CDN에서 Chart.js를 포함하고 C# 모델에서 직렬화된 데이터로 차트를 구성하세요.

@model SalesReportModel

<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>

<canvas id="salesChart"></canvas>

<script>
    // Initialize bar chart with data from C# model
    const ctx = document.getElementById('salesChart').getContext('2d');
    new Chart(ctx, {
        type: 'bar',
        data: {
            // Serialize model data to JavaScript arrays
            labels: @Html.Raw(System.Text.Json.JsonSerializer.Serialize(Model.MonthLabels)),
            datasets: [{
                label: 'Monthly Sales',
                data: @Html.Raw(System.Text.Json.JsonSerializer.Serialize(Model.MonthlySales)),
                backgroundColor: 'rgba(52, 152, 219, 0.7)'
            }]
        }
    });
</script>
@model SalesReportModel

<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>

<canvas id="salesChart"></canvas>

<script>
    // Initialize bar chart with data from C# model
    const ctx = document.getElementById('salesChart').getContext('2d');
    new Chart(ctx, {
        type: 'bar',
        data: {
            // Serialize model data to JavaScript arrays
            labels: @Html.Raw(System.Text.Json.JsonSerializer.Serialize(Model.MonthLabels)),
            datasets: [{
                label: 'Monthly Sales',
                data: @Html.Raw(System.Text.Json.JsonSerializer.Serialize(Model.MonthlySales)),
                backgroundColor: 'rgba(52, 152, 219, 0.7)'
            }]
        }
    });
</script>
HTML

JavaScript로 생성된 콘텐츠가 포함된 페이지를 렌더링할 때, 스크립트 실행이 완료될 때까지 기다린 후 페이지를 캡처하도록 렌더러를 구성하십시오.

:path=/static-assets/pdf/content-code-examples/tutorials/crystal-reports-alternative-csharp/javascript-wait-rendering.cs
using IronPdf;

string html = "<h1>Report</h1>";

// Configure renderer to wait for JavaScript execution
var renderer = new ChromePdfRenderer();
renderer.RenderingOptions.WaitFor.JavaScript(500); // Wait 500ms for JS to complete
var pdf = renderer.RenderHtmlAsPdf(html);
$vbLabelText   $csharpLabel

샘플 출력

조건부 서식 및 비즈니스 로직 적용

재고 보고서는 조치가 필요한 항목을 즉시 파악할 수 있도록 시각적 표시기를 활용하면 효과적입니다. 조건부 서식을 사용하면 사용자가 수백 개의 행을 일일이 살펴보고 문제를 찾아야 하는 대신 예외 사항을 시각적으로 명확하게 표시할 수 있습니다. Razor의 인라인 표현식을 사용하여 데이터 값에 따라 CSS 클래스를 적용하세요.


@foreach (var item in Model.Items.OrderBy(x => x.Quantity))
{
    // Apply CSS class based on stock level thresholds
    var rowClass = item.Quantity <= Model.CriticalStockThreshold ? "stock-critical" :
                   item.Quantity <= Model.LowStockThreshold ? "stock-low" : "";

    <tr class="@rowClass">
        <td>@item.SKU</td>
        <td>@item.ProductName</td>
        <td class="text-right">

            <span class="quantity-badge @(item.Quantity <= 5 ? "badge-critical" : "badge-ok")">
                @item.Quantity
            </span>
        </td>
    </tr>
}

@foreach (var item in Model.Items.OrderBy(x => x.Quantity))
{
    // Apply CSS class based on stock level thresholds
    var rowClass = item.Quantity <= Model.CriticalStockThreshold ? "stock-critical" :
                   item.Quantity <= Model.LowStockThreshold ? "stock-low" : "";

    <tr class="@rowClass">
        <td>@item.SKU</td>
        <td>@item.ProductName</td>
        <td class="text-right">

            <span class="quantity-badge @(item.Quantity <= 5 ? "badge-critical" : "badge-ok")">
                @item.Quantity
            </span>
        </td>
    </tr>
}
HTML

샘플 출력

하위 보고서 및 섹션 구분 생성

개별적으로 생성된 보고서를 하나의 문서로 결합하려면 IronPDF의 병합 기능을 사용하십시오.

using IronPdf;

// Combine multiple reports into a single PDF document
public byte[] GenerateCombinedReport(SalesReportModel sales, InventoryReportModel inventory)
{
    var renderer = new ChromePdfRenderer();

    // Render each report section separately
    var salesPdf = renderer.RenderHtmlAsPdf(RenderSalesReport(sales));
    var inventoryPdf = renderer.RenderHtmlAsPdf(RenderInventoryReport(inventory));

    // Merge PDFs into one document
    var combined = PdfDocument.Merge(salesPdf, inventoryPdf);
    return combined.BinaryData;
}
using IronPdf;

// Combine multiple reports into a single PDF document
public byte[] GenerateCombinedReport(SalesReportModel sales, InventoryReportModel inventory)
{
    var renderer = new ChromePdfRenderer();

    // Render each report section separately
    var salesPdf = renderer.RenderHtmlAsPdf(RenderSalesReport(sales));
    var inventoryPdf = renderer.RenderHtmlAsPdf(RenderInventoryReport(inventory));

    // Merge PDFs into one document
    var combined = PdfDocument.Merge(salesPdf, inventoryPdf);
    return combined.BinaryData;
}
$vbLabelText   $csharpLabel

샘플 출력

목차 생성

IronPDF는 HTML의 제목 요소를 기반으로 목차를 자동으로 생성할 수 있습니다.

:path=/static-assets/pdf/content-code-examples/tutorials/crystal-reports-alternative-csharp/table-of-contents.cs
using IronPdf;

// Generate PDF with automatic table of contents
var renderer = new ChromePdfRenderer();
renderer.RenderingOptions.TableOfContents = TableOfContentsTypes.WithPageNumbers;
var pdf = renderer.RenderHtmlFileAsPdf("report.html");
$vbLabelText   $csharpLabel

크리스탈 리포트에서 IronPDF로 마이그레이션

기존 보고 시스템을 이전하려면 혼란을 최소화하면서 현대화 및 간소화의 기회를 포착할 수 있도록 신중한 계획이 필요합니다. 원본 보고서의 모든 기능을 그대로 복제하거나 모든 특징을 유지하려고 하기보다는, 크리스탈 리포트의 개념이 HTML 기반 접근 방식에 어떻게 적용되는지 이해하는 것이 작업 속도를 높이는 데 도움이 됩니다.

크리스탈 리포트 개념을 IronPDF에 매핑

개념적 매핑을 이해하면 기존 보고서를 체계적으로 번역하는 데 도움이 됩니다.

크리스탈 리포트 IronPDF 동등품
보고서 섹션 CSS 페이지 나누기 속성을 가진 HTML div
매개변수 필드 Razor 뷰로 전달되는 모델 속성
수식 필드 C# 모델 클래스의 계산 속성
누적 합계 LINQ 집계
하위 보고서 부분 보기 또는 병합된 PDF 문서
그룹화/정렬 템플릿에 데이터를 전달하기 전에 LINQ 작업을 수행합니다.
교차표 보고서 중첩 루프를 사용한 HTML 테이블
조건부 서식 Razor의 @if 블록은 CSS 클래스와 함께 사용됩니다.

.rpt 템플릿 변환을 위한 최적의 전략

.rpt 파일을 프로그램적으로 분석하려고 시도하지 마십시오. 대신 기존 PDF 출력물을 시각적 사양으로 간주하고 체계적인 4단계 전략을 사용하여 논리를 재구축하십시오.

  1. 목록 작성: 모든 .rpt 파일의 용도, 데이터 출처 및 사용 빈도를 명시하여 목록을 작성합니다. 마이그레이션 범위를 줄이기 위해 더 이상 필요 없는 보고서를 삭제합니다.

  2. 우선순위 지정: 자주 실행되는 보고서부터 먼저 마이그레이션합니다. 레이아웃이 단순하거나 유지 관리 문제가 지속적으로 발생하는 보고서를 대상으로 마이그레이션을 진행합니다.

  3. 참고: 기존 크리스탈 리포트를 PDF로 내보내기. 개발자가 이를 참고하여 일치시킬 수 있도록 시각적 사양으로 활용하세요.

  4. 검증: 실제 운영 데이터 볼륨으로 테스트합니다. 10개 행일 때는 즉시 렌더링되는 템플릿도 10,000개 행이 되면 속도가 느려질 수 있습니다.

.NET을 이용한 일괄 보고서 생성 및 예약 실행

운영 시스템은 종종 여러 보고서를 동시에 생성하거나 보고서 작업을 예약된 시간에 실행해야 합니다. IronPDF의 스레드 안전 설계는 두 가지 시나리오 모두를 효율적으로 지원합니다.

여러 보고서를 동시에 생성

일괄 처리를 위해서는 Parallel.ForEachAsync 또는 Task.WhenAll를 사용한 비동기 패턴을 사용하십시오.

using IronPdf;
using System.Collections.Concurrent;

// Generate multiple invoices in parallel using async processing
public async Task<List<ReportResult>> GenerateInvoiceBatchAsync(List<InvoiceModel> invoices)
{
    var results = new ConcurrentBag<ReportResult>();

    // Process invoices concurrently with controlled parallelism
    await Parallel.ForEachAsync(invoices,
        new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount },
        async (invoice, token) =>
        {
            // Each thread gets its own renderer instance
            var renderer = new ChromePdfRenderer();
            string html = BuildInvoiceHtml(invoice);
            var pdf = await renderer.RenderHtmlAsPdfAsync(html);

            // Save individual invoice PDF
            string filename = $"Invoice_{invoice.InvoiceNumber}.pdf";
            await pdf.SaveAsAsync(filename);

            results.Add(new ReportResult { InvoiceNumber = invoice.InvoiceNumber, Success = true });
        });

    return results.ToList();
}
using IronPdf;
using System.Collections.Concurrent;

// Generate multiple invoices in parallel using async processing
public async Task<List<ReportResult>> GenerateInvoiceBatchAsync(List<InvoiceModel> invoices)
{
    var results = new ConcurrentBag<ReportResult>();

    // Process invoices concurrently with controlled parallelism
    await Parallel.ForEachAsync(invoices,
        new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount },
        async (invoice, token) =>
        {
            // Each thread gets its own renderer instance
            var renderer = new ChromePdfRenderer();
            string html = BuildInvoiceHtml(invoice);
            var pdf = await renderer.RenderHtmlAsPdfAsync(html);

            // Save individual invoice PDF
            string filename = $"Invoice_{invoice.InvoiceNumber}.pdf";
            await pdf.SaveAsAsync(filename);

            results.Add(new ReportResult { InvoiceNumber = invoice.InvoiceNumber, Success = true });
        });

    return results.ToList();
}
$vbLabelText   $csharpLabel

샘플 출력

일괄 처리 예시에서는 여러 개의 송장을 동시에 생성합니다. 다음은 생성된 일괄 청구서 중 하나입니다.

ASP.NET Core 백그라운드 서비스와 보고서 생성 기능 통합

예약된 보고서 생성은 ASP.NET Core의 호스팅 서비스 인프라에 자연스럽게 통합됩니다.

// Background service for scheduled report generation
public class DailyReportService : BackgroundService
{
    private readonly IServiceProvider _serviceProvider;

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            // Calculate next run time (6 AM daily)
            var nextRun = DateTime.Now.Date.AddDays(1).AddHours(6);
            await Task.Delay(nextRun - DateTime.Now, stoppingToken);

            // Create scoped service for report generation
            using var scope = _serviceProvider.CreateScope();
            var reportService = scope.ServiceProvider.GetRequiredService<IReportGenerationService>();

            // Generate and distribute daily report
            var salesReport = await reportService.GenerateDailySalesSummaryAsync();
            // Email or save reports as needed
        }
    }
}
// Background service for scheduled report generation
public class DailyReportService : BackgroundService
{
    private readonly IServiceProvider _serviceProvider;

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            // Calculate next run time (6 AM daily)
            var nextRun = DateTime.Now.Date.AddDays(1).AddHours(6);
            await Task.Delay(nextRun - DateTime.Now, stoppingToken);

            // Create scoped service for report generation
            using var scope = _serviceProvider.CreateScope();
            var reportService = scope.ServiceProvider.GetRequiredService<IReportGenerationService>();

            // Generate and distribute daily report
            var salesReport = await reportService.GenerateDailySalesSummaryAsync();
            // Email or save reports as needed
        }
    }
}
$vbLabelText   $csharpLabel

전체 테스트 프로젝트를 다운로드하세요

이 튜토리얼의 모든 코드 예제는 바로 실행 가능한 .NET 10 테스트 프로젝트에서 사용할 수 있습니다. 다운로드 에는 전체 소스 코드, 데이터 모델, HTML 템플릿 및 위에 표시된 모든 샘플 PDF를 생성하는 테스트 실행기가 포함되어 있습니다.

다음 단계

이 가이드의 예시들을 통해 IronPDF가 간단한 품목별 청구서 와 총액, 그룹화된 데이터와 사진이 포함된 복잡한 직원 명부, 조건부 서식과 차트가 포함된 재고 보고서, 수백 개의 문서를 동시에 처리하는 일괄 처리, 백그라운드 서비스를 통한 예약 생성 등 다양한 비즈니스 보고 요구 사항을 처리할 수 있음을 보여줍니다.

기존 크리스탈 리포트 구현에 대한 대안을 평가하고 있다면, 가장 중요한 보고서 하나부터 시작해 보세요. 여기에 제시된 HTML-PDF 변환 패턴을 사용하여 다시 구축하고, 개발 경험과 출력 품질을 비교한 다음, 이를 바탕으로 기능을 확장하십시오. 많은 팀들이 패턴과 기본 템플릿을 정립하는 데 몇 시간이 걸리면서 첫 번째 변환 보고서를 작성하는 데 어려움을 느끼지만, 이후 보고서들은 Razor 템플릿 과 스타일을 재사용하여 몇 분 만에 작성할 수 있다는 것을 알게 됩니다. 레이아웃의 정확성을 위해 픽셀 단위로 정확하게 렌더링하는 방법을 설명하는 가이드에서는 크리스탈 리포트의 출력 결과를 CSS와 정확히 일치시키는 방법을 다룹니다.

건축을 시작할 준비가 되셨나요? IronPDF를 다운로드 하고 무료 체험판을 사용해 보세요. 이 라이브러리는 단일 보고서 렌더링부터 .NET 환경 전반에 걸친 대용량 배치 생성 까지 모든 것을 처리합니다. 보고서 마이그레이션에 대한 질문이 있거나 아키텍처 관련 지침이 필요한 경우 엔지니어링 지원팀에 문의하십시오 .

자주 묻는 질문

IronPDF란 무엇인가요?

IronPDF는 개발자가 PDF 문서를 프로그래밍 방식으로 생성, 편집 및 생성할 수 있도록 하는 C# 라이브러리로, Crystal Reports와 같은 기존 보고 도구에 대한 현대적인 대안을 제공합니다.

IronPDF는 어떻게 Crystal Reports의 대안으로 활용될 수 있나요?

IronPDF는 개발자가 HTML/CSS 템플릿을 사용하여 보고서를 생성할 수 있도록 함으로써 보고서 생성에 유연하고 현대적인 접근 방식을 제공합니다. 이러한 템플릿은 Crystal Reports의 경직된 구조와는 달리 쉽게 스타일을 지정하고 수정할 수 있습니다.

IronPDF를 사용하여 송장을 만들 수 있나요?

네, IronPDF의 HTML/CSS 템플릿을 사용하면 상세하고 맞춤 설정 가능한 송장을 만들 수 있어 전문가 수준의 문서를 쉽게 디자인할 수 있습니다.

IronPDF로 직원 명부를 생성할 수 있나요?

물론입니다. IronPDF는 동적 데이터와 HTML/CSS를 활용하여 명확하고 체계적인 직원 명부를 생성할 수 있도록 지원합니다.

IronPDF는 재고 보고서 작성에 어떻게 도움을 줄 수 있을까요?

IronPDF는 HTML/CSS 템플릿을 사용하여 재고 보고서 생성을 간소화할 수 있으며, 이 템플릿은 데이터를 동적으로 채워 최신 정보를 반영하고 시각적으로 매력적인 보고서를 제공합니다.

IronPDF에서 HTML/CSS 템플릿을 사용하는 장점은 무엇인가요?

IronPDF에서 HTML/CSS 템플릿을 사용하면 디자인의 유연성, 손쉬운 업데이트, 웹 기술과의 호환성을 확보할 수 있어 보고서 레이아웃을 쉽게 유지 관리하고 개선할 수 있습니다.

IronPDF는 .NET 10을 지원합니까?

네, IronPDF는 .NET 10과 호환되므로 개발자는 보고서 생성에 필요한 최신 .NET 기능 및 개선 사항을 활용할 수 있습니다.

IronPDF는 보고서 생성 속도를 어떻게 향상시키나요?

IronPDF는 성능에 최적화되어 있어 HTML/CSS를 효율적으로 처리하고 고품질 PDF 문서로 렌더링하여 보고서를 신속하게 생성할 수 있습니다.

커티스 차우
기술 문서 작성자

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

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

시작할 준비 되셨나요?
Nuget 다운로드 17,527,568 | 버전: 2026.2 방금 출시되었습니다