Pages

Friday, 1 March 2013

MVC - SSL, Testing and Production Nuget Package Released

Following on from my earlier post regarding awareness of SSL flag removal that can cause security holes ending up in released code from a development environment, I've released RemoteRequireHttps as a package on Nuget.org

Please download and use as required!


Thursday, 3 January 2013

HTML to PDF Conversion in MVC 4

My preferred tool for dynamically creating PDF documents in a web application is iTextSharp (http://sourceforge.net/projects/itextsharp/) however, I recently had a PDF generation problem to solve where iTextSharp couldn't help.

One thing that seems to be an issue is the generation of PDF documents from HTML, and ensuring CSS used to style the HTML is included in the generation. If the CSS is not included, then the resulting PDF can be greatly different from the source HTML. This can be a problem if for example you need to convert a HTML page returned from a third party's webservice. You have no control over the source HTML, but need the PDF to be styled as per the returned HTML. This situation is commonly encountered with invoice generation, delivery label generation, that sort of thing.

There is an excellent program available that will convert HTML to PDF and preserve all styling, however it is  a command line program - wkhtmltopdf (http://code.google.com/p/wkhtmltopdf/). The solving of my HTML to PDF program was resolved by using this program, but calling it from an MVC 4 Web Application.

This is achieved by using a new instance of the System.Diagnostics.Process class, to create a process calling the wkhtmltopdf application. A URL to the HTML page I want to convert is passed to the wkhtmlpdf application through the arguments set when calling the application. Wrapping this functionality into a method looks like the following:


public static byte[] ConvertHtmlToPdf(string url)
        {

            const string fileName = " - ";
            const string wkhtmlDir = @"C:\Program Files (x86)\wkhtmltopdf\";
            const string wkhtml = @"C:\Program Files (x86)\wkhtmltopdf\wkhtmltopdf.exe";
            var p = new Process
                {
                    StartInfo =
                        {
                            CreateNoWindow = true,
                            RedirectStandardOutput = true,
                            RedirectStandardError = true,
                            RedirectStandardInput = true,
                            UseShellExecute = false,
                            FileName = wkhtml,
                            WorkingDirectory = wkhtmlDir
                        }
                };

            var switches = "";
            switches += "--print-media-type ";
            switches += "--margin-top 10mm --margin-bottom 10mm --margin-right 10mm --margin-left 10mm ";
            switches += "--page-size Letter ";
            p.StartInfo.Arguments = switches + " " + url + " " + fileName;
            p.Start();

            //read output
            var buffer = new byte[32768];
            byte[] file;
            using (var ms = new MemoryStream())
            {
                while (true)
                {
                    var read = p.StandardOutput.BaseStream.Read(buffer, 0, buffer.Length);

                    if (read <= 0)
                    {
                        break;
                    }
                    ms.Write(buffer, 0, read);
                }
                file = ms.ToArray();
            }

            // wait or exit
            p.WaitForExit(60000);

            // read the exit code, close process
            var returnCode = p.ExitCode;
            p.Close();
            return returnCode == 0 ? file : null;
        }

You may need to change the location of the program file folder!

I can then call this method using a controller action, passing in a URL:


[HttpPost]
        public ActionResult Index(string htmlUrl)
        {
            
            var pdf = ConvertHtmlToPdf(htmlUrl);
            var ms = new MemoryStream(pdf);
            return new FileStreamResult(ms, "application/pdf");
        }

My full Home Controller looks like:


using System.Diagnostics;
using System.IO;
using System.Web.Mvc;

namespace HtmlToPDF.Controllers
{
    public class HomeController : Controller
    {
        //
        // GET: /Home/

        public ActionResult Index()
        {
            return View();
        }

        [HttpPost]
        public ActionResult Index(string htmlUrl)
        {
            
            var pdf = ConvertHtmlToPdf(htmlUrl);
            var ms = new MemoryStream(pdf);
            return new FileStreamResult(ms, "application/pdf");
        }

        public static byte[] ConvertHtmlToPdf(string url)
        {

            const string fileName = " - ";
            const string wkhtmlDir = @"C:\Program Files (x86)\wkhtmltopdf\";
            const string wkhtml = @"C:\Program Files (x86)\wkhtmltopdf\wkhtmltopdf.exe";
            var p = new Process
                {
                    StartInfo =
                        {
                            CreateNoWindow = true,
                            RedirectStandardOutput = true,
                            RedirectStandardError = true,
                            RedirectStandardInput = true,
                            UseShellExecute = false,
                            FileName = wkhtml,
                            WorkingDirectory = wkhtmlDir
                        }
                };

            var switches = "";
            switches += "--print-media-type ";
            switches += "--margin-top 10mm --margin-bottom 10mm --margin-right 10mm --margin-left 10mm ";
            switches += "--page-size Letter ";
            p.StartInfo.Arguments = switches + " " + url + " " + fileName;
            p.Start();

            //read output
            var buffer = new byte[32768];
            byte[] file;
            using (var ms = new MemoryStream())
            {
                while (true)
                {
                    var read = p.StandardOutput.BaseStream.Read(buffer, 0, buffer.Length);

                    if (read <= 0)
                    {
                        break;
                    }
                    ms.Write(buffer, 0, read);
                }
                file = ms.ToArray();
            }

            // wait or exit
            p.WaitForExit(60000);

            // read the exit code, close process
            var returnCode = p.ExitCode;
            p.Close();
            return returnCode == 0 ? file : null;
        }

    }
}


And my Home / Index view looks like:

@{
    ViewBag.Title = "Index";
}

<h2>HTML to PDF Conversion Example</h2>
@using (Html.BeginForm("Index", "Home"))
{
    <div>
        URL to Convert: <input type="text" name="htmlUrl" width="500px" />
    </div>
    <div>
        <input type="submit" value="Convert" /> 
    </div>
}

A sample MVC 4 Web Application can be downloaded from here: http://www.web-architecture.com/Files/HtmlToPdf.zip

Please Note: You will need to install wkhtmltopdf from here: http://code.google.com/p/wkhtmltopdf/ for the sample to work. Don't forget to check the program folder location before running!