Creare una ActionResult custom per renderizzare un PDF in MVC sfruttando una view

di Stefano Mostarda, in ASP.NET,

Alzi la mano chi nelle proprie applicazioni web ha dovuto generare dei PDF. Ora, alzi la mano chi di voi ha usato iTextSharp per generare questi PDF.

Se facessi queste domande durante uno speech, sono sicuro che il 95% dei presenti alzerebbe la mano (me compreso). Sono altrettanto sicuro che di questo 95%, alla seconda domanda il 90% manterrebbe la mano alzata.

Molto spesso, la generazione dei PDF è dinamica cioè il loro contenuto (o una parte) deve essere generato da una sorgente dati. Per esempio, mi è capitato di dover generare un PDF con una griglia i cui dati provenivano da un database. In questi casi, generare pezzi di PDF al volo usando gli oggetti di iTextSharp comporta una notevole quantità di codice. In altri casi, mi è capitato che il PDF dovesse essere generato partendo da una pagina web e dovevano essere semplicemente riempiti alcuni campi con dei dati provenienti da un database.

In questi casi la soluzione che preferisco è quella di creare un template HTML con dei segnaposto che poi modifico con dati reali (un segnaposto può rappresentare un nome, una partita iva, una lista di clienti, e così via). Una volta costruito il codice HTML definitivo, questo viene dato in pasto a iTextSharp che lo parsa e ne genera un PDF.

Come sempre, sviluppare in MVC questa funzionalità ha una strada semplice (che in genere è anche quella meno pulita) e una strada più complessa (che però è riutilizzabile e meglio integrata nel framework). La strada più semplice è quella di salvare il template HTML in un file nella cartella App_Data e nella action leggere il file, rimpiazzare i segnaposto, dare l?HTML in pasto a iTextSharp facendosi tornare il PDF come array di byte che alla fine inviamo al client. A prima vista sembrano molti passi, ma in realtà sono semplici e alcuni di questi comportano una sola riga di codice. Quello che non mi piace di questa tecnica è che chi sviluppa la action deve anche occuparsi della renderizzazione del PDF.

Nella logica di MVC, il risultato finale che deve essere inviato al client deve essere generato dalla ActionResult che la action restituisce e non dalla action stessa. Per questo motivo la strada che preferisco è quella di creare una ActionResult custom che nel suo metodo ExecuteResult genera il codice HTML processando una view (che sarebbe il template HTML) e poi da il codice HTML in pasto a iTextSharp che genera il PDF il quale viene recuperato come array di byte e inviato al client. In questo modo, chi scrive la action deve solo preoccuparsi di ritornare la ActionResult custom senza "sporcarsi le mani" con la generazione del PDF. Oltre al vantaggio di una maggior integrazione con il framework MVC, questa tecnica garantisce anche un facile riutilizzo del codice in quanto potremmo mettere la nostra ActionResult custom in un assembly e renderla disponibile in altri progetti MVC.

Passiamo ora dalla teoria alla pratica. Le cose da fare sono 2:

  1. Creare una view per generare il codice HTML da dare in pasto a iTextSharp
  2. Creare una ActionResult custom che genera il PDF

Non mostro il codice della view in quanto è una normale view Razor (che genera il codice HTML come da specifiche di iTextSharp). La cosa interessante è la ActionResult custom: PDFResult.

public class PDFResult : ActionResult
{
      string _viewName;
      object _model;      public PDFResult(string viewName, object model)
      {
            _viewName = viewName;
     _model = model;
      } 
      public override void ExecuteResult(ControllerContext context)
      {
            string html;
            using (StringWriter sw = new StringWriter())
            {
                  //Renderizza la view
                  context.Controller.ViewData.Model = _model;
                  ViewEngineResult viewResult = ViewEngines.Engines.FindView(context, _viewName, null);
                  ViewContext viewContext = new ViewContext(context, viewResult.View, context.Controller.ViewData, context.Controller.TempData, sw);
                  viewResult.View.Render(viewContext, sw);
       viewResult.ViewEngine.ReleaseView(context, viewResult.View);

                  //Recupera il codice HTML generato dalla view
                  html = sw.ToString();
            }

            //Trasforma il codice HTML in un PDF
            using (MemoryStream msOutput = new MemoryStream())
            {
                  using (TextReader reader = new StringReader(html))
                  { 
                        using (Document document = new Document(PageSize.A4, 30, 30, 30, 30))
                        {
                              using (PdfWriter writer = PdfWriter.GetInstance(document, msOutput))
                              {
                                    using (HTMLWorker worker = new HTMLWorker(document))
                                    {
                                          document.Open();
                                          worker.StartDocument();
                                          worker.Parse(reader); 
                                          worker.EndDocument();
                                          worker.Close();
                                          document.Close();
             }
           }
         }
                  }

                  //Scrive il PDF nello stream di output e le intestazioni HTTP necessarie
                  var response = context.HttpContext.Response;
                  response.Clear();
                  response.ContentType = "application/pdf";
                  response.AddHeader("content-disposition", "attachment;filename=file.pdf");
                  var buffer = msOutput.GetBuffer();
                  response.OutputStream.Write(buffer, 0, (int)buffer.Length);
            }
      }
}

Una delle cose che più mi piace di questa action è che la generazione del codice HTML passa per il motore di view di ASP.NET quindi non dobbiamo inventarci nulla; è tutto perfettamente integrato nel framework. Inoltre, il codice di generazione del PDF è estremamente semplice e pulito il che non fa mai male.

A questo punto, nella action possiamo scrivere un codice del genere:

  public ActionResult MyAction(){
  var model = //general il model per la view che viene renderizzata dalla calsse PDFResult
  return new PDFResult("nomeview", model);
}
 

Che ne pensate?

Stay tuned?

Commenti

Visualizza/aggiungi commenti

| Condividi su: Twitter, Facebook, LinkedIn

Per inserire un commento, devi avere un account.

Fai il login e torna a questa pagina, oppure registrati alla nostra community.

Nella stessa categoria
I più letti del mese