Introduction
So I have been doing this Nancy series for a while now and the demoproject is still up at Github.
Today I'm going to try and return a pdf from a request.
The Response.
So instead of returning json or html or some such I want to return a pdf page. I guess I could write a complete viewengine if needed but I don't need that I just want my trees module to return a pdf in a certain format when requested.
This is actually very simple to do you just need to return an object that inherits from Response and that makes the pdf. I used itextsharp to make the pdf and Nancy did all the heavy lifting from there on in ;-). ITextsharp is on nuget so you should use that.
And here is my response.
- using System;
- using System.IO;
- using Nancy;
- using NancyDemo.Csharp.Model;
- using iTextSharp.text;
- using iTextSharp.text.pdf;
- namespace NancyDemo.Csharp.Processors
- {
- public class TreeModelPdfResponse : Response
- {
- public TreeModelPdfResponse(TreeModel model)
- {
- this.Contents = GetPdfContents(model);
- this.ContentType = "application/pdf";
- this.StatusCode = HttpStatusCode.OK;
- }
- private Action<Stream> GetPdfContents(TreeModel model)
- {
- return stream =>
- {
- var oDoc = new Document(PageSize.A4);
- PdfWriter.GetInstance(oDoc, stream);
- oDoc.Open();
- oDoc.Add(new Paragraph("This a Tree"));
- oDoc.Add(new Paragraph("Id: " + model.Id));
- oDoc.Add(new Paragraph("Genus: " + model.Genus));
- oDoc.Close();
- };
- }
- }
- }
And in my Treesmodule I will add this Get-method.
- Get["/trees/pdf/{Id}"] = parameters =>
- {
- int result;
- var isInteger = int.TryParse(parameters.id, out result);
- var tree = treeService.FindById(result);
- if (isInteger && tree != null)
- {
- return new TreeModelPdfResponse(tree);
- }
- else
- {
- return HttpStatusCode.NotFound;
- }
- };
So if I now go to my page http://localhost/trees/pdf/1 I will get my custom pdf.
Or I can just embed it in an iframe of course.
Which would look like this in our code.
- <div id="pdf">
- <iframe width="400" height="500" src="/trees/pdf/@Model.Id" id="pdf_content" />
- </div>
Now that was pretty simples, huh.
Content negotiation.
Of course you might have noticed that my get looks a lot like the get I already have and a pdf is just a MIME-type like json or xml. So if I could just tell my service that I want application/pdf than why wouldnt it just give me that? Well it can.
To test this I used easyhttp (because I could not find how to do this from within a html page(yet).
- using System;
- using System.IO;
- using EasyHttp.Http;
- namespace NancyDemo.Csharp.Easyhttp
- {
- class Program
- {
- private static void Main(string[] args)
- {
- var http = new HttpClient();
- http.Request.Accept = "application/pdf";
- http.Request.ParametersAsSegments = true;
- http.GetAsFile("http://localhost:65367/trees/1", "e:\\temp\\Test.pdf");
- Console.ReadLine();
- }
- }
- }
My Get in my NancyModule uses the Negotiate.
Like this.
- Get["/trees/{Id}"] = parameters =>
- {
- int result;
- var isInteger = int.TryParse(parameters.id, out result);
- var tree = treeService.FindById(result);
- if(isInteger && tree != null)
- {
- return Negotiate.WithModel(tree);
- }
- else
- {
- return HttpStatusCode.NotFound;
- }
- };
I then just need to add a Processor class.
- using System;
- using System.Collections.Generic;
- using Nancy;
- using Nancy.Responses.Negotiation;
- namespace NancyDemo.Csharp.Processors
- {
- public class PdfProcessor : IResponseProcessor
- {
- private static readonly IEnumerable<Tuple<string, MediaRange>> extensionMappings =
- new[] { new Tuple<string, MediaRange>("pdf", MediaRange.FromString("application/pdf")) };
- public ProcessorMatch CanProcess(MediaRange requestedMediaRange, dynamic model, NancyContext context)
- {
- if (IsExactPdfContentType(requestedMediaRange))
- {
- return new ProcessorMatch
- {
- ModelResult = MatchResult.DontCare,
- RequestedContentTypeResult = MatchResult.ExactMatch
- };
- }
- return new ProcessorMatch
- {
- ModelResult = MatchResult.DontCare,
- RequestedContentTypeResult = MatchResult.NoMatch
- };
- }
- private static bool IsExactPdfContentType(MediaRange requestedContentType)
- {
- if (requestedContentType.Type.IsWildcard && requestedContentType.Subtype.IsWildcard)
- {
- return true;
- }
- return requestedContentType.Matches("application/pdf");
- }
- public Response Process(MediaRange requestedMediaRange, dynamic model, NancyContext context)
- {
- return new PdfResponse(model);
- }
- public IEnumerable<Tuple<string, MediaRange>> ExtensionMappings
- {
- get { return extensionMappings; }
- }
- }
- }
And a response class.
- using System;
- using System.IO;
- using Nancy;
- using NancyDemo.Csharp.Model;
- using iTextSharp.text;
- using iTextSharp.text.pdf;
- namespace NancyDemo.Csharp.Processors
- {
- public class PdfResponse<TModel> : Response
- {
- public PdfResponse(TModel model)
- {
- this.Contents = GetPdfContents(model);
- this.ContentType = "application/pdf";
- this.StatusCode = HttpStatusCode.OK;
- }
- protected virtual Action<Stream> GetPdfContents(TModel model)
- {
- return stream =>
- {
- var oDoc = new Document(PageSize.A4);
- PdfWriter.GetInstance(oDoc, stream);
- oDoc.Open();
- foreach (var prop in model.GetType().GetProperties())
- {
- oDoc.Add(new Paragraph(prop.Name + ":" + prop.GetValue(model).ToString()));
- }
- oDoc.Close();
- };
- }
- }
- public class PdfResponse : PdfResponse<object>
- {
- public PdfResponse(object model)
- : base(model)
- {
- }
- }
- }
This will work for other objets than TreeModel too, it's far from complete though.
And now my servicecall with easyhttp will create a file for me and that looks like this.
Conclusion
Again that was easy. But if anyone can tell me how to get the content negotiation to work from within a html page I would be even more happy.






LTD Social Sitings
Note: Watch for social icons on posts by your favorite authors to follow their postings on these and other social sites.