PDF generator using Asp.Net MVC views as templates

September 11, 2018
6 min read


Not too long ago, I had to work on a task which was asking to create a PDF for a given page/form. Therefore, in this post I would like to take you in a journey that will end with a generic solution, that can be applied anywhere else in the solution in a simple manner, making use of Asp.Net MVC features.

If you don’t have experience with C#, Asp.Net or MVC please keep reading, as it has as well some good lessons about programming in general.

PDF generator using Asp.Net MVC views as templates. Ion Balan - ASSIST Software


Not too long ago, I had to work on a task which was asking to create a PDF for a given page/form. Therefore, in this post I would like to take you in a journey that will end with a generic solution, that can be applied anywhere else in the solution in a simple manner, making use of Asp.Net MVC features.

If you don’t have experience with C#, Asp.Net or MVC please keep reading, as it has as well some good lessons about programming in general.

I. The existing solution

Represents a class, PdfManager, which is responsible for PDF generation, in a "brute" manner: passing the object for which the PDF is generated, as a parameter. This means that for every form/page/object for which the PDF is generated has its own method. 
Then, depending on what PDF is generated, each method constructs the PDF line by line, cell by cell, applying styles for each of these as required. The result: a class that has more than 1600 lines of code, which is hard to read and understand.

As said above, the styling is mixed in the code, making refinements hard. To generate the PDF, PdfManager is making use of some helpers of different kinds, breaking the Single Responsibility Principle. In contrast, the new class that I’ve created, responsible for PDF generation,  and name it PdfGenerator, to avoid name clash with the existing class, it has only 37 lines.

Obviously, the class itself is not the only thing that is required to generate the PDF, but I wanted to show that if you distribute the responsibilities correctly your code will become a lot simple and easy to read and understand.

II. The new solution

There are a couple of articles on the web describing how to generate PDFs using Asp.Net MVC. Our biggest challenge was the fact that our application was on Asp.Net MVC 3 – yes, I know, very old, out of date, exposed to so many issues and problems that have been fixed since then, but the biggest problem of all was that most of the articles that I found were targeting much newer versions (although in the end, it did not make a difference).   

As such, one of the steps for our solution was to migrate to MVC 4, at least. Initially, my efforts were around making use of RazorGenerator (custom tool for Visual Studio that allows you to precompile the view, to get a smaller published bundle and faster startup time). However, this turned into a problem with the deployment, as it would have required changing that. 

In order to make use of its PrecompiledMvcEngine, RazorGenerator registers this on App_Start, like so: 

public static class RazorGeneratorMvcStart {
        public static void Start() {
            var engine = new PrecompiledMvcEngine(typeof(RazorGeneratorMvcStart).Assembly) {
                UsePhysicalViewsIfNewer = HttpContext.Current.Request.IsLocal
            };

            ViewEngines.Engines.Insert(0, engine);

            // StartPage lookups are done by WebPages.
            VirtualPathFactoryManager.RegisterVirtualPathFactory(engine);
        }
    }

Then in the code, you can make use of the engine like following:


var viewEngine = ViewEngines.Engines.OfType<PrecompiledMvcEngine>().FirstOrDefault();
Once you have a reference for the view engine then you can access the views with FindView method. And once you have the view you can manipulate it as needed.

As such, when we run into problems with the deployment, after a couple of attempts, I realized that, if it is possible to use the PrecompiledMvcEngine in such a manner, then it shouldn’t be a difference if using RazorViewEngine, the Asp.Net MVC engine. And so it was. As such the entire solution consists of the following 3 things:
 
1. PdfGenerator class: ​
using HiQPdf;
    /// <summary>
    /// A generic PDF generator
    /// Note: HiQPdf can be used free for PDF with up to 3 pages.
    /// </summary>
    public class PdfGenerator : IPdfGenerator
    {
        /// <summary>
        /// Generates a PDF, for a specific view/template
        /// </summary>
        /// <param name="controllerName">Controller owning the view/template</param>
        /// <param name="viewName">The view/template name</param>
        /// <param name="viewModel">The view model for view/template</param>
        /// <returns></returns>
        public byte[] GeneratePdf(string controllerName, string viewName, IPdfTemplateViewModel form)
        {
            var templateService = new TemplateService();
            string documentContent = templateService.RenderTemplate(controllerName, viewName, form);

            // instantiate the HiQPdf HTML to PDF converter
            var htmlToPdfConverter = new HtmlToPdf();
            htmlToPdfConverter.Document.Margins = new PdfMargins(20);
            htmlToPdfConverter.Document.PageSize = PdfPageSize.A4;
            htmlToPdfConverter.Document.PageOrientation = PdfPageOrientation.Portrait;


            // render the HTML code as PDF in memory
            byte[] bytes = htmlToPdfConverter.ConvertHtmlToMemory(documentContent, null);

            return bytes;
        }
    }

2. TemplateService class:

public class TemplateService : ITemplateService
    {       
        /// <summary>
        /// Renders a PDF template, returning the resulting page as a string
        /// </summary>
        /// <param name="controllerName">The controller coresponding to view </param>
        /// <param name="viewName">The template name</param>
        /// <param name="viewModel">The view model for template</param>
        /// <returns></returns>
        public string RenderTemplate(string controllerName, string viewName, IPdfTemplateViewModel viewModel)            
        {
            var routeData = new RouteData();
            routeData.Values.Add("controller", controllerName);
            var controllerContext = new ControllerContext(new HttpContextWrapper(new HttpContext(new HttpRequest(null, "http://google.com", null), new HttpResponse(null))), routeData, new FakeController());

            var viewEngine = ViewEngines.Engines.OfType<RazorViewEngine>().FirstOrDefault();
            if (viewEngine == null)
            {
                throw new InvalidOperationException("Could not get and instance for RazorViewEngine.");
            }

            using (var outputWriter = new StringWriter())
            {
                var viewResult = viewEngine.FindView(controllerContext, viewName, "", false);

                if (viewResult.View == null)
                {
                    throw new TemplateServiceException(string.Format("Failed to render template {0} because it was not found.", viewName));
                }

                var viewDictionary = new ViewDataDictionary<IPdfTemplateViewModel>()
                {
                    Model = viewModel
                };


                try
                {
                    var viewContext = new ViewContext(controllerContext, viewResult.View, viewDictionary, new TempDataDictionary(), outputWriter);

                    viewResult.View.Render(viewContext, outputWriter);
                }
                catch (Exception ex)
                {
                    throw new TemplateServiceException("Failed to render template due to a engine failure", ex);
                }

                return outputWriter.ToString();
            }
        }
    }
3. A cshtml file, representing the view/the template that will be rendered. This is a regular MVC view as any other.
Using this will be like this: 
pdfGenerator.GeneratePdf("ControllerName", "TemplateViewName", pdfTemplateModel)
HiQPdf was the only free library that supports the entire spectrum of Html styling. The only problem is that its free for PDF of up to 3 pages.

III. An additional problem that needed to be solved
 

As a side problem to be solved, was the fact that the PDF that it needed to be generated, included a series of questions, which could have child question like following, and needed to be displayed without a question number under parent:

1. Question 1: Answer
  • Question 1 child question 1: child question answer 1 
  • Question 1 child question 2: child question answer 2 
Applying "brute force" on this will result in a convoluted code with a lot of ifs. The solution was to apply the Chain of Responsibilities design pattern, and ended with the following class:
 
    public class QuestionAnswerViewModel
    {
        public QuestionAnswerViewModel()
        { }

        public QuestionAnswerViewModel(Answer answer)
        {
            if (answer.Value.HasValue)
            {
                Answer = answer.Value.Value ? Shared.Label_Yes : Shared.Label_No;
            }
            else if (!string.IsNullOrWhiteSpace(answer.Text))
            {
                Answer = answer.Text;
            }
            Question = answer.Question.Text;            
        }

        public string Answer { get; set; }
        public int Order { get; set; }
        public string Question { get; set; }
        public QuestionAnswerViewModel Successor { get; protected set; }

        public void SetSuccessor(QuestionAnswerViewModel successor)
        {
            Successor = successor;
        }
    }  

This then allows you to call the DisplayTemplate for this class recursively if any of  the questions have a Successor:

@if (Model.Successor != null)
{
  @Html.DisplayFor(m => m.Successor,"QuestionAnswer")
}

Share on:

Want to stay on top of everything?

Get updates on industry developments and the software solutions we can now create for a smooth digital transformation.

* I read and understood the ASSIST Software website's terms of use and privacy policy.

Frequently Asked Questions

1. What is ASSIST Software's development process?  

The Software Development Life Cycle (SDLC) we employ defines the following stages for a software project. Our SDLC phases include planning, requirement gathering, product design, development, testing, deployment, and maintenance.

2. What software development methodology does ASSIST Software use?  

ASSIST Software primarily leverages Agile principles for flexibility and adaptability. This means we break down projects into smaller, manageable sprints, allowing continuous feedback and iteration throughout the development cycle. We also incorporate elements from other methodologies to increase efficiency as needed. For example, we use Scrum for project roles and collaboration, and Kanban boards to see workflow and manage tasks. As per the Waterfall approach, we emphasize precise planning and documentation during the initial stages.

3. I'm considering a custom application. Should I focus on a desktop, mobile or web app?  

We can offer software consultancy services to determine the type of software you need based on your specific requirements. Please explore what type of app development would suit your custom build product.   

  • A web application runs on a web browser and is accessible from any device with an internet connection. (e.g., online store, social media platform)   
  • Mobile app developers design applications mainly for smartphones and tablets, such as games and productivity tools. However, they can be extended to other devices, such as smartwatches.    
  • Desktop applications are installed directly on a computer (e.g., photo editing software, word processors).   
  • Enterprise software manages complex business functions within an organization (e.g., Customer Relationship Management (CRM), Enterprise Resource Planning (ERP)).

4. My software product is complex. Are you familiar with the Scaled Agile methodology?

We have been in the software engineering industry for 30 years. During this time, we have worked on bespoke software that needed creative thinking, innovation, and customized solutions. 

Scaled Agile refers to frameworks and practices that help large organizations adopt Agile methodologies. Traditional Agile is designed for small, self-organizing teams. Scaled Agile addresses the challenges of implementing Agile across multiple teams working on complex projects.  

SAFe provides a structured approach for aligning teams, coordinating work, and delivering value at scale. It focuses on collaboration, communication, and continuous delivery for optimal custom software development services. 

5. How do I choose the best collaboration model with ASSIST Software?  

We offer flexible models. Think about your project and see which models would be right for you.   

  • Dedicated Team: Ideal for complex, long-term projects requiring high continuity and collaboration.   
  • Team Augmentation: Perfect for short-term projects or existing teams needing additional expertise.   
  • Project-Based Model: Best for well-defined projects with clear deliverables and a fixed budget.   

Contact us to discuss the advantages and disadvantages of each model. 

ASSIST Software Team Members

See the past, present and future of tech through the eyes of an experienced Romanian custom software company. The ASSIST Insider newsletter highlights your path to digital transformation.

* I read and understood the ASSIST Software website's terms of use and privacy policy.

Follow us

© 2025 ASSIST Software. All rights reserved. Designed with love.