1

I have an Angular 4 application which consumes an Asp.Net Web Api, and I want the Api to return a binary file. The Api route seems to be working correctly - I tested it using a rest console and the response is as expected. However, when trying to use the same route in the Angular app, the request sends but returns an error. I can see with the C# debugger that the request is executing completely and doesn't fail on the server. Here's the error in the JS console:enter image description here

This error occurs on all browsers tested (Chrome, Firefox, IE, Edge, Safari).

Here's the server side code:

[Route("api/getfile")]
public IHttpActionResult GetFile()
{
    byte[] file = <file generator code>;
    System.Net.Http.HttpResponseMessage responseMessage = new System.Net.Http.HttpResponseMessage
    {
        Content = new System.Net.Http.StreamContent(new System.IO.MemoryStream(file))
    };
    responseMessage.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/octet-stream");
    responseMessage.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment");
    responseMessage.Content.Headers.ContentDisposition.FileName = "file.pdf";
    return this.ResponseMessage(responseMessage);
}

And here's the Angular code:

let headers = new Headers({
  "Accept": "application/octet-stream",
  "X-Requested-With": "XMLHttpRequest",
  "Authorization": `Bearer ${token}`
});
let opts = new RequestOptions({headers = headers});
opts.responseType = ResponseContentType.Blob;
// Uses old @angular/http, not HttpClient
this.http
  .get(`${apiUrl}/getfile`, opts)
  .map(res => res.blob())
  .catch(err => handleError(err));

EDIT: I tried using a plain XMLHttpRequest instead of Angular's Http service and it works. What do I need to do to get this to work with Angular?

EDIT 2: It works if I fetch an actual file on the file system that's accessible using the same host that the Angular app is running on. The Angular app is on localhost:8080, while the api is on a different port. If I expose a file on localhost:8080 (e.g., in the build folder) than I can fetch that file. This makes me wonder if it's a security issue, or maybe has to do with the headers or the way Web Api returns the binary data.

4
  • Have you tried ResponseContentType.ArrayBuffer ? Commented Jul 19, 2018 at 21:58
  • @RandomUs1r Yes, same error Commented Jul 19, 2018 at 21:59
  • try to rewrite let opts = new RequestOptions({headers = headers}); to let opts = new RequestOptions({headers, responseType: 'blob'}); Commented Jul 24, 2018 at 20:15
  • 1. It looks like your request might be reporting progress and you are treating the progress events as the content. The observable looks like it's returning a progress event that you are then calling blob() on in your map function. Set reportProgress to false. 2. If you are trying to report progress, you need to return content-length (that's why lengthComputable is false) and keep an eye on those progress events. Look at this answer for details. Commented Jul 26, 2018 at 1:56

3 Answers 3

0
+100

On your Api that will return your PDF

  FileContentResult result;
  if(!string.IsNullOrEmpty(fileName))
  {
     string absoluteFileName = Path.Combine(pathToFile, fileName);
     byte[] fileContents = System.IO.File.ReadAllBytes(absoluteFileName);
     result = new FileContentResult(fileContents, "application/pdf");
  }

And then on Angular:

 downloadFile(api: string) {
    window.open(this.endPoint + api);
  }

Try the old Way:

            FileInfo  fileInfo = New FileInfo(filePath)         
        Response.Clear()
        Response.ClearHeaders()
        Response.ClearContent()
        Response.AddHeader("Content-Disposition", "attachment;filename=" + fileInfo.Name)
        Response.AddHeader("Content-Type",  "application/pdf")
        Response.ContentType = "application/pdf"
        Response.AddHeader("Content-Length", fileInfo.Length.ToString())
        Response.TransmitFile(fileInfo.FullName)
        Response.Flush()
        Response.End()
Sign up to request clarification or add additional context in comments.

7 Comments

I'm not using MVC. Can I use FileContentResult?
Are you on WebAPI 2 or 1 ?. I think from WeBAPI 2 is available IHttpActionResult
I'm using WebApi 2, with an ASP.net Ajax web application, .NET 4.5
The last piece of code should produce your pdf the right way
All depends on the libraries you have available on your project
|
0

This is for an image that I was keeping in a blob column in a db, but process should be similar. I ended up doing something like this back in Angular 4 (although this was 4.3+ which means HttpClient, not Http) to handle downloading files on clicking a button:

public downloadImage(id: number, imageName: string, imageType: string) {

    this.http.get(urlToApiHere, { responseType: 'blob' }).subscribe((image: Blob) => {
      if (isPlatformBrowser(this.platformId)) {
        let a = window.document.createElement("a");
        document.body.appendChild(a);
        let blobUrl = window.URL.createObjectURL(image);
        a.href = blobUrl;
        a.download = imageName;
        a.click();
        window.URL.revokeObjectURL(blobUrl);
        document.body.removeChild(a);
      }
    })
  }

This API is .Net Core, but should be similar in .Net MVC, I believe:

[HttpGet]
public FileResult DisplayLineItemImage(int lineItemID)
{
  using (var db = new Context())
  {
    var image = //retrieve blob binary, type, and filename here
    if (image.Image == null)
    {
      //throw error
    }
    return File(image.Image, image.Type, image.Name);
  }
}

Comments

0

The second answer by Crying Freeman, using Response directly, does work, but it bypasses Owin's processing and would mean having to manually implement things like CORS or anything else normally handled using CORS.

I found another solution, to use a custom formatter to allow returning a byte array from the controller method. This is also nicer because I don't need to set any headers manually, not even Content-Type.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.