Как сгенерировать отчет в формате PDF из данных JSON на C#


    В этой короткой статье мы покажем вам, как динамически генерировать PDF-отчет с картинками и таблицей на основе данных JSON и .docx template под .NET platform.

    Мы увидим новейший язык C# 9.0 и новейший стандарт "System.Text.Json" сборка, а также наши SDK - "SautinSoft.Document" который выполнит всю работу за нас.
Итак, мы сгенерируем отчет в формате PDF со списком пород кошек, их описанием и фотографией.
Предположим, что в реальном приложении у нас есть объект JSON, содержащий все данные о породах кошек: название, описание, ссылку на картинку и диапазон веса.

    В нашем приложении мы создадим объект JSON вручную и заполним его данными тестирования.

Давайте начнем:

  1. Прежде всего, мы должны создать класс, описывающий породу кошек:

    class CatBreed
        {
            public string Title { get; set; }
            public string Description { get; set; }
            public string PictUrl { get; set; }
            /// <summary>
            /// Weight in lb. (Fields in template: WeightFrom, WeightTo). Here we are using a tuple.
            /// </summary>
            public (int, int) Weight { get; set; }        
        }
    
  2. Создайте метод, который заполняет коллекцию путем тестирования данных (различные породы кошек), сериализует эту коллекцию в объект JSON и возвращает ее в виде строки:

    public static string CreateJsonObject()
            {
                string json = String.Empty;
                List<CatBreed> cats = new List<CatBreed>
                {
                    new CatBreed() {Title = "Australian Mist",
                        Description = "The Australian Mist (formerly known as the Spotted Mist) is a breed of cat developed in Australia.",
                        PictUrl = "australian-mist.jpg",
                        Weight = (8, 15)},                    
                    new CatBreed() {Title = "Maine Coon",
                        Description = "The Maine Coon is a large domesticated cat breed. It has a distinctive physical appearance and valuable hunting skills.",
                        PictUrl = "maine-coon.png",
                        Weight = (13, 18)},                    
                    new CatBreed() {Title = "Scottish Fold",
                        Description = "The original Scottish Fold was a white barn cat named Susie, who was found at a farm near Coupar Angus in Perthshire, Scotland, in 1961.",
                        PictUrl = "scottish-fold.jpg",
                        Weight = (9, 13)},
                    new CatBreed() {Title = "Oriental Shorthair",
                        Description = "The Oriental Shorthair is a breed of domestic cat that is developed from and closely related to the Siamese cat.",
                        PictUrl = "oriental-shorthair.jpg",
                        Weight = (8, 12)},                    
                    new CatBreed() {Title = "Bengal cat",
                        Description = "The earliest mention of an Asian leopard cat × domestic cross was in 1889, when Harrison Weir wrote of them in Our Cats and ...",
                        PictUrl = "bengal-cat.jpg",
                        Weight = (10, 15)},                    
                    new CatBreed() {Title = "Russian Blue",
                        Description = "The Russian Blue is a naturally occurring breed that may have originated in the port of Arkhangelsk in Russia.",
                        PictUrl = "russian-blue.jpg",
                        Weight = (8, 15)},
                    new CatBreed() {Title = "Mongrel cat",
                        Description = "A mongrel, mutt or mixed-breed cat is a cat that does not belong to one officially recognized breed, but he's cool and gentle!",
                        PictUrl = "mongrel-cat.jpg",
                        Weight = (8, 16)}                    
                };
    
                // Generate full path for the cat's pictures.
                string pictDirectory = Path.GetFullPath(@"..\..\picts\");
                foreach (var cb in cats)
                {
                    cb.PictUrl = Path.Combine(pictDirectory, cb.PictUrl);
                }
    
                // Make serialization to JSON format.
                JsonSerializerOptions options = new() {IncludeFields = true };
                json = JsonSerializer.Serialize(cats, options);
                return json;
            }
    
  3. Мы должны подготовить .docx templateчтобы подготовить отчет с помощью MS Word или любого другого приложения .docs, здесь вы найдете как создать шаблон DOCX

  4. Последний метод, который мы создадим, должен принимать JSON в виде строки и генерировать PDF, например, как "PDF/A 1". На самом деле SDK - "SautinSoft.Document" позволяет вам выбрать любой выходной формат для отчета: PDF, DOCX, HTML или даже RTF.
    Здесь, в этом методе, мы также добавим обработчик событий для оформления наших изображений. Ссылки на эти изображения хранятся в объекте JSON в поле "Picture".
    Список этого метода:

    public static void GeneratePdfReport(string json)
            {
                // Get data from json.
                JsonSerializerOptions options = new() { IncludeFields = true };
                var cats = JsonSerializer.Deserialize<List<CatBreed>>(json, options);            
    
                // Load the template document.
                string templatePath = @"..\..\cats-template.docx";
    
                DocumentCore dc = DocumentCore.Load(templatePath);
    
                // To be able to mail merge from your own data source, it must be wrapped into an object that implements the IMailMergeDataSource interface.
                CustomMailMergeDataSource customDataSource = new CustomMailMergeDataSource(cats);
    
                // Decorate each cat beed by by appropriate picture.
                // Set picture width to 80 mm, height to Auto.
                dc.MailMerge.FieldMerging += (senderFM, eFM) =>
                {
                    // Insert an icon before the product name
                    if (eFM.RangeName == "CatBreed" && eFM.FieldName == "PictUrl")
                    {
                        eFM.Inlines.Clear();
                        string pictPath = eFM.Value.ToString();
                        Picture pict = new Picture(dc, pictPath);
                        double kWH = 1f;
                        double desiredWidthMm = 80;
    
                        if (pict.Layout.Size.Width > 0 && pict.Layout.Size.Height > 0)
                            kWH = pict.Layout.Size.Width / pict.Layout.Size.Height;
    
                        pict.Layout = new InlineLayout(new Size(desiredWidthMm, desiredWidthMm / kWH, LengthUnit.Millimeter));
    
                        eFM.Inlines.Add(pict);                    
                        eFM.Cancel = false;
                    }
                };
    
                // Execute the mail merge.
                dc.MailMerge.Execute(customDataSource);
    
                string resultPath = "CatBreeds.pdf";
    
                // Save the output to file
                PdfSaveOptions so = new PdfSaveOptions()
                {
                    Compliance = PdfCompliance.PDF_A1a
                };
    
                dc.Save(resultPath, so);
    
                // Open the result for demonstration purposes.
                System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo(resultPath) { UseShellExecute = true });
            }
    
  5. Здесь вы найдете полный исходный код нашего приложения для создания отчетов:

Загрузите полученный файл: CatBreeds.pdf

Полный код

using System;
using System.IO;
using System.Collections.Generic;
using SautinSoft.Document;
using SautinSoft.Document.Drawing;
using SautinSoft.Document.MailMerging;
using Newtonsoft.Json;

/// <summary>
/// Generates a report in PDF format (PDF/A) based on JSON data and .docx template.
/// </summary>
/// <remarks>
/// See details at: https://www.sautinsoft.com/products/document/help/net/developer-guide/mail-merge-generate-pdf-report-from-json-data-net-csharp-vb.php
/// </remarks>
namespace CatBreedReportApp
{
    class CatBreed
    {
        public string Title { get; set; }
        public string Description { get; set; }
        public string PictUrl { get; set; }
        /// <summary>
        /// Weight in lb. (Fields in template: WeightFrom, WeightTo). Here we are using a tuple.
        /// </summary>
        public (int, int) Weight { get; set; }        
    }
    class Program
    {
        static void Main(string[] args)
        {
            // Get your free 30-day key here:   
            // https://sautinsoft.com/start-for-free/

            // 1. Get json data
            string json = CreateJsonObject();

            // 2. Show json to Console.
            Console.WriteLine(json);

            // 3. Generate report based on .docx template and json.
            GeneratePdfReport(json);
        }

        public static void GeneratePdfReport(string json)
        {
            // Get data from json.            
            var cats = JsonConvert.DeserializeObject<List<CatBreed>>(json);

            // Load the template document.
            string templatePath = @"..\..\..\cats-template.docx";

            DocumentCore dc = DocumentCore.Load(templatePath);

            // To be able to mail merge from your own data source, it must be wrapped into an object that implements the IMailMergeDataSource interface.
            CustomMailMergeDataSource customDataSource = new CustomMailMergeDataSource(cats);

            // Decorate each cat beed by by appropriate picture.
            // Set picture width to 80 mm, height to Auto.
            dc.MailMerge.FieldMerging += (senderFM, eFM) =>
            {
                // Insert an icon before the product name
                if (eFM.RangeName == "CatBreed" && eFM.FieldName == "PictUrl")
                {
                    eFM.Inlines.Clear();
                    string pictPath = eFM.Value.ToString();
                    Picture pict = new Picture(dc, pictPath);
                    double kWH = 1f;
                    double desiredWidthMm = 80;

                    if (pict.Layout.Size.Width > 0 && pict.Layout.Size.Height > 0)
                        kWH = pict.Layout.Size.Width / pict.Layout.Size.Height;

                    pict.Layout = new InlineLayout(new Size(desiredWidthMm, desiredWidthMm / kWH, LengthUnit.Millimeter));

                    eFM.Inlines.Add(pict);                    
                    eFM.Cancel = false;
                }
            };

            // Execute the mail merge.
            dc.MailMerge.Execute(customDataSource);

            string resultPath = "CatBreeds.pdf";

            // Save the output to file
            PdfSaveOptions so = new PdfSaveOptions()
            {
                Compliance = PdfCompliance.PDF_A1a
            };

            dc.Save(resultPath, so);

            // Open the result for demonstration purposes.
            System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo(resultPath) { UseShellExecute = true });
        }
        public static string CreateJsonObject()
        {
            string json = String.Empty;
            List<CatBreed> cats = new List<CatBreed>
            {
                new CatBreed() {Title = "Australian Mist",
                    Description = "The Australian Mist (formerly known as the Spotted Mist) is a breed of cat developed in Australia.",
                    PictUrl = "australian-mist.jpg",
                    Weight = (8, 15)},                    
                new CatBreed() {Title = "Maine Coon",
                    Description = "The Maine Coon is a large domesticated cat breed. It has a distinctive physical appearance and valuable hunting skills.",
                    PictUrl = "maine-coon.png",
                    Weight = (13, 18)},                    
                new CatBreed() {Title = "Scottish Fold",
                    Description = "The original Scottish Fold was a white barn cat named Susie, who was found at a farm near Coupar Angus in Perthshire, Scotland, in 1961.",
                    PictUrl = "scottish-fold.jpg",
                    Weight = (9, 13)},
                new CatBreed() {Title = "Oriental Shorthair",
                    Description = "The Oriental Shorthair is a breed of domestic cat that is developed from and closely related to the Siamese cat.",
                    PictUrl = "oriental-shorthair.jpg",
                    Weight = (8, 12)},                    
                new CatBreed() {Title = "Bengal cat",
                    Description = "The earliest mention of an Asian leopard cat × domestic cross was in 1889, when Harrison Weir wrote of them in Our Cats and ...",
                    PictUrl = "bengal-cat.jpg",
                    Weight = (10, 15)},                    
                new CatBreed() {Title = "Russian Blue",
                    Description = "The Russian Blue is a naturally occurring breed that may have originated in the port of Arkhangelsk in Russia.",
                    PictUrl = "russian-blue.jpg",
                    Weight = (8, 15)},
                new CatBreed() {Title = "Mongrel cat",
                    Description = "A mongrel, mutt or mixed-breed cat is a cat that does not belong to one officially recognized breed, but he's cool and gentle!",
                    PictUrl = "mongrel-cat.jpg",
                    Weight = (8, 16)}                    
            };

            // Generate full path for the cat's pictures.
            string pictDirectory = Path.GetFullPath(@"..\..\..\picts\");
            foreach (var cb in cats)
            {
                cb.PictUrl = Path.Combine(pictDirectory, cb.PictUrl);
            }

            // Make serialization to JSON format.            
            json = JsonConvert.SerializeObject(cats);
            return json;
        }

        /// <summary>
        /// A custom mail merge data source that allows SautinSoft.Document to retrieve data from CatBeeds objects.
        /// </summary>
        public class CustomMailMergeDataSource : IMailMergeDataSource
        {
            private readonly List<CatBreed> _cats;
            private int _recordIndex;

            /// <summary>
            /// The name of the data source. 
            /// </summary>
            public string Name
            {
                get { return "CatBreed"; }
            }

            /// <summary>
            /// SautinSoft.Document calls this method to get a value for every data field.
            /// </summary>
            public bool TryGetValue(string valueName, out object value)
            {
                switch (valueName)
                {
                    case "Title":
                        value = _cats[_recordIndex].Title;
                        return true;
                    case "Description":
                        value = _cats[_recordIndex].Description;
                        return true;
                    case "PictUrl":
                        value = _cats[_recordIndex].PictUrl;
                        return true;
                    case "WeightFrom":
                        value = _cats[_recordIndex].Weight.Item1;
                        return true;
                    case "WeightTo":
                        value = _cats[_recordIndex].Weight.Item2;
                        return true;
                    default:
                        // A field with this name was not found
                        value = null;
                        return false;
                }
            }

            /// <summary>
            /// A standard implementation for moving to a next record in a collection.
            /// </summary>
            public bool MoveNext()
            {
                return (++_recordIndex < _cats.Count);
            }

            public IMailMergeDataSource GetChildDataSource(string sourceName)
            {
                return null;
            }
            public CustomMailMergeDataSource(List<CatBreed> cats)
            {
                _cats = cats;
                // When the data source is initialized, it must be positioned before the first record.
                _recordIndex = -1;
            }
        }
    }
}

Download

Imports System
Imports System.IO
Imports System.Collections.Generic
Imports SautinSoft.Document
Imports SautinSoft.Document.Drawing
Imports SautinSoft.Document.MailMerging
Imports Newtonsoft.Json
''' Get your free 100-day key here:   
''' https://sautinsoft.com/start-for-free/
''' <summary>
''' Generates a report in PDF format (PDF/A) based on JSON data and .docx template.
''' </summary>
''' <remarks>
''' See details at: https://www.sautinsoft.com/products/document/help/net/developer-guide/mail-merge-generate-pdf-report-from-json-data-net-csharp-vb.php
''' </remarks>
Namespace CatBreedReportApp
    Friend Class CatBreed
        Public Property Title() As String
        Public Property Description() As String
        Public Property PictUrl() As String
        Public Property WeightMin() As Integer
        Public Property WeightMax() As Integer

    End Class

    Friend Class Program
        Shared Sub Main(ByVal args() As String)
            ' 1. Get json data
            Dim json As String = CreateJsonObject()

            ' 2. Show json to Console.
            Console.WriteLine(json)

            ' 3. Generate report based on .docx template and json.
            GeneratePdfReport(json)
        End Sub
        Public Shared Sub GeneratePdfReport(ByVal json As String)
            ' Get data from json.            
            Dim cats = JsonConvert.DeserializeObject(Of List(Of CatBreed))(json)

            ' Load the template document.
            Dim templatePath As String = "..\..\..\cats-template.docx"

            Dim dc As DocumentCore = DocumentCore.Load(templatePath)

            ' To be able to mail merge from your own data source, it must be wrapped into an object that implements the IMailMergeDataSource interface.
            Dim customDataSource As New CustomMailMergeDataSource(cats)

            ' Decorate each cat beed by by appropriate picture.
            ' Set picture width to 80 mm, height to Auto.
            AddHandler dc.MailMerge.FieldMerging, Sub(senderFM, eFM)
                                                      ' Insert an icon before the product name
                                                      If eFM.RangeName = "CatBreed" AndAlso eFM.FieldName = "PictUrl" Then
                                                          eFM.Inlines.Clear()
                                                          Dim pictPath As String = eFM.Value.ToString()
                                                          Dim pict As New Picture(dc, pictPath)
                                                          Dim kWH As Double = 1.0F
                                                          Dim desiredWidthMm As Double = 80

                                                          If pict.Layout.Size.Width > 0 AndAlso pict.Layout.Size.Height > 0 Then
                                                              kWH = pict.Layout.Size.Width \ pict.Layout.Size.Height
                                                          End If

                                                          pict.Layout = New InlineLayout(New Size(desiredWidthMm, desiredWidthMm / kWH, LengthUnit.Millimeter))

                                                          eFM.Inlines.Add(pict)
                                                          eFM.Cancel = False
                                                      End If
                                                  End Sub

            ' Execute the mail merge.
            dc.MailMerge.Execute(customDataSource)

            Dim resultPath As String = "CatBreeds.pdf"

            ' Save the output to file
            Dim so As New PdfSaveOptions() With {.Compliance = PdfCompliance.PDF_A1a}

            dc.Save(resultPath, so)

            ' Open the result for demonstration purposes.
            System.Diagnostics.Process.Start(New System.Diagnostics.ProcessStartInfo(resultPath) With {.UseShellExecute = True})
        End Sub
        Public Shared Function CreateJsonObject() As String
            Dim json As String = String.Empty
            Dim cats As New List(Of CatBreed) From {
                New CatBreed() With {
                    .Title = "Australian Mist",
                    .Description = "The Australian Mist (formerly known as the Spotted Mist) is a breed of cat developed in Australia.",
                    .PictUrl = "australian-mist.jpg",
                    .WeightMin = 8,
                    .WeightMax = 15
                },
                New CatBreed() With {
                    .Title = "Maine Coon",
                    .Description = "The Maine Coon is a large domesticated cat breed. It has a distinctive physical appearance and valuable hunting skills.",
                    .PictUrl = "maine-coon.png",
                    .WeightMin = 13,
                    .WeightMax = 18
                },
                New CatBreed() With {
                    .Title = "Scottish Fold",
                    .Description = "The original Scottish Fold was a white barn cat named Susie, who was found at a farm near Coupar Angus in Perthshire, Scotland, in 1961.",
                    .PictUrl = "scottish-fold.jpg",
                    .WeightMin = 9,
                    .WeightMax = 13
                },
                New CatBreed() With {
                    .Title = "Oriental Shorthair",
                    .Description = "The Oriental Shorthair is a breed of domestic cat that is developed from and closely related to the Siamese cat.",
                    .PictUrl = "oriental-shorthair.jpg",
                    .WeightMin = 8,
                    .WeightMax = 12
                },
                New CatBreed() With {
                    .Title = "Bengal cat",
                    .Description = "The earliest mention of an Asian leopard cat × domestic cross was in 1889, when Harrison Weir wrote of them in Our Cats and ...",
                    .PictUrl = "bengal-cat.jpg",
                    .WeightMin = 10,
                    .WeightMax = 15
                },
                New CatBreed() With {
                    .Title = "Russian Blue",
                    .Description = "The Russian Blue is a naturally occurring breed that may have originated in the port of Arkhangelsk in Russia.",
                    .PictUrl = "russian-blue.jpg",
                    .WeightMin = 8,
                    .WeightMax = 15
                },
                New CatBreed() With {
                    .Title = "Mongrel cat",
                    .Description = "A mongrel, mutt or mixed-breed cat is a cat that does not belong to one officially recognized breed, but he's cool and gentle!",
                    .PictUrl = "mongrel-cat.jpg",
                    .WeightMin = 8,
                    .WeightMax = 16
                }
            }

            ' Generate full path for the cat's pictures.
            Dim pictDirectory As String = Path.GetFullPath("..\..\..\picts\")
            For Each cb In cats
                cb.PictUrl = Path.Combine(pictDirectory, cb.PictUrl)
            Next cb

            ' Make serialization to JSON format.            
            json = JsonConvert.SerializeObject(cats)
            Return json
        End Function
        ''' <summary>
        ''' A custom mail merge data source that allows SautinSoft.Document to retrieve data from CatBeeds objects.
        ''' </summary>
        Public Class CustomMailMergeDataSource
            Implements IMailMergeDataSource

            Private ReadOnly _cats As List(Of CatBreed)
            Private _recordIndex As Integer

            ''' <summary>
            ''' The name of the data source. 
            ''' </summary>
            Public ReadOnly Property Name() As String
                Get
                    Return "CatBreed"
                End Get
            End Property

            Private ReadOnly Property IMailMergeDataSource_Name As String Implements IMailMergeDataSource.Name
                Get
                    Return "CatBreed"
                End Get
            End Property

            ''' <summary>
            ''' SautinSoft.Document calls this method to get a value for every data field.
            ''' </summary>
            Public Function TryGetValue(ByVal valueName As String, <System.Runtime.InteropServices.Out()> ByRef value As Object) As Boolean
                Select Case valueName
                    Case "Title"
                        value = _cats(_recordIndex).Title
                        Return True
                    Case "Description"
                        value = _cats(_recordIndex).Description
                        Return True
                    Case "PictUrl"
                        value = _cats(_recordIndex).PictUrl
                        Return True
                    Case "WeightFrom"
                        value = _cats(_recordIndex).WeightMin
                        Return True
                    Case "WeightTo"
                        value = _cats(_recordIndex).WeightMax
                        Return True
                    Case Else
                        ' A field with this name was not found
                        value = Nothing
                        Return False
                End Select
            End Function

            Public Sub New(ByVal cats As List(Of CatBreed))
                _cats = cats
                ' When the data source is initialized, it must be positioned before the first record.
                _recordIndex = -1
            End Sub

            Private Function IMailMergeDataSource_MoveNext() As Boolean Implements IMailMergeDataSource.MoveNext
                _recordIndex += 1
                Return (_recordIndex < _cats.Count)
            End Function

            Private Function IMailMergeDataSource_TryGetValue(valueName As String, ByRef value As Object) As Boolean Implements IMailMergeDataSource.TryGetValue
                Select Case valueName
                    Case "Title"
                        value = _cats(_recordIndex).Title
                        Return True
                    Case "Description"
                        value = _cats(_recordIndex).Description
                        Return True
                    Case "PictUrl"
                        value = _cats(_recordIndex).PictUrl
                        Return True
                    Case "WeightFrom"
                        value = _cats(_recordIndex).WeightMin
                        Return True
                    Case "WeightTo"
                        value = _cats(_recordIndex).WeightMax
                        Return True
                    Case Else
                        ' A field with this name was not found
                        value = Nothing
                        Return False
                End Select
            End Function

            Private Function IMailMergeDataSource_GetChildDataSource(sourceName As String) As IMailMergeDataSource Implements IMailMergeDataSource.GetChildDataSource
                Return Nothing
            End Function
        End Class

    End Class
End Namespace

Download


Если вам нужен пример кода или у вас есть вопрос: напишите нам по адресу support@sautinsoft.com или спросите в онлайн-чате (правый нижний угол этой страницы) или используйте форму ниже:



Вопросы и предложения всегда приветствуются!

Мы разрабатываем компоненты .Net с 2002 года. Мы знаем форматы PDF, DOCX, RTF, HTML, XLSX и Images. Если вам нужна помощь в создании, изменении или преобразовании документов в различных форматах, мы можем вам помочь. Мы напишем для вас любой пример кода абсолютно бесплатно.