dCarMal
dCarMal

Reputation: 151

Convert Razor HTML into downloadable PDF

I have a dynamic filtered table of elements, and each one have a group of buttons to operate with them. One of them shows a partial HTML generated with Razor with the element's data.

Table entry:

foreach (var item in listado.Where(x => x.Raw != null)) {
<tr>
    <td>@item.Id</td>
    <td>@item.Usuario</td>
    <td>@item.NIF_CIF</td>
    <td>
        @if (!(String.IsNullOrEmpty(item.Telefono)) && !(String.IsNullOrWhiteSpace(item.Telefono))) {
            @item.Telefono
        } else {
            @Html.Raw("No disponible")}
    </td>
    <td>@Math.Round(item.Raw.PrecioTotal).ToString("0.##")&nbsp;&euro;</td>
    <td>@item.FechaCreacion.ToShortDateString()</td>
    <td class="btn-group-sm" role="group">
        <button class="eliminar btn btn-secondary btn-danger" data-itemid="@item.Id">Eliminar</button>
        <button class="convertirPDF btn btn-secondary btn-info" data-itemid="@item.Id">PDF</button>
        <button class="detalles btn brn-secondary btn-info" data-itemid="@item.Id">Detalles</button></td>
</tr>
<tr id="vistaDetalles@(item.Id)" hidden>
    <td colspan="7">
        @Html.Partial("_PresupuestoFinal", item)
    </td>
</tr> }

The element I want to work with is generated in the line

@Html.Partial("_PresupuestoFinal", item)

So then, with jQuery, I provide function to those buttons. The PDF button code is:

$(".convertirPDF").on("click", function (id) {
    var itemId = $(this).data('itemid');
    var presupuesto = $('#content').html($(this).find('#vistaDetalles' + itemId).html());
    Pdf(presupuesto);
});

function Pdf(presupuesto) {
    presupuestoHTML = presupuesto;

    $.ajax({
        method: "GET",
        url: 'DescargarPDF',
        data: { presupuestoHTML: presupuesto },
        cache: false,
        async: true,
    });
};

But every tim I press the button, I get on console:

TypeError: can't convert undefined to object

Referring to the line when I invoke the function Pdf. What I want to do is to pick that particular html (the one corresponding to the element) and convert it to a PDF file. Since I can't get enough info to know where the error is, what am I doing wrong?

BTW that url: 'DescargarPDF line is pointing to the method in my controller.

EDIT

Here's my controller method as requested:

public void DescargarPDF (string presupuestoHTML) {

        Response.Clear();
        Response.ContentType = "application/pdf";
        Response.AddHeader("content-disposition", "attachment;filename=" + "PDF.pdf");
        Response.Cache.SetCacheability(HttpCacheability.NoCache);

        MemoryStream ms = new MemoryStream();
        TextReader txtReader = new StringReader(presupuestoHTML);

        // Creamos un objeto de clase itextsharp document
        Document doc = new Document(PageSize.A4, 25, 25, 30, 30);

        // Creamos un pdfwriter de itextsharp que lee el documento y dirige un stream XML a un archivo
        PdfWriter oPdfWriter = PdfWriter.GetInstance(doc, ms);

        // Creamos un trabajador para parsear el documento
        HTMLWorker htmlWorker = new HTMLWorker(doc);

        // Abrimos el documento y se lo pasamos al trabajador
        doc.Open();
        htmlWorker.StartDocument();

        // Parseamos el presupuesto en html al documento
        htmlWorker.Parse(txtReader);

        // Cerramos el documento y el trabajador
        htmlWorker.EndDocument();
        htmlWorker.Close();
        doc.Close();

        var bPDF = ms.ToArray();

        Response.BinaryWrite(bPDF);
        Response.End();
    }

The script doesn't hit it.

2nd EDIT

So I got more info on the error:

presupuestoHTML[toArray]

Is what's causing the error. Don't know where, or how.

3rd EDIT

Ok so I know now the issue is on the script retrieving the html I want. I changed that line:

$(".convertirPDF").on("click", function (id) {
    itemId = $(this).data('itemid');
    presupuesto = document.documentElement.getElementsByTagName('vistaDetalles' + itemId);
    Pdf(presupuesto);
});

And now the error in console is:

TypeError: 'item' called on an object that does not implement interface HTMLCollection.

I'll keep looking into it, hope this gets the issue clearer.

2nd UPDATE

I've been working on this, I got everything working except for the file download. I'll explain:

Here's the script, it hits the controller method with the proper data.

$(".convertirPDF").on("click", function (id) {
    var itemId = $(this).data('itemid');
    // var presupuesto = $('#vistaDetalles' + itemId).html();
    Pdf(itemId);
});

function Pdf(itemid) {
    var id = itemid;

    $.ajax({
        method: "POST",
        url: 'DescargarPDF',
        data: { itemId: id },
        cache: false,
        async: true,
    });
};

So that works. Here's my controller method, where I suspect the trouble is caused:

 public FileResult DescargarPDF (int itemId) {
        var presupuesto = ReglasNegocio.Fachada.Consultas.ObtenerPresupuesto(itemId);             
        var archivo = new Rotativa.PartialViewAsPdf("_PresupuestoFinal", presupuesto) { FileName = "Presupuesto_" + itemId + ".pdf" };

        return File(archivo.FileName, "application/pdf");
    }

That's my last try so far. I've tried Rotativa's ActionAsPdf as well and I got on the browser console a stream of data (which I have to guess it's the pdf file) not the downloadable file. I've also tried other options like converting the file to a byte array and stream it but since I don't want to save the file, that option is discarded (functions require a file path, which I'm not able to provide because the file is not saved, still on memory). Still working on it.

Upvotes: 1

Views: 1058

Answers (2)

dCarMal
dCarMal

Reputation: 151

I asked another question to try to solve the not downloading file issue, and I got it.

Before anything, I was doing the request through ajax because I thought to be a good way to do so, but as it turns out, there's a much simpler way: not using ajax.

So, I removed the script for the button and the button itself, so now looks like this:

<a href="DescargarPDF/[email protected]" target="_blank" class="btn btn-secondary btn-info">PDF</a>

It has the same appearance than before, but it's actually a link to my controller's method, which right now looks like this:

public FileResult DescargarPDF (int itemId) {
        var presupuesto = ReglasNegocio.Fachada.Consultas.ObtenerPresupuesto(itemId);             
        var archivo = new Rotativa.PartialViewAsPdf("_PresupuestoFinal", presupuesto) { FileName = "Presupuesto_" + itemId + ".pdf", PageSize = Rotativa.Options.Size.A4 };
        var binario = archivo.BuildFile(this.ControllerContext);        

        return File(binario, "application/pdf", archivo.FileName);           
    }

I know in most cases this wouldn't be a valid solution since I just left ajax behind, but there are many other questions where the answer worked for them and they still use ajax to manage the request.

Still, I hope this helps. Thanks to everyone. Happy coding.

UPDATE

I just found out why my PDF file was dropped into console, check the other question, I left a little explanation for that particular issue. Thanks everyone.

Upvotes: 1

Kerlos Bekhit
Kerlos Bekhit

Reputation: 128

You could use only javascript:

Upvotes: 0

Related Questions