Как сгенерировать отчет в формате 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 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
Imports System.Runtime.InteropServices

''' <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
        ''' <summary>
        ''' Weight in lb. (Fields in template: WeightFrom, WeightTo). Here we are using a tuple.
        ''' </summary>
        Public Property Weight As (Integer, Integer)
    End Class
    Friend Class Program
        Public Shared Sub Main(args As String())
            ' Get your free key here:   
            ' https://sautinsoft.com/start-for-free/

            ' 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(json As String)
            ' Get data from json.            
            Dim cats = JsonConvert.DeserializeObject(Of List(Of CatBreed))(json)

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

            Dim 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.
            Dim customDataSource As CustomMailMergeDataSource = 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 Equals(eFM.RangeName, "CatBreed") AndAlso Equals(eFM.FieldName, "PictUrl") Then
                                                          eFM.Inlines.Clear()
                                                          Dim pictPath As String = eFM.Value.ToString()
                                                          Dim pict As Picture = 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

                                                          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 = "CatBreeds.pdf"

            ' Save the output to file
            Dim so As PdfSaveOptions = 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 = String.Empty
            Dim cats As List(Of CatBreed) = 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",
                    .Weight = (8, 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",
                    .Weight = (13, 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",
                    .Weight = (9, 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",
                    .Weight = (8, 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",
                    .Weight = (10, 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",
                    .Weight = (8, 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",
                    .Weight = (8, 16)
                }
            }

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

            ' 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 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(valueName As String, <Out> 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).Weight.Item1
                        Return True
                    Case "WeightTo"
                        value = _cats(_recordIndex).Weight.Item2
                        Return True
                    Case Else
                        ' A field with this name was not found
                        value = Nothing
                        Return False
                End Select
            End Function

            ''' <summary>
            ''' A standard implementation for moving to a next record in a collection.
            ''' </summary>
            Public Function MoveNext() As Boolean Implements IMailMergeDataSource.MoveNext
                Return Threading.Interlocked.Increment(_recordIndex) < _cats.Count
            End Function

            Public Function GetChildDataSource(sourceName As String) As IMailMergeDataSource Implements IMailMergeDataSource.GetChildDataSource
                Return Nothing
            End Function
            Public Sub New(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
        End Class
    End Class
End Namespace

Download


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



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

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