1
\$\begingroup\$

I am trying to merge two pdfs - one from a razor view and one from a file on the server. To create the pdf from the razor view I have used the following nuget package: MVCRazorToPDF.

Unfortunately this didn't allow me to merge an existing file with the pdf so I have downloaded the project and changed the PdfActionResult to accept a mergeFilename.

I then generate the new pdf using the following:

public byte[] GeneratePdfOutput(ControllerContext context, object model = null, string viewName = null,
    Action<PdfWriter, Document> configureSettings = null, string filename = null)
{
    byte[] output;

    if (!string.IsNullOrEmpty(filename) && File.Exists(filename))
    {
        using (var copyDoc = new Document())
        {
            using (var copyStream = new MemoryStream())
            {
                copyDoc.Open();

                PdfCopy copy = new PdfCopy(copyDoc, copyStream);
                copy.CloseStream = false;
                copy.AddDocument(new PdfReader(GetBytes(context, model, viewName, configureSettings)));

                using (var reader = new PdfReader(filename))
                {
                    reader.ConsolidateNamedDestinations();

                    for (int i = 1; i <= reader.NumberOfPages; i++)
                    {
                        PdfImportedPage page = copy.GetImportedPage(reader, i);
                        copy.AddPage(page);
                    }
                }

                copy.Close();
                copyDoc.Close();
                output = copyStream.ToArray();
            }
        }
    }
    else
    {
        output = GetBytes(context, model, viewName, configureSettings);
    }

    return output;
}

private byte[] GetBytes(ControllerContext context, object model = null, string viewName = null, Action<PdfWriter, Document> configureSettings = null)
{
    if (viewName == null)
    {
        viewName = context.RouteData.GetRequiredString("action");
    }

    context.Controller.ViewData.Model = model;

    using (var document = new Document())
    {
        using (var workStream = new MemoryStream())
        {
            PdfWriter writer = PdfWriter.GetInstance(document, workStream);
            writer.CloseStream = false;

            if (configureSettings != null)
            {
                configureSettings(writer, document);
            }

            document.Open();

            using (var reader = new StringReader(RenderRazorView(context, viewName)))
            {
                XMLWorkerHelper.GetInstance().ParseXHtml(writer, document, reader);
                document.Close();
                return workStream.ToArray();
            }
        }
    }
}

I was wondering if there was anyway to do this so that the GetBytes was part of the first function - ie I open a new document and then add the razor and if there is a file on the server, I add that using the same memory stream and document (rather than having to create new ones), without the need for the else

\$\endgroup\$
2
\$\begingroup\$

If you change the first line from

byte[] output;  

to

byte[] output = GetBytes(context, model, viewName, configureSettings);  

and then revert the if condition you can return early.

So the remaining code (if the filename is valid and the file exists) could become

  • by stacking the usings which saves some horizontal spacing
  • by using the byte[] output for the PdfReader ctor
  • and using copyStream.GetBuffer() instead of calling its ToArray() method which eleminates the creation of an additional array

like so

public byte[] GeneratePdfOutput(ControllerContext context, object model = null, string viewName = null,
    Action<PdfWriter, Document> configureSettings = null, string filename = null)
{
    byte[] output = GetBytes(context, model, viewName, configureSettings);  

    if (string.IsNullOrEmpty(filename) || !File.Exists(filename))
    {
        return output;
    }

    using (var copyDoc = new Document())
    using (var copyStream = new MemoryStream())
    {
        copyDoc.Open();

        PdfCopy copy = new PdfCopy(copyDoc, copyStream);
        copy.CloseStream = false;
        copy.AddDocument(new PdfReader(output));

        using (var reader = new PdfReader(filename))
        {
            reader.ConsolidateNamedDestinations();

            for (int i = 1; i <= reader.NumberOfPages; i++)
            {
                PdfImportedPage page = copy.GetImportedPage(reader, i);
                copy.AddPage(page);
            }
        }

        copy.Close();
        copyDoc.Close();
        return copyStream.GetBuffer();
    }
}
\$\endgroup\$
6
  • \$\begingroup\$ So there is no way to get rid of GetBytes function altogether? \$\endgroup\$
    – Pete
    Nov 30 '15 at 13:40
  • \$\begingroup\$ Ahh, it's based on iTextSharp. If no one is able to come up with a better answer then I'll accept this one. Thanks for the improvements \$\endgroup\$
    – Pete
    Nov 30 '15 at 13:52
  • \$\begingroup\$ Just few notes: no need to call copy.Close() and copyDoc.Close() (they're already on using), same for document.Close() in GetBytes()). Also no need to set writer.CloseStream (it's safe to "dispose" MemoryStream twice). Well I'd also pick a better name for GetBytes()... \$\endgroup\$ Nov 30 '15 at 15:51
  • \$\begingroup\$ They should be iTextSharp classes, they're (AFAIK). \$\endgroup\$ Nov 30 '15 at 15:59
  • \$\begingroup\$ @AdrianoRepetti if I don't call the closes, the copyStream isn't set \$\endgroup\$
    – Pete
    Dec 1 '15 at 9:19

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.