Generování souborů ve formátu PDF pomocí Razor Engine
12.04.2021Občas potřebujeme v aplikaci vygenerovat PDF soubor a to je téma dnešního článku.
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řipravíme si webovou aplikaci, do které nainstalujeme NuGet balíček Select.HtmlToPdf. Vytvoříme pohled (view), který budeme chtít převést do souboru ve formátu PDF. Dále vytvoříme třídu, která nám tento pohled převede na HTML a také třídu, která nám HTML následně převede do výsledného PDF formátu. Výhodou použití Razor šablony (view) je, že šabloně můžeme předat model s daty z kontroleru, čí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
Převod Razor šablony na HTML
Třída je univerzální a můžeme ji například použít i při generování obsahu e-mailu 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();
}
}
}
}
Převod HTML na PDF
Tato třída funguje jako jednoduchý wrapper nad vybranou PDF knihovnou, která nám převod 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 služby zaregistrovat
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, nastavíme obrázek tak, aby se zkopíroval do projektové složky output. Jednou z možností, jak obrázek dostat do PDF, je převést ho na formát Base64.
Pdf.cshtml
@model Web.Models.PdfViewModel
<h1>Hello World!</h1>
<img src="data:image/jpeg;charset=utf-8;base64, @Model.ImageJpegBase64" />
Služby nyní 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 jednoduchý způsob, jak můžeme ve webové aplikaci generovat PDF soubory. Vytvořili jsme třídu ViewRenderService, která umí připravit HTML kód z Razor šablony a třídu PdfService, která funguje jako wrapper nad PDF knihovnou. Obě třídy mají vlastní rozhraní (interface), které můžeme využít v testech.