dinktopdf - Exportar html a pdf en ASP.NET Core
itextsharp (5)
Quiero exportar un fragmento de html a un archivo pdf, pero no tengo ningún paquete compatible con nuget.
Cuando intento instalar a alguien: "X no es compatible con netcoreapp1.0 (.NETCoreApp, Version = v1.0)".
¿Alguien sabe alguna forma de exportar a un pdf utilizando el núcleo de asp.net?
¡Muchas gracias por adelantado!
¡Yo estaba teniendo el mismo problema! Quería generar archivos PDF a partir de cadenas HTML. Luego me encontré con PhantomJs, que es una utilidad de línea de comandos para convertir archivos html a pdf. ¡Escribí un contenedor multiplataforma sobre C # para .NET CORE y está funcionando muy bien en Linux! Aunque a partir de ahora es solo para Linux de 64 bits, ya que es la única plataforma .NET Core Supports actualmente. El proyecto se puede encontrar here
PhantomJs.NetCore.PdfGenerator gen = new PhantomJs.NetCore.PdfGenerator("/path/to/pantomjsfolder");
string outputFilePath = gen.GeneratePdf("<h1>Hello</h1>","/folder/to/write/file/in");
Copiado de mi respuesta original aquí Exportar a pdf usando ASP.NET 5 :
Una forma de generar pdf desde html en .NET Core (sin ninguna dependencia de .NET framework) es usar Node.js desde la aplicación .NET Core. El siguiente ejemplo muestra cómo implementar un convertidor de HTML a PDF en un proyecto de aplicación Web principal ASP.NET limpio (plantilla de API web).
Instale el paquete NuGet Microsoft.AspNetCore.NodeServices
En Startup.cs agregue la línea services.AddNodeServices()
como esta
public void ConfigureServices(IServiceCollection services)
{
// ... all your existing configuration is here ...
// Enable Node Services
services.AddNodeServices();
}
Ahora instale los paquetes Node.js requeridos:
Desde la línea de comandos, cambie el directorio de trabajo a la raíz del proyecto .NET Core y ejecute estos comandos.
npm init
y siga las instrucciones para crear el archivo package.json
npm install jsreport-core --save
npm install jsreport-jsrender --save
npm install jsreport-phantom-pdf --save
Cree un archivo pdf.js
en la raíz del proyecto que contiene
module.exports = function (callback) {
var jsreport = require(''jsreport-core'')();
jsreport.init().then(function () {
return jsreport.render({
template: {
content: ''<h1>Hello {{:foo}}</h1>'',
engine: ''jsrender'',
recipe: ''phantom-pdf''
},
data: {
foo: "world"
}
}).then(function (resp) {
callback(/* error */ null, resp.content.toJSON().data);
});
}).catch(function (e) {
callback(/* error */ e, null);
})
};
Echa un vistazo here para obtener más información sobre jsreport-core
.
Ahora cree una acción en un controlador Mvc que llame a este script Node.js
[HttpGet]
public async Task<IActionResult> MyAction([FromServices] INodeServices nodeServices)
{
var result = await nodeServices.InvokeAsync<byte[]>("./pdf");
HttpContext.Response.ContentType = "application/pdf";
string filename = @"report.pdf";
HttpContext.Response.Headers.Add("x-filename", filename);
HttpContext.Response.Headers.Add("Access-Control-Expose-Headers", "x-filename");
HttpContext.Response.Body.Write(result, 0, result.Length);
return new ContentResult();
}
Por supuesto, puede hacer lo que quiera con el byte[]
devuelto por nodeServices, en este ejemplo solo lo estoy emitiendo desde una acción del controlador para que pueda verse en el navegador.
También puede intercambiar los datos entre Node.js y .NET Core por una cadena codificada en base64 usando resp.content.toString(''base64'')
en pdf.js
y usar var result = await nodeServices.InvokeAsync<byte[]>("./pdf");
en la acción y luego decodificar la cadena codificada en base64.
La mayoría de las soluciones de generador de pdf todavía dependen del marco .NET 4.5 / 4.6. Pero parece que hay algunas alternativas de pago disponibles si no te gusta usar Node.js:
- NReco.PdfGenerator.LT
- EVO HTML to PDF Converter Client para .NET Core
- Winnovative HTML to PDF Converter Client para .NET Core
Aunque no he probado ninguno de estos
Espero que pronto veamos algún progreso de código abierto en esta área.
Esta es una solución que funciona para ASP.NET Core 2.0, que permite generar archivos PDF dinámicos desde cshtml
, enviarlos directamente a los usuarios o guardarlos antes de enviarlos.
Para complementar la respuesta de Jan Blaha allí , para mayor flexibilidad, es posible que desee utilizar el siguiente código:
/// Generate a PDF from a html string
async Task<(string ContentType, MemoryStream GeneratedFileStream)> GeneratePDFAsync(string htmlContent)
{
IJsReportFeature feature = new JsReportFeature(HttpContext);
feature.Recipe(Recipe.PhantomPdf);
if (!feature.Enabled) return (null, null);
feature.RenderRequest.Template.Content = htmlContent;
var report = await _RenderService.RenderAsync(feature.RenderRequest);
var contentType = report.Meta.ContentType;
MemoryStream ms = new MemoryStream();
report.Content.CopyTo(ms);
return (contentType, ms);
}
Usando una clase para representar archivos cshtml como una cadena, puede usar el siguiente servicio (que se puede inyectar como un servicio de ámbito):
public class ViewToStringRendererService: ViewExecutor
{
private ITempDataProvider _tempDataProvider;
private IServiceProvider _serviceProvider;
public ViewToStringRendererService(
IOptions<MvcViewOptions> viewOptions,
IHttpResponseStreamWriterFactory writerFactory,
ICompositeViewEngine viewEngine,
ITempDataDictionaryFactory tempDataFactory,
DiagnosticSource diagnosticSource,
IModelMetadataProvider modelMetadataProvider,
ITempDataProvider tempDataProvider,
IServiceProvider serviceProvider)
: base(viewOptions, writerFactory, viewEngine, tempDataFactory, diagnosticSource, modelMetadataProvider)
{
_tempDataProvider = tempDataProvider;
_serviceProvider = serviceProvider;
}
public async Task<string> RenderViewToStringAsync<TModel>(string viewName, TModel model)
{
var context = GetActionContext();
if (context == null) throw new ArgumentNullException(nameof(context));
var result = new ViewResult()
{
ViewData = new ViewDataDictionary<TModel>(
metadataProvider: new EmptyModelMetadataProvider(),
modelState: new ModelStateDictionary())
{
Model = model
},
TempData = new TempDataDictionary(
context.HttpContext,
_tempDataProvider),
ViewName = viewName,
};
var viewEngineResult = FindView(context, result);
viewEngineResult.EnsureSuccessful(originalLocations: null);
var view = viewEngineResult.View;
using (var output = new StringWriter())
{
var viewContext = new ViewContext(
context,
view,
new ViewDataDictionary<TModel>(
metadataProvider: new EmptyModelMetadataProvider(),
modelState: new ModelStateDictionary())
{
Model = model
},
new TempDataDictionary(
context.HttpContext,
_tempDataProvider),
output,
new HtmlHelperOptions());
await view.RenderAsync(viewContext);
return output.ToString();
}
}
private ActionContext GetActionContext()
{
var httpContext = new DefaultHttpContext();
httpContext.RequestServices = _serviceProvider;
return new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
}
/// <summary>
/// Attempts to find the <see cref="IView"/> associated with <paramref name="viewResult"/>.
/// </summary>
/// <param name="actionContext">The <see cref="ActionContext"/> associated with the current request.</param>
/// <param name="viewResult">The <see cref="ViewResult"/>.</param>
/// <returns>A <see cref="ViewEngineResult"/>.</returns>
ViewEngineResult FindView(ActionContext actionContext, ViewResult viewResult)
{
if (actionContext == null)
{
throw new ArgumentNullException(nameof(actionContext));
}
if (viewResult == null)
{
throw new ArgumentNullException(nameof(viewResult));
}
var viewEngine = viewResult.ViewEngine ?? ViewEngine;
var viewName = viewResult.ViewName ?? GetActionName(actionContext);
var result = viewEngine.GetView(executingFilePath: null, viewPath: viewName, isMainPage: true);
var originalResult = result;
if (!result.Success)
{
result = viewEngine.FindView(actionContext, viewName, isMainPage: true);
}
if (!result.Success)
{
if (originalResult.SearchedLocations.Any())
{
if (result.SearchedLocations.Any())
{
// Return a new ViewEngineResult listing all searched locations.
var locations = new List<string>(originalResult.SearchedLocations);
locations.AddRange(result.SearchedLocations);
result = ViewEngineResult.NotFound(viewName, locations);
}
else
{
// GetView() searched locations but FindView() did not. Use first ViewEngineResult.
result = originalResult;
}
}
}
if(!result.Success)
throw new InvalidOperationException(string.Format("Couldn''t find view ''{0}''", viewName));
return result;
}
private const string ActionNameKey = "action";
private static string GetActionName(ActionContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (!context.RouteData.Values.TryGetValue(ActionNameKey, out var routeValue))
{
return null;
}
var actionDescriptor = context.ActionDescriptor;
string normalizedValue = null;
if (actionDescriptor.RouteValues.TryGetValue(ActionNameKey, out var value) &&
!string.IsNullOrEmpty(value))
{
normalizedValue = value;
}
var stringRouteValue = routeValue?.ToString();
if (string.Equals(normalizedValue, stringRouteValue, StringComparison.OrdinalIgnoreCase))
{
return normalizedValue;
}
return stringRouteValue;
}
}
Luego, para concluir, en su controlador, suponiendo que la plantilla de vista cshtml de la maquinilla de afeitar sea /Views/Home/PDFTemplate.cshtml
puede usar lo siguiente.
Nota: Es cshtml
archivo cshtml
deba copiarse cuando se publique (incluso si las vistas están compiladas).
var htmlContent = await _ViewToStringRendererService.RenderViewToStringAsync("Home/PDFTemplate", viewModel);
(var contentType, var generatedFile) = await GeneratePDFAsync(htmlContent);
Response.Headers["Content-Disposition"] = $"attachment; filename=/"{System.Net.WebUtility.UrlEncode(fileName)}/"";
// You may save your file here
using (var fileStream = new FileStream(Path.Combine(folder, fileName), FileMode.Create))
{
await generatedFile.CopyToAsync(fileStream);
}
// You may need this for re-use of the stream
generatedFile.Seek(0, SeekOrigin.Begin);
return File(generatedFile.ToArray(), "application/pdf", fileName);
Puede consultar la librería DinkToPdf . Es una envoltura alrededor de la biblioteca wkhtmltopdf para .NET Core.
Convertidor sincronizado
Utilice este convertidor en aplicaciones multihilo y servidores web. Las tareas de conversión se guardan para bloquear la recopilación y se ejecutan en un solo hilo.
var converter = new SynchronizedConverter(new PdfTools());
Definir documento para convertir
var doc = new HtmlToPdfDocument()
{
GlobalSettings = {
ColorMode = ColorMode.Color,
Orientation = Orientation.Landscape,
PaperSize = PaperKind.A4Plus,
},
Objects = {
new ObjectSettings() {
PagesCount = true,
HtmlContent = @"Lorem ipsum dolor sit amet, consectetur adipiscing elit. In consectetur mauris eget ultrices iaculis. Ut odio viverra, molestie lectus nec, venenatis turpis.",
WebSettings = { DefaultEncoding = "utf-8" },
HeaderSettings = { FontSize = 9, Right = "Page [page] of [toPage]", Line = true, Spacing = 2.812 }
}
}
};
Puede usar jsreport .net sdk si está en .net core 2.0 sin servicios de nodo más complejos. Esto incluye, entre otras características, filtros para convertir sus vistas de maquinilla de afeitar existentes en pdf. De la docs :
1. Instalar nugets jsreport.Binary , jsreport.Local y jsreport.AspNetCore
2. En tu Startup.cs
configúralo como lo siguiente
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddJsReport(new LocalReporting()
.UseBinary(JsReportBinary.GetBinary())
.AsUtility()
.Create());
}
3. Luego, debe agregar el atributo MiddlewareFilter
a la acción particular y especificar qué conversión desea usar. En este caso html a pdf de conversión.
[MiddlewareFilter(typeof(JsReportPipeline))]
public IActionResult Invoice()
{
HttpContext.JsReportFeature().Recipe(Recipe.PhantomPdf);
return View();
}
Puede acceder a muchas otras opciones para encabezados, pies de página o diseño de página en JsReportFeature()
. Tenga en cuenta que de la misma manera también puede producir archivos de Excel desde html. Ver más información en la docs .
PD: soy el autor de jsreport.