Generování PDF pomocí Razor Engine
12.04.2021Občas je potřeba vygenerovat PDF soubor a to je téma, kterému se věnuje tento článek.
Slovníček
ASP.NET Core | Open-source webový framework od Microsoftu (první release 2016). |
Select.HtmlToPdf | Nuget balíček pro volné použití od společnosti SelectPDF (free varianta má limit 5 stránek). |
FreeSpire.PDF | Alternativou k Select.HtmlToPdf může být Nuget balíček pro volné použití od společnosti E-ICEBLUE (free varianta má limit 10 stránek). |
Obsah
- Úvod
- Příprava prostředí
- Vytvoření třídy pro konverzi Razor šablony na HTML
- Vytvoření třídy pro konverzi HTML na PDF
- Závěr
- Odkazy
Úvod
Připravíme si webovou aplikaci, do které si nainstalujeme Select.HtmlToPdf Nuget balíček. Vytvoříme si pohled (view), který budeme chtít převést do PDF. Dále potřebujeme třídu, která nám takový pohled převede na HTML a také třídu, která nám HTML již převede do výsledného PDF formátu. Výhoda při použití Razor šablony (view) je taková, že šabloně můžeme předat model s daty z kontroleru a tím můžeme dynamicky měnit obsah PDF souboru dle potřeby.
Příprava prostředí
Vytvoříme webový projekt ze základní šablony Visual Studia
dotnet new mvc --output Web
Pomocí Package Manager Console ve Visual Studiu nainstalujeme Select.HtmlToPdf.NetCore balíček
Install-Package Select.HtmlToPdf.NetCore
Vytvoření třídy pro konverzi Razor šablony na HTML
Třída je univerzální a můžeme jí například použít i při generování obsahu emailu z razor šablony apod.
IViewRenderService.cs
using System.Threading.Tasks;
namespace Web.Application.Services.ViewRender
{
public interface IViewRenderService
{
Task<string> RenderToStringAsync(string viewName, object model);
}
}
ViewRenderService.cs
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Routing;
using System;
using System.IO;
using System.Threading.Tasks;
namespace Web.Application.Services.ViewRender
{
public class ViewRenderService : IViewRenderService
{
private readonly IRazorViewEngine _razorViewEngine;
private readonly ITempDataProvider _tempDataProvider;
private readonly IServiceProvider _serviceProvider;
public ViewRenderService(IRazorViewEngine razorViewEngine,
ITempDataProvider tempDataProvider,
IServiceProvider serviceProvider)
{
_razorViewEngine = razorViewEngine;
_tempDataProvider = tempDataProvider;
_serviceProvider = serviceProvider;
}
public async Task<string> RenderToStringAsync(string viewName, object model)
{
var httpContext = new DefaultHttpContext { RequestServices = _serviceProvider };
var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
using (var sw = new StringWriter())
{
var viewResult = _razorViewEngine.FindView(actionContext, viewName, false);
if (viewResult.View == null)
{
throw new ArgumentNullException($"{viewName} does not match any available view.");
}
var viewDictionary = new ViewDataDictionary(new EmptyModelMetadataProvider(), new ModelStateDictionary())
{
Model = model
};
var viewContext = new ViewContext(
actionContext,
viewResult.View,
viewDictionary,
new TempDataDictionary(actionContext.HttpContext, _tempDataProvider),
sw,
new HtmlHelperOptions()
);
await viewResult.View.RenderAsync(viewContext);
return sw.ToString();
}
}
}
}
Vytvoření třídy pro konverzi HTML na PDF
Tato třída funguje jako jednoduchý wrapper nad vybranou PDF knihovnou, která nám konverzi provede.
IPdfService.cs
namespace Web.Application.Services.Pdf
{
public interface IPdfService
{
byte[] Create(string html);
}
}
PdfService.cs (třída má již nadefinovanou konfiguraci pro layout, kterou lze v případě potřeby parametrizovat)
using SelectPdf;
namespace Web.Application.Services.Pdf
{
public class PdfService : IPdfService
{
public byte[] Create(string html)
{
var converter = new HtmlToPdf();
converter.Options.PdfPageSize = PdfPageSize.A4;
converter.Options.PdfPageOrientation = PdfPageOrientation.Portrait;
converter.Options.MarginLeft = 10;
converter.Options.MarginRight = 10;
converter.Options.MarginTop = 20;
converter.Options.MarginBottom = 20;
converter.Options.AllowContentHeightResize = false;
converter.Options.AutoFitHeight = HtmlToPdfPageFitMode.NoAdjustment;
converter.Options.AutoFitWidth = HtmlToPdfPageFitMode.NoAdjustment;
PdfDocument doc = converter.ConvertHtmlString(html);
byte[] result = doc.Save();
doc.Close();
return result;
}
}
}
Použití
Nyní máme již vše připravené a můžeme zaregistrovat naše služby
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<IViewRenderService, ViewRenderService>();
services.AddTransient<IPdfService, PdfService>();
services.AddControllersWithViews();
}
Připravíme si Model a Razor šablonu, kterou budeme chtít převést na PDF
PdfViewModel.cs
using System;
using System.IO;
namespace Web.Models
{
public class PdfViewModel
{
public PdfViewModel()
{
// Load "files\image.jpg" as base64.
ImageJpegBase64 =
Convert.ToBase64String(
File.ReadAllBytes(
Path.Combine(
AppDomain.CurrentDomain.BaseDirectory,
@"files\image.jpg")));
}
public string ImageJpegBase64 { get; private set; }
}
}
Protože model obsahuje cestu k obrázku, tak obrázek nastavíme, aby se nám zkopíroval do output složky. Převést si obrázek na Base64 formát je jedna z možností, jak obrázek dostat do PDF.
Pdf.cshtml
@model Web.Models.PdfViewModel
<h1>Hello World!</h1>
<img src="data:image/jpeg;charset=utf-8;base64, @Model.ImageJpegBase64" />
Služby pak můžeme načíst a použít kdekoliv v aplikaci, například v controlleru
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Web.Application.Services.Pdf;
using Web.Application.Services.ViewRender;
using Web.Models;
namespace Web.Controllers
{
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
private readonly IViewRenderService _viewRender;
private readonly IPdfService _pdf;
public HomeController(ILogger<HomeController> logger, IViewRenderService viewRender, IPdfService pdf)
{
_logger = logger;
_viewRender = viewRender;
_pdf = pdf;
}
public IActionResult DownloadPdf()
{
var model = new PdfViewModel();
string html = _viewRender.RenderToStringAsync("Home/pdf", model).Result;
byte[] pdf = _pdf.Create(html);
return new FileContentResult(pdf, "application/pdf")
{
FileDownloadName = "Document.pdf"
};
}
}
}
Při zavolání URL akce Home/DownloadPdf se nám PDF dokument stáhne
Závěr
V dnešním článku jsme si ukázali jednoduchou cestu, jak z webové aplikace vygenerovat PDF dokument. Vytvořili jsme si třídu ViewRenderService, která má univerzální použití a která nám připraví HTML kód z Razor šablony a vytvořili jsme si třídu PdfService, která funguje jako wrapper nad PDF knihovnou našeho výběru. Obě třídy mají interface, takže je můžeme snadno použít v unit testech.