Login or Sign Up to become a member!
LessThanDot Sit Logo

LessThanDot

Web Developer

Less Than Dot is a community of passionate IT professionals and enthusiasts dedicated to sharing technical knowledge, experience, and assistance. Inside you will find reference materials, interesting technical discussions, and expert tips and commentary. Once you register for an account you will have immediate access to the forums and all past articles and commentaries.

LTD Social Sitings

Lessthandot twitter Lessthandot Linkedin Lessthandot friendfeed Lessthandot facebook Lessthandot rss

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

Your profile

    Search

    XML Feeds

    Google Ads

    « Making an interface for plugwise with nancyNancy and C# uploading and showing an image »
    comments

    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.

    1. using System;
    2. using System.IO;
    3. using Nancy;
    4. using NancyDemo.Csharp.Model;
    5. using iTextSharp.text;
    6. using iTextSharp.text.pdf;
    7.  
    8. namespace NancyDemo.Csharp.Processors
    9. {
    10.     public class TreeModelPdfResponse : Response
    11.     {
    12.         public TreeModelPdfResponse(TreeModel model)
    13.         {
    14.             this.Contents = GetPdfContents(model);
    15.             this.ContentType = "application/pdf";
    16.             this.StatusCode = HttpStatusCode.OK;
    17.         }
    18.  
    19.         private Action<Stream> GetPdfContents(TreeModel model)
    20.         {
    21.             return stream =>
    22.                 {
    23.                     var oDoc = new Document(PageSize.A4);
    24.                     PdfWriter.GetInstance(oDoc, stream);
    25.                     oDoc.Open();
    26.                     oDoc.Add(new Paragraph("This a Tree"));
    27.                     oDoc.Add(new Paragraph("Id: " + model.Id));
    28.                     oDoc.Add(new Paragraph("Genus: " + model.Genus));
    29.                     oDoc.Close();
    30.                 };
    31.         }
    32.     }
    33. }

    And in my Treesmodule I will add this Get-method.

    1. Get["/trees/pdf/{Id}"] = parameters =>
    2.             {
    3.                 int result;
    4.                 var isInteger = int.TryParse(parameters.id, out result);
    5.                 var tree = treeService.FindById(result);
    6.                 if (isInteger && tree != null)
    7.                 {
    8.                     return new TreeModelPdfResponse(tree);
    9.                 }
    10.                 else
    11.                 {
    12.                     return HttpStatusCode.NotFound;
    13.                 }
    14.             };

    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.

    1. <div id="pdf">
    2.     <iframe width="400" height="500" src="/trees/pdf/@Model.Id" id="pdf_content" />
    3. </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).

    1. using System;
    2. using System.IO;
    3. using EasyHttp.Http;
    4.  
    5. namespace NancyDemo.Csharp.Easyhttp
    6. {
    7.     class Program
    8.     {
    9.         private static void Main(string[] args)
    10.         {
    11.             var http = new HttpClient();
    12.             http.Request.Accept = "application/pdf";
    13.             http.Request.ParametersAsSegments = true;
    14.             http.GetAsFile("http://localhost:65367/trees/1", "e:\\temp\\Test.pdf");
    15.             Console.ReadLine();
    16.         }
    17.     }
    18. }

    My Get in my NancyModule uses the Negotiate.

    Like this.

    1. Get["/trees/{Id}"] = parameters =>
    2.                 {
    3.                     int result;
    4.                     var isInteger = int.TryParse(parameters.id, out result);
    5.                     var tree = treeService.FindById(result);
    6.                     if(isInteger && tree != null)
    7.                     {
    8.                         return Negotiate.WithModel(tree);
    9.                     }
    10.                     else
    11.                     {
    12.                         return HttpStatusCode.NotFound;
    13.                     }
    14.                 };

    I then just need to add a Processor class.

    1. using System;
    2. using System.Collections.Generic;
    3. using Nancy;
    4. using Nancy.Responses.Negotiation;
    5.  
    6. namespace NancyDemo.Csharp.Processors
    7. {
    8.     public class PdfProcessor : IResponseProcessor
    9.     {
    10.         private static readonly IEnumerable<Tuple<string, MediaRange>> extensionMappings =
    11.             new[] { new Tuple<string, MediaRange>("pdf", MediaRange.FromString("application/pdf")) };
    12.  
    13.         public ProcessorMatch CanProcess(MediaRange requestedMediaRange, dynamic model, NancyContext context)
    14.         {
    15.             if (IsExactPdfContentType(requestedMediaRange))
    16.             {
    17.                 return new ProcessorMatch
    18.                     {
    19.                         ModelResult = MatchResult.DontCare,
    20.                         RequestedContentTypeResult = MatchResult.ExactMatch
    21.                     };
    22.             }
    23.  
    24.             return new ProcessorMatch
    25.             {
    26.                 ModelResult = MatchResult.DontCare,
    27.                 RequestedContentTypeResult = MatchResult.NoMatch
    28.             };
    29.         }
    30.  
    31.         private static bool IsExactPdfContentType(MediaRange requestedContentType)
    32.         {
    33.             if (requestedContentType.Type.IsWildcard && requestedContentType.Subtype.IsWildcard)
    34.             {
    35.                 return true;
    36.             }
    37.  
    38.             return requestedContentType.Matches("application/pdf");
    39.         }
    40.  
    41.         public Response Process(MediaRange requestedMediaRange, dynamic model, NancyContext context)
    42.         {
    43.             return new PdfResponse(model);
    44.         }
    45.  
    46.         public IEnumerable<Tuple<string, MediaRange>> ExtensionMappings
    47.         {
    48.             get { return extensionMappings; }
    49.         }
    50.     }
    51. }

    And a response class.

    1. using System;
    2. using System.IO;
    3. using Nancy;
    4. using NancyDemo.Csharp.Model;
    5. using iTextSharp.text;
    6. using iTextSharp.text.pdf;
    7.  
    8. namespace NancyDemo.Csharp.Processors
    9. {
    10.     public class PdfResponse<TModel> : Response
    11.     {
    12.         public PdfResponse(TModel model)
    13.         {
    14.             this.Contents = GetPdfContents(model);
    15.             this.ContentType = "application/pdf";
    16.             this.StatusCode = HttpStatusCode.OK;
    17.         }
    18.  
    19.         protected virtual Action<Stream> GetPdfContents(TModel model)
    20.         {
    21.             return stream =>
    22.                 {
    23.                     var oDoc = new Document(PageSize.A4);
    24.                     PdfWriter.GetInstance(oDoc, stream);
    25.                     oDoc.Open();
    26.                     foreach (var prop in model.GetType().GetProperties())
    27.                     {
    28.                         oDoc.Add(new Paragraph(prop.Name + ":" + prop.GetValue(model).ToString()));    
    29.                     }
    30.                     oDoc.Close();
    31.                 };
    32.         }
    33.     }
    34.  
    35.     public class PdfResponse : PdfResponse<object>
    36.     {
    37.         public PdfResponse(object model)
    38.             : base(model)
    39.         {
    40.         }
    41.     }
    42. }

    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.

    About the Author

    User bio imageChris is awesome.
    Social SitingsTwitterHomePageLTD RSS Feed
    c#, nancy
    InstapaperVote on HN

    4 comments

    Comment from: Steven Robbins [Visitor] · http://www.grumpydev.com
    Steven Robbins You CanProcess method is wrong - not caring about the model is fine, but you need to be checking the media range that's passed in to see if it matches application/pdf. You current processor says "I am an exact match for every content type in existance" :)
    12/30/12 @ 11:12
    Comment from: Phillip Haydon [Visitor] · http://www.philliphaydon.com
    Phillip Haydon Freaking out that you did a C# post!

    I would change:

    var isInteger = int.TryParse(parameters.id, out result);
    if(isInteger && tree != null)

    to

    var id = parameters.id.Default(0);
    if(id > 0 && tree != null)

    It's much nicer to read :)

    .Default is method defined on DynamicDictionary and allows you to specify a default. If you're unsure if the value would be parseable you can do.

    var id = parameters.id.TryParse(0);

    Which will give you 0 if it can't parse the value.
    12/30/12 @ 11:19
    Comment from: Christiaan Baes (chrissie1) [Member]
    Christiaan Baes (chrissie1) @Steven I fixed it.

    @Phillip I'll try to remember ;-).
    12/30/12 @ 11:41
    Comment from: peter [Visitor]
    peter Kudos on using sumatra, not that daily-update-nightmare that is adobe reader.
    Also note that itextsharp has its own licensing quirks.
    12/31/12 @ 13:42

    Leave a comment


    Your email address will not be revealed on this site.

    To mislead the spambots.

    Your URL will be displayed.
    (Line breaks become <br />)
    (Name, email & website)
    (Allow users to contact you through a message form (your email will not be revealed.)