3

I am trying to return a pdf file from my REST API and have added a ReportController to my collection of controllers as follows.

public class ReportController : ApiController
{
    public HttpResponseMessage Get(int id)
    {
        var result = new HttpResponseMessage(HttpStatusCode.OK);
        string fileName = id.ToString();

        MemoryStream memoryStream = GetStreamFromBlob(fileName);
        result.Content = new ByteArrayContent(memoryStream.ToArray());
        result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");

        return result;
    }
}

The other controllers all work fine, however this is the first that has been set to return a HttpResponseMessage rather than a serializable object or collection of objects.

However I am having difficulty consuming this from the client end. Have tried a number of versions of code to do this however the controller code never gets hit, and there seem to be few complete examples of a successful way to call this. The following is my current version:-

public async Task<string> GetPdfFile(int id)
{
    string fileName = string.Format("C:\\Code\\PDF_Client\\{0}.pdf", id);

    using (HttpClient proxy = new HttpClient())
    {
        string url = string.Format("http://localhost:10056/api/report/{0}", id);
        HttpResponseMessage reportResponse = await proxy.GetAsync(url);  //****
        byte[] b = await reportResponse.Content.ReadAsByteArrayAsync();
        System.IO.File.WriteAllBytes(fileName, b);
    }
    return fileName;
}

However the line **** fails with the message No connection could be made because the target machine actively refused it 127.0.0.1:10056.

As I say, other controllers at http://localhost:10056/api/ work fine.

Is this the correct way to GET a file from a WEBAPI server method?

Do you have any advice as to other areas of the code that could be better such as use of await/async, or better ways for the controller to return a file?

PaulR
  • 861
  • 1
  • 8
  • 11
  • is your goal to have the client Open\Save the file from the browser? Have you seen [Darin's Post](http://stackoverflow.com/a/5830215/2779990)? – Stinky Towel Nov 15 '13 at 02:46
  • The error you are seeing is related to general connectivity issues, but i see that you are also saying that it works fine with other controllers...check the following which describes about the error: http://stackoverflow.com/questions/2972600/no-connection-could-be-made-because-the-target-machine-actively-refused-it ....your code looks fine to me, but yeah it can improved for performance though... – Kiran Nov 15 '13 at 04:18
  • @StinkyTowel - no I want to retrieve the file in my client code and save it to disk and view it as well. Thanks for the link, but my first problem is the "target machine actively refused" error. – PaulR Nov 15 '13 at 05:19
  • @KiranChalla - thanks for the link, however as you have noted my client app has just been & accessing other controllers prior to that so I can't help but think this controller has a bug somewhere. Maybe incorrect case or maybe the word "report" is not allowed in this context? As I say, it is the first controller where I have returned an HttpResponseMessage so that is my first thought. Maybe an attribute on the method is needed? I tried a Get() method that just returns a byte[] rather than an HttpResponseMessage but couldn't get that to work either sadly, should that work? – PaulR Nov 15 '13 at 05:29

1 Answers1

5

I came across this same issue, wanting to write a PDF to the output from an ApiController action.

This link helped me out: http://www.asp.net/web-api/overview/formats-and-model-binding/media-formatters

I wrote my own MediaTypeFormatter for writing PDFs from byte arrays.

public class PdfFormatter : MediaTypeFormatter
{
    #region Constants and Fields

    private const int ChunkSizeMax = 1024 * 10;

    #endregion

    #region Constructors and Destructors

    public PdfFormatter()
    {
        SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/pdf"));
    }

    #endregion

    #region Public Methods

    public override bool CanReadType(Type type)
    {
        return false; // can't read any types
    }

    public override bool CanWriteType(Type type)
    {
        return type == typeof(byte[]);
    }

    public override Task WriteToStreamAsync(
        Type type, object value, Stream writeStream, HttpContent content, TransportContext transportContext)
    {
        Task t = new Task(() => WritePdfBytes(value, writeStream));
        t.Start();
        return t;
    }

    #endregion

    #region Methods

    private static void WritePdfBytes(object value, Stream writeStream)
    {
        byte[] buffer = value as byte[];
        int offset = 0;

        while (offset < buffer.Length)
        {
            int chunkSize = Math.Min(buffer.Length - offset, ChunkSizeMax);
            writeStream.Write(buffer, offset, chunkSize);
            offset += chunkSize;
        }
    }

    #endregion
}

Then, I registered this formatter in the Global.asax like this:

private void SetupFormatters()
{
    GlobalConfiguration.Configuration.Formatters.Add(new PdfFormatter());
}

The relevant part of my ApiController Get method looks like this:

public HttpResponseMessage Get(string url, string title)
{
    byte[] pdfBytes;

    /* generate the pdf into pdfBytes */

    string cleanTitle = new Regex(@"[^\w\d_-]+").Replace(title, "_");
    string contentDisposition = string.Concat("attachment; filename=", cleanTitle, ".pdf");
    HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.OK, pdfBytes, MediaTypeHeaderValue.Parse("application/pdf"));
    response.Content.Headers.ContentDisposition = ContentDispositionHeaderValue.Parse(contentDisposition);

    return response;
}
GrimFere
  • 141
  • 1
  • 3