5

I've managed to download a file from server side:

[EnableCors("AllowAll")]
[HttpGet("{id}", Name = "Get")]
public IActionResult Get(int id)
{
    var fileName = "Far30b4949.x86.20170503.zip"; //*** Creation file name
    var filepath = _hostingEnvironment.WebRootPath;
    byte[] fileBytes = System.IO.File.ReadAllBytes(_hostingEnvironment.WebRootPath + 
                          @"\" + fileName);
    return File(fileBytes, "application/zip", fileName); //*** Sending file name
}

and code of client side:

public downloadFile() {       
    let projectAUrl = 'http://localhost:49591/api/file/5';
    return this.http.get(projectAUrl, {responseType: ResponseContentType.Blob})
        .map((response) => {
            return new Blob([response.blob()], {type:'application/zip'})
        })
        .subscribe((res)=> {
            saveAs(res, "Name does not come here")//there is no file name, 
            //but there is a file type("application/zip")
        });
}

CORS settings:

public void ConfigureServices(IServiceCollection services)
{
    // Add framework services.
    services.AddMvc();
    //Add CORS support to the service
    services.AddCors(options=> options.AddPolicy("AllowAll", p => 
            p.AllowAnyOrigin()
             .AllowAnyHeader()
             .AllowAnyMethod()
             .AllowCredentials()));
}

What I have at client side: enter image description here

However, there is no file name at client side when a file is downloaded from server side. How can I get file name?

Update:

I've removed a call to map():

public downloadFile() {       
    let projectAUrl = 'http://localhost:49591/api/file/5';
    return this.http.get(projectAUrl, {responseType: ResponseContentType.Blob})            
        .subscribe((res)=> {
            saveAs(res, "Name does not come here")//there is no file name, 
            //but there is a file type("application/zip")
        });
}

however, there is no file name:

enter image description here

Update 2:

If I use the following policy for CORS:

services.AddCors(options => options.AddPolicy("ExposeResponseHeaders", 
    p =>
    { 
        p.WithOrigins("http://localhost:49591")
         .WithExposedHeaders("Content-Disposition");
    }));

then I get the following error:

XMLHttpRequest cannot load http://localhost:49591/api/file/5. No 'Access-
Control-Allow-Origin' header is present on the requested resource. Origin 
'http://localhost:3000' is therefore not allowed access. The response had 
HTTP status code 500.
sideshowbarker
  • 81,827
  • 26
  • 193
  • 197
StepUp
  • 36,391
  • 15
  • 88
  • 148

2 Answers2

12

Just remove your call to map and extract both the blob data and file name inside subscribe lambda function:

public downloadFile() {       
    let projectAUrl = 'http://localhost:49591/api/file/5';
    return this.http.get(projectAUrl, {responseType: ResponseContentType.Blob})
        .subscribe((response)=> {
            var blob = new Blob([response.blob()], {type:'application/zip'});
            var header = response.headers.get('Content-Disposition');
            var filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
            var matches = filenameRegex.exec(header);
            if (matches != null && matches[1]) { 
                fileName = matches[1].replace(/['"]/g, '');
            }
            saveAs(blob, fileName);
        });
}

Content-Disposition header parsing taken from: How to get file name from content-disposition

Regarding your CORS configuration

You may try with the following policy (add any other header you may find is not read by the browser):

services.AddCors(options => options.AddPolicy("ExposeResponseHeaders", 
    p =>
    { 
        p.WithOrigins("http://localhost:3000") // single origin THIS MUST BE THE SAME OF YOUR ANGULAR APPLICATION (not your ASP.NET Core app address)
         .AllowAnyMethod() // any method
         .AllowAnyHeader() // any header is *allowed*
         .AllowCredentials() // credentials allowed
         .WithExposedHeaders("Content-Disposition"); // content-disposition is *exposed* (and allowed because of AllowAnyHeader)
    }));
Federico Dipuma
  • 17,655
  • 4
  • 39
  • 56
  • Please, see my update section. I've tried, however, there is no `Content-Disposition` here. I've removed a call to `map`. – StepUp May 21 '17 at 16:28
  • Are you accessing to your ASP.NET Core app from another domain? If yes, your headers must be exposed using CORS (note: in Chrome even different ports of localhost are considered different domains). [Here a guide on how to enable CORS in MVC 6](https://learn.microsoft.com/en-us/aspnet/core/security/cors#set-the-allowed-request-headers). – Federico Dipuma May 21 '17 at 16:34
  • yeap, I am accessing from another domain and I've allowed all settings for CORS. Please, see my settings for CORS. – StepUp May 21 '17 at 17:36
  • Does the response inside Chrome inspector show the `Content-Disposition` header? This seems to me an issue of a combination of Angular2+ and/or CORS. You should also try with different browser to see if the `response.headers` variable changes. – Federico Dipuma May 21 '17 at 17:44
  • Do you mean `Chrome Inspector` as a `Developer Tools`? No, it is strange, but there is no other headers than I have shown in my question. – StepUp May 21 '17 at 18:19
  • Yes. Another thing you may try is to add only `content-dispositon` to your CORS configuration (and not `AllowAllHeaders`) – Federico Dipuma May 21 '17 at 18:21
  • I've tried this approach, and I've got the error. Please, see `Update 2`. – StepUp May 21 '17 at 18:27
  • WithExposedHeaders("Content-Disposition") helps with Angular+Chrome. – Edgars Pivovarenoks Jul 08 '20 at 18:10
6

Try to add "Access-Control-Expose-Headers" explicitly in Controller before return result.
For example:

[HttpPost("export/excel")]
public async Task<IActionResult> ExportToExcel([FromBody] ExportReportRequest request)
{
    ...
    (byte[] fileContents, string fileName) = await this.salaryService.ExportReportToExcelAsync(request, userId).ConfigureAwait(false);
    this.Response.Headers.Add("Access-Control-Expose-Headers", "Content-Disposition");
    return this.File(fileContents, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", fileName);
}
Ivan Vakhrushev
  • 340
  • 2
  • 9