Cómo crear un PDF a partir de una Vista en ASP.NET MVC

En el anterior artículo de este Bolg Cómo crear un PDF a partir de una Vista en ASP.NET Core MVC, vimos cómo crear dinámicamente un PDF a partir de una Vista definida en una aplicación ASP.NET Core MVC.

En esta ocasión y basándonos en el ejemplo ya desarrollado en el artículo anterior, veremos cómo realizar la misma funcionalidad, pero esta vez para aplicaciones ASP.NET MVC sobre .NET Framework 4.6.1 o superior.

html-to-pdf

Para realizar este ejemplo, utilizaremos la herramienta Rotativa. Esta librería de uso gratuito, podemos integrarla en nuestros proyectos ASP.NET MVC vía NuGet, y es compatible con versiones Microsoft.AspNet.Mvc (>= 5.2.3).

Básicamente, Rotativa para ASP.NET MVC, crea archivos PDF de forma dinámica a partir de cualquier Vista existente en nuestra aplicación MVC. Esta funcionalidad la consigue con el uso de la herramienta wkhtmltopdf, que no es más que una librería de código abierto (LGPLv3) en línea de comandos, que transforma HTML e imágenes en PDF utilizando utilizando el motor de renderizado Qt Web Kit.

 

Integrando Rotativa en nuestra aplicación ASP.NET MVC

En primer lugar, crearemos un nuevo proyecto Web ASP.NET MVC desde Visual Studio 2019 (o cualquier otra versión), con plataforma de destino .NET Framework 4.7.2 (.NET Framework 4.6.1 o superior).

La instalación de Rotativa la haremos a través de NuGet. Para esto abriremos la consola de administración de paquetes NuGet de Visual Studio (Herramientas > Administrador de paquetes NuGet > Consola del Administrador de paquetes), y ejecutamos el siguiente comando:

PM> Install-Package Rotativa -Version 1.7.3

Una vez finalizada la instalación, observaremos que se ha creado una nueva carpeta en nuestro proyecto de nombre Rotativa.

Dentro de esta carpeta deberán estar los archivos ejecutables wkhtmltopdf.exe y wkhtmltoimage.exe. Estos ejecutables  conforman la herramienta wkhtmltopdf, que es necesaria para que Rotativa pueda generar los PDFs.

Además encontraremos los documentos de ayuda help-wkhtmltoimage.txthelp-wkhtmltopdf.txt, que son básicamente guías de referencia para configurar la opciones de generación del PDF en la herramienta wkhtmltopdf.

rotativa-folder

Importante: Para que estos dos ejecutables, sean accesibles cuando publiquemos nuestra aplicación en un entorno de producción, es necesario establecer su propiedad [Copiar en el directorio de salida] = Copiar siempre.

copiar siempre

 

Construyendo nuestra aplicación ASP.NET MVC de ejemplo

El Modelo de datos

Para realizar este ejemplo, crearemos el nuevo Modelo de datos Customer.cs (tabla Customers de la base de datos de pruebas Northwind de Microsoft). Como ORM utilizaremos Entity Framework .

Nota: Los Scripts de la tabla Customers y los datos de prueba, están disponibles para descargar al final del artículo.

    public class Customer
    {
        [Key]
        [StringLength(5)]
        public string CustomerID { get; set; }

        [Required]
        [StringLength(40)]
        public string CompanyName { get; set; }

        [Required]
        [StringLength(30)]
        public string ContactName { get; set; }

        [StringLength(30)]
        public string ContactTitle { get; set; }

        [StringLength(100)]
        public string Address { get; set; }

        [StringLength(15)]
        public string City { get; set; }

        [StringLength(15)]
        public string Region { get; set; }

        [StringLength(10)]
        public string PostalCode { get; set; }

        [Required]
        [StringLength(15)]
        public string Country { get; set; }

        [StringLength(24)]
        public string Phone { get; set; }

        [StringLength(24)]
        public string Fax { get; set; }

        [Required]
        [StringLength(100)]
        [DataType(DataType.EmailAddress)]
        public string Email { get; set; }
    }

Nota: Como no es el objetivo de este artículo explicar como "enlazar" Entity Framework a una base de datos, supondremos que ya hemos definido el DbContext de la aplicación con el correspondiente DbSet del modelo de datos Customer en la clase AppDbContext.cs.

    public class AppDbContext : DbContext
    {
        // You can add custom code to this file. Changes will not be overwritten.
        // 
        // If you want Entity Framework to drop and regenerate your database
        // automatically whenever you change your model schema, please use data migrations.
        // For more information refer to the documentation:
        // http://msdn.microsoft.com/en-us/data/jj591621.aspx
    
        public AppDbContext() : base("name=DefaultConnection")
        {
        }

        public DbSet<Customer> Customers { get; set; }
    }

 

La Vista

A continuación, en la Vista Index.cshtml de la aplicación, eliminaremos todo el contenido que viene por defecto, y crearemos una tabla <table /> que nos muestre los datos de contacto del Modelo Customer

Pero antes de ver como quedaría el código resultante, veremos una serie de puntos a tener en cuenta, en lo que se refiere a la maquetación y estilos CSS de la Vista.

Puntos a tener en cuenta en el formato de la Vista

Como en todo proceso de maquetación, debemos tener en cuenta que el contenido HTML generado, se debe ajustar a las dimensiones de impresión de un folio DIN A4 a la hora de generar nuestro PDF

Para ello, es aconsejable realizar ciertas modificaciones en los estilos CSS de la Vista, para solucionar una serie problemas de formato reconocidos a la hora de generar el PDF.

El solapamiento en las cabeceras de tablas <table /> 

Este es un problema identificado y recurrente en Rotativa, que seguramente os encontrareis a la hora de generar PDFs a partir de tablas HTML. El problema consiste en que la cabecera de la tabla se solapa con la primera línea de la siguiente página del listado.

Para solucionarlo deberemos aplicar el siguiente estilo CSS a la tabla:

    <style>
        /* SOLUCIONA PROBLEMA DE SOLAPAMIENTO EN TABLAS <table /> */
        thead { display: table-header-group }
        tfoot { display: table-row-group }
        tr { page-break-inside: avoid }        
    </style>

Márgenes de del documento

Para asegurarnos de que la página HTML se ajuste a los márgenes del documento PDF generado, es conveniente eliminar cualquier marginpadding que tenga la Vista en los estilos heredados de la misma.

Esto lo haremos en los estilos en linea de la etiqueta <body />:

<body style="padding: 0px; margin: 0px;">

</body>

Sin página maestra

Siempre que podamos es conveniente crear Vistas que no dependan de una página maestra. Para nuestro ejemplo, la página Index.cshtml será una página completa e independiente con sus propios estilos CSS.

Recordar siempre en ASP.NET MVC, indicar que no queremos que se renderice la página maestra _Layout.cshtml:

@{
    Layout = null;
}

Una vez realizadas las recomendaciones de formato, ya podemos construir la Vista Index.cshtml:

@model IEnumerable<Customer>

@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>ContactPDF</title>
    <link rel="stylesheet" href="~/Content/bootstrap.css" />
    <link rel="stylesheet" href="~/Content/Site.css" />

    <style>
        /* SOLUCIONA PROBLEMA DE SOLAPAMIENTO EN TABLAS <table /> */
        thead { display: table-header-group }
        tfoot { display: table-row-group }
        tr { page-break-inside: avoid }        
    </style>

</head>
<body style="background: #b6ff00; padding: 0px; margin: 0px;">

    <div class="container">

        <table class="table">
            <thead>
                <tr>
                    <th>Nº&nbsp;</th>
                    <th>
                        @Html.DisplayNameFor(model => model.CompanyName)
                    </th>
                    <th>
                        @Html.DisplayNameFor(model => model.ContactName)
                    </th>
                    <th>
                        @Html.DisplayNameFor(model => model.Phone)
                    </th>
                    <th>
                        @Html.DisplayNameFor(model => model.Email)
                    </th>
                </tr>
            </thead>
            <tbody>
                @{
                    var _count = 0;
                }
                @foreach (var item in Model)
                {
                    _count++;
                    <tr>
                        <td>
                            @_count
                        </td>
                        <td>
                            @Html.DisplayFor(modelItem => item.CompanyName)
                        </td>
                        <td>
                            @Html.DisplayFor(modelItem => item.ContactName)
                        </td>
                        <td>
                            @Html.DisplayFor(modelItem => item.Phone)
                        </td>
                        <td>
                            @Html.DisplayFor(modelItem => item.Email)
                        </td>

                    </tr>
                }
            </tbody>
        </table>

    </div>

</body>
</html>

La cabecera y el pié de página

En ciertas ocasiones, podemos necesitar que nuestro PDF auto-generado, disponga de una cabecera y un pie personalizados en cada página del documento (por ejemplo una factura, un albarán etc.)

Para esto, y en primer lugar, debemos definir las plantillas Html que actuarán como cabecera y pie de página. Estas plantillas serán Vistas cshtml como las habituales en nuestro proyecto (no valen Vistas parciales), pudiendo recibir si fuera necesario un Modelo de datos desde el Controlador.

Para este ejemplo crearemos una vista HeaderPDF.cshtml para la cabecera, con un logotipo corporativo y un título descriptivo.

@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>HeaderPDF</title>
    <link rel="stylesheet" href="~/Content/bootstrap.css" />
    <link rel="stylesheet" href="~/Content/Site.css" />
</head>
<body style="background: #00ffff; padding: 0px; margin: 0px;">
    <div class="row">
        <div class="col-xs-4">
            <img src="~/images/generic-logo.png" />
        </div>
        <div class="col-xs-8 text-right">
            <h2>Customers Lista</h2>
        </div>
    </div>
</body>
</html>

También crearemos otra para el pie de página FooterPDF.cshtml, que nos indicará el número de página del documento.

@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>ContactFooterPDF</title>
    <link rel="stylesheet" href="~/Content/bootstrap.css" />
    <link rel="stylesheet" href="~/Content/Site.css" />
</head>
<body style="background: #ffd800; padding: 0px; margin: 0px;">
    <div class="text-center">
        <strong>
            Página <span id='page'></span> de
            <span id='topage'></span>
        </strong>        

        <script>
            var vars = {};
            var x = window.location.search.substring(1).split('&');
            for (var i in x) {
                var z = x[i].split('=', 2);
                vars[z[0]] = unescape(z[1]);
            }
            document.getElementById('page').innerHTML = vars.page;
            document.getElementById('topage').innerHTML = vars.topage;
        </script>

    </div>
</body>
</html>

Nota: Como vemos en la plantilla para el pie de página, hemos insertado un pequeño script que se encargará de analizar el QueryString de la petición de la página (window.location.search), donde Rotativa inserta como parámetros el número de página page y el total de páginas topage, entre otras variables informativas.

 

El Controlador

Por último, implementaremos el código que generará el archivo PDF a partir de la Vista anteriormente creada. 

Esto lo realizaremos de una manera extremadamente simple y a la vez elegante. Simplemente devolveremos desde la Acción Index()del Controlador HomeController, un objeto de la clase Rotativa.ViewAsPdf el cual recibe como parámetros el nombre de la Vista y el Modelo de datos.

        // GET: Customers
        public ActionResult Index()
        {
            // Define la URL de la Cabecera 
            string _headerUrl = Url.Action("HeaderPDF", "Home", null, "https");
            // Define la URL del Pie de página
            string _footerUrl = Url.Action("FooterPDF", "Home", null, "https");

            return new ViewAsPdf("Index", appDbContext.Customers.ToList())
            {
                // Establece la Cabecera y el Pie de página
                CustomSwitches = "--header-html " + _headerUrl + " --header-spacing 0 " +
                                 "--footer-html " + _footerUrl + " --footer-spacing 0"
                ,PageSize = Rotativa.Options.Size.A4
                //,FileName = "CustomersLista.pdf" // SI QUEREMOS QUE EL ARCHIVO SE DESCARGUE DIRECTAMENTE
                ,PageMargins = new Rotativa.Options.Margins(40, 10, 10, 10)
            };
        }

Como podemos ver en el código, hemos hecho referencia a las Acciones HeaderPDF()FooterPDF(). Estas Acciones serán las encargadas de devolver las Vistas que conformarán la cabecera y el pie de página de nuestro PDF auto-generado.

Por lo tanto, incluiremos estas dos Acciones en nuestro HomeController:

        public ActionResult HeaderPDF()
        {
            return View("HeaderPDF");
        }

        public ActionResult FooterPDF()
        {
            return View("FooterPDF");
        }

En este punto, y si todo a ido bien, ya podemos ejecutar nuestra aplicación ASP.NET MVC y comprobar como se genera dinámicamente el archivo PDF a partir de la Vista Index.cshtml.

rotativa-pdf

Nota: Como vemos en el PDF, hemos resaltado en colores las secciones que conforman la cabecera, el cuerpo y el pie de página. Esta división en colores es altamente recomendable durante el proceso de maquetación, ya que nos permite tener una visión exacta del resultado final en lo que respecta a márgenes, espaciado, etc.

 

Configurando Rotativa.ViewAsPdf()

La clase ViewAsPdf de la librería Rotativa, nos permite a través de sus propiedades, configurar múltiples características del archivo PDF generado dinámicamente. Aspectos como el nombre del archivo, la orientación, los márgenes, tamaño, etc. son fácilmente modificables desde las propiedades del objeto ViewAsPdf.

rotativa-propiedades

Una de las propiedades más interesantes de la clase ViewAsPdf es CustomSwitches. A través de ella, podemos acceder directamente a la opciones de configuración en línea de comandos de la herramienta wkhtmltopdf.

Como vimos anteriormente en el código del Controlador, es en la propiedad CustomSwitches donde le indicamos a Rotativa las Vistas que serán utilizadas como cabecera y pie de página del archivo PDF.

            // Define la URL de la Cabecera 
            string _headerUrl = Url.Action("HeaderPDF", "Home", null, "https");
            // Define la URL del Pie de página
            string _footerUrl = Url.Action("FooterPDF", "Home", null, "https");

            return new ViewAsPdf("Index", appDbContext.Customers.ToList())
            {
                // Establece la Cabecera y el Pie de página
                CustomSwitches = "--header-html " + _headerUrl + " --header-spacing 0 " +
                                 "--footer-html " + _footerUrl + " --footer-spacing 0"
                  
                ,PageMargins = new Rotativa.Options.Margins(40, 10, 10, 10)

            };

Nota: Como en todo proceso de maquetación, no siempre quedarán ajustadas a la primera la nueva cabecera y pie de página al documento PDF generado. Para ello, debemos realizar nosotros este ajuste manualmente a través de los parámetros --header-spacing y --footer-spacing de wkhtmltopdf, y la propiedad PageMargins de la clase ViewAsPdf.

Descargas

Script Tabla- dbo.Customers.sql
Datos de prueba - dbo.Customers.data.sql

   EtiquetasASP.NET MVC PDF .NET Framework

  Compartir


  Nuevo comentario

El campo Comentario es obligatorio.
El campo Nombre es obligatorio.

Enviando ...

  Comentarios

No hay comentarios para este Post.


Perfil para Rafael Acosta en Stack Overflow en español, preguntas y respuestas para programadores y profesionales de la informática.

  Etiquetas

.NET Core .NET Framework .NET MVC .NET Standard AJAX ASP.NET ASP.NET Core ASP.NET MVC Bootstrap Buenas prácticas C# Cookies Entity Framework Gráficos JavaScript jQuery JSON JWT PDF Pruebas Unitarias Seguridad SEO SOAP Sql Server SqLite Swagger Validación Web API Web Forms Web Services WYSIWYG

  Nuevos


  Populares















Utilizamos cookies propias y de terceros para mejorar nuestros servicios y ofrecerle una mejor experiencia de navegación. Si continúa navegando consideramos que acepta su uso. Más información   Acepto