ASP.NET MVC Action Results and PDF Content

The Action Result in ASP.NET MVC provides a simple and versatile means of returning different types of response to the browser. Want to serve a PDF file with dynamically-generated content? Do an SEO-friendly permanent redirect? Dino shows you how simple this can be using a tailor-made ActionResult class

In ASP.NET Web Forms, the vast majority of HTTP requests are for pages, upon which an HTML stream is returned. You can force an ASP.NET Web page to return a different type of response such as an image but that is a rather unnatural action. In fact, if you served an image from an ASPX endpoint, you would set up a much more costly operation than that from a plain custom HTTP handler. This is because ASP.NET Web Forms is a framework designed around the page and HTML content whereas ASP.NET MVC can serve any type of content at the same cost.

In this article, I’ll delve deep into the hidden code that takes the return value of a controller’s action method down to the browser. Along the way, I’ll discuss the implementation of custom action result types.

The Result of an Action

In ASP.NET MVC, each HTTP request is mapped to an action method defined on a controller class. The action method is merely a public method with no special constraints on the input parameters and is forced to return a type that inherits from a system type-the ActionResult type. More precisely, you can design an action method to return any .NET type, including primitive and complex types. You can also return void. If the action method is void, the actual type being processed is EmptyResult. If the type is any .NET type that doesn’t inherit ActionResult, the actual response is encapsulated in a ContentResult type.

The actual return value of any controller action is an object that inherits from ActionResult. As the name suggests, this object represents the result of the action: It embeds data and knows how to process it in order to generate the response for the browser. It is important to note that the ActionResult object is not what the client browser is going to receive. Getting an ActionResult object is only the first step to finalize the request.

Here’s the code of the ActionResult class as returned by .NET Reflector. As you can see, ActionResult is an abstract class with just one overridable method-ExecuteResult.

The response for the browser is generated and written to the output stream by invoking ExecuteResult on a concrete type that derives from ActionResult. The class that does this is the action invoker, a system component that governs the whole process of executing a request and creating the response for the browser.

You can envisage the ActionResult class as being a way to encapsulate the particular type of response that you want to send to the browser. The response certainly comprises the actual data but it also includes the content type, the status code, and any cookies and headers that you intend to send. All of these things are aspects of the response you might want to control through a tailor-made ActionResult class.

Inside Real-World Action Result Classes

The mechanics of action result classes is best understood by taking a tour of a couple of system-provided action result classes. Let’s start with a very simple class such as HttpStatusCodeResult.

As you can see, all it does is to set the status code and description of the HTTP response object. In ASP.NET MVC 3, a specific HTTP response class is built on top of HttpStatusCodeResult; the HttpUnauthorizedResult class. As the code listing shows, this class is just a wrapper that does nothing more than set the status code:

To create, for example, a HttpNotFoundResult custom type, all you have to do is to duplicate the previous code and just set the proper status code; 0x194 (404) in this case.

A slightly more sophisticated example is the FileResult class. This class supplies a public property, the ContentType property that contains the type of the data being written to the output stream.

In the implementation of ExecuteResult, the class simply downloads the content of a given file. FileResult is a base class, and in fact it exposes an abstract method-the WriteFile method-through which derived class can specify how to download and where and how to get the bits. It is interesting to note the role of the FileDownloadName property. The property doesn’t refer to the name of the server file to return to the browser. Instead, it gets and sets the content-disposition header. The header gives a name for the file that can be used to save the file locally on the client, and the browser would use  to display the default name within a file-download dialog box.

The class FilePathResult builds on top of FileResult and just adds the ability to download any type of file. Here’s the source code:

This class defines a FileName property and uses the native TransmitFile method of the Response object to download the file. With this information to hand, we can conclude that the action result object is simply a way to encapsulate all the tasks you need to accomplish in particular situations such as:

  • When a requested resource is missing
  • When a requested resource is redirected
  • When some special response must be served to the browser

Let’s examine a few interesting cases where you might want to have custom action result objects.

Permanent Redirect

Suppose that at some point you decide to expose a given feature of your application through another URL but still need to support the old URL. To increase your Search-Engine Optimization (SEO) ratio, you might want to implement a permanent redirect instead of a classic (temporary) HTTP 302 redirect. ASP.NET MVC 3 supplies a RedirectResult class with a Boolean property to make the redirect permanent via a HTTP 301 status code. This feature, however, is lacking in ASP.NET MVC 2. Anyway, it would be a good exercise to have a look at a possible implementation that follows closely that of RedirectResult in ASP.NET MVC.

Having this class available you can easily move your features around without impacting the SEO level of your application. Here’s how to use this class in a controller method.

Figure 1 shows the result of the call as it shows up in FireBug.

1321-F1_small.jpg

Figure 1. The original URL results permanently moved.

Returning PDF Data

A common developer requirement is to return binary data from a request. Within the category of ‘binary data’ falls many different types such as the pixels of an image, the content of a PDF file, or even a Silverlight package. Frankly, you don’t really need an ad hoc action result object to deal with binary data. Among the built-in action result objects, you can certainly find one that helps you to work with your particular type of binary data.

If the content you want to transfer is stored within a disk file, you can use the FilePathResult object. If your content is available through a stream you use FileStreamResult and you opt for FileContentResult if you have it available as a byte array. All these objects derive from FileResult and differ from one another only in the way that they write out data to the response stream. Let’s see what it takes to return some PDF data.

To be honest, for the task “return a PDF file from an action method”, the big problem to solve is how you get hold of the PDF content. If your PDF content is a static resource such as a server file, then all you need is using FilePathResult. Better yet, you can use the ad hoc File method as below:

To create PDF content on the fly, you can use a bunch of libraries such as Report.net or iTextSharp. Some commercial products and various open-source projects also let you create PDF content from HTML content. This would be particularly interesting for an ASP.NET MVC application in that you can grab a partial view and turn it into downloadable PDF content. A simple and effective tool that turns a web page or HTML document into PDF is wkhtmltopdf-a command line to you use as below:

wkhtmltopdf [url] [pdf-file]

The link where you can get the tool is http://code.google.com/p/wkhtmltopdf. In ASP.NET MVC, you deploy the executable to the server, configure permissions properly so that a new file can be created in a given folder, and then invoke the executable programmatically:

In this case, getpdf is assumed to be the name of an action method in your particular controller class. There are a couple of details that you’ll need to set in this code snippet. First, how do you locate the executable (variable PATH or perhaps the application’s working directory)? Second, where do you save the PDF file? How do you deal with related write permissions? Once you have the PDF file, you just pass its path to the TransmitFile of the Response object for actual transmission.

Creating PDF Content from Word Documents

You might want to return PDF content that is created from a fixed template such as a Word document template (*.dotx). If you don’t mind having Word installed on the Web server you can quickly develop a solution by using interop assemblies. Here’s the code you would need to create a new Word document from a fixed template padded with bookmarks. Next, you fill in the bookmarks with fresh data and save it as PDF.

The first parameter indicates the name of the resulting PDF file. The second argument is the name of the Word DOTX template. Finally, the remaining parameters are for the data to store in the document via bookmarks. In particular, the template must have a couple of bookmarks with fixed names (the ones in the code are merely examples). Once the document is finalized and saved, you may close the Word application but you need to choose the close option wdDoNotSaveChanges as otherwise it would pop up a dialog box which would be very, very, bad for an unattended server environment.

A moment ago, I just raised a little doubt on the effectiveness of using an Office application from within a server environment such as ASP.NET. Actually, there’s a KB article from Microsoft that marks it as definitely not being recommended practice. The URL is http://support.microsoft.com/?id=257757. Overall, the reason for not using Office applications server-side is that these applications may exhibit an unpleasant behavior in terms of scalability, permissions, and interactivity. Having said that, as long as this was not a critical production system, and if you can find the right balance of permissions and parameters and fine tune the ASP.NET application the way you want the only serious concern is scalability. For this reason, you might want to move Office functions to a service that does the work in isolation and returns a PDF stream.

The companion code for this article provides a sample ASP.NET MVC project. The code works just fine as long as you use the embedded Visual Studio Web server. If you move it to a real IIS Web server saving the file locally fails because of default security permissions. The ASP.NET default account doesn’t, by default, have write permission on the folder where the file is created. Figure 2 shows the sample PDF document created by previous code. Note the typical ASP.NET MVC about URL and then the content in the browser.

1321-F2_small.jpg

Figure 2. Downloading a PDF document

Final Considerations

When it comes to PDF in ASP.NET MVC, the biggest problem is not that of returning the content but just of creating it. What’s the template of the document? Most libraries offer a markup language or an API through which you “draw” content on a surface. For example, this is what you need for a trivial hello-world using Report.net. The source code is adapted from one of the examples coming with the library. (Note that the library is under the AGPL license, meaning that either your project is open-source or you need to buy a commercial license.)

For a fixed template, Word offers a great tool at the cost of running Office on the server. In some cases, an interesting workaround would be to create a PDF template with input forms (this requires Adobe tools) and then using PDF libraries to just fill in those values and return the saved template.

As it often happens in software, the major difficulty is not technical but analytical. PDF content from ASP.NET MVC is no exception.