Pugnatore
Pugnatore

Reputation: 405

Im not able to access Response.Headers("Content-Disposition") on client even after enable CORS

Im trying to retrieve the filename of an excel file that is generated in the backend (Asp.net Core 2.2) and as you can see in the c# code, the filename is retrieved in the header of the response but from the client I cannot access to the header 'Content Disposition'

As you can see on the print bellow, in spite of the Content disposition is not present in the headers, it is present in XHR response headers

log of response.headers enter image description here

XHR t

I already enable de CORS policy in the backend as you can see here:

Startup.cs (ConfigureServices method)

services
    .AddCors(options =>
    {
        options.AddPolicy(CorsAllowAllOrigins,
            builder => builder.WithOrigins("*").WithHeaders("*").WithMethods("*"));
    })
    .AddMvc(options =>
    {
        options.Filters.Add(new AuthorizeFilter(authorizationPolicy));
    })
    .SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

services
    .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        var key = Encoding.ASCII.GetBytes(jwtSecurityOptions.SecretKey);

        options.Audience = jwtSecurityOptions.Audience;
        options.RequireHttpsMetadata = false;
        options.SaveToken = true;
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuerSigningKey = true,
            IssuerSigningKey = new SymmetricSecurityKey(key),
            ValidateIssuer = false,
            ValidateAudience = false
        };
    })

Startup.cs(Configure method)

app.UseAuthentication();
app.UseCors(CorsAllowAllOrigins);
app.UseMvc();

Controller

[HttpGet("{reportResultId}/exportExcelFile")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesErrorResponseType(typeof(void))]
[Authorize(Policy = nameof(RoleType.N1))]
public ActionResult ExportExcelFile(int reportResultId, [FromQuery]bool matchedRows, [FromQuery]ExportExcelType exportExcelType)
{
    var authenticatedUser = Request.GetAuthenticatedUser();
    var result = _reconciliationResultService.GetDataForExportExcelFile(reportResultId, matchedRows,authenticatedUser.UserName ,exportExcelType, authenticatedUser.UserName, authenticatedUser.Id);

    if (result != null)
    {
        MemoryStream memoryStream = new MemoryStream(result.WorkbookContent);
        var contentType = new Microsoft.Net.Http.Headers.MediaTypeHeaderValue("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        var fileStreamResult = new FileStreamResult(memoryStream, contentType)
        {
            FileDownloadName = result.FileName
        };

        fileStreamResult.FileDownloadName=result.FileName;

        return fileStreamResult;
    }
    else
    {
        return null;
    }
}

--------------Client ---------------------

Container.ts

exportExcelFile(matchedRows: string) {
    this._reportService.exportExcelFile(matchedRows, this.reportInfo.id, this.exportExcelType).subscribe((response) => {
        var filename = response.headers.get("Content-Disposition").split('=')[1]; //An error is thrown in this line because response.headers.get("Content-Disposition") is always null
        filename = filename.replace(/"/g, "")
        const blob = new Blob([response.body],
            { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });

        const file = new File([blob], filename,
            { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });

        this.exportingExcelFile = false;
        this.ready = true;
        saveAs(file);
    });
}

ReportService.ts

exportExcelFile(matchedRows: string, reportInfoId: number, exportExcelType:ExportExcelType): Observable<any> {
    const url = `${environment.apiUrls.v1}/reconciliationResult/${reportInfoId}/exportExcelFile?matchedRows=${matchedRows}&exportExcelType=${exportExcelType}`;
    return this.http.get(url, { observe: 'response', responseType: 'blob' });
}

app.module.ts

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';

import { AppRoutingModule } from './app.routing.module';
import { ReportModule } from './report/report.module';

import { AppComponent } from './app.component';
import { TopNavComponent } from './layout/topnav.component';
import { SideBarComponent } from './layout/sidebar.component';
import { DatePipe } from '@angular/common';
import { DynamicDialogModule } from 'primeng/dynamicdialog';
import { SharedModule } from './core/shared.module';
import { JwtInterceptor } from './core/_helpers/jwt.interceptor';
import { ErrorInterceptor } from './core/_helpers/error.interceptor';
import { LoginRoutingModule } from './login/login-routing.module';
import { LogingModule } from './login/login.module';

@NgModule({
  imports: [
    BrowserModule,
    HttpClientModule,
    SharedModule,
    ReportModule,
    AppRoutingModule,
    DynamicDialogModule,
    LogingModule,
    LoginRoutingModule
  ],
  declarations: [
    AppComponent,
    TopNavComponent,
    SideBarComponent
  ],
  providers: [
    DatePipe,
    { provide: HTTP_INTERCEPTORS, useClass: JwtInterceptor, multi: true },
     { provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true }
  ],
  bootstrap: [AppComponent]
})

export class AppModule { }
```

Note: I'm implementing JWT. I don't know if it can have any impact on the headers.

Upvotes: 7

Views: 5629

Answers (2)

crocx49
crocx49

Reputation: 41

You can also use something like this before returning your response:

HttpContext.Response.Headers.Add("Access-Control-Expose-Headers", "Content-Disposition");
HttpContext.Response.Headers.Add("Content-Disposition", YOUR_VALUE);

This way, each endpoint can have a custom header exposed, without updating Startup.cs.

Upvotes: 2

Kirk Larkin
Kirk Larkin

Reputation: 93133

Your server needs to expose the header as part of its CORS configuration, using WithExposedHeaders. Here's an example:

services
    .AddCors(options =>
    {
        options.AddPolicy(CorsAllowAllOrigins, builder => 
            builder.WithOrigins("*")
                   .WithHeaders("*")
                   .WithMethods("*")
                   .WithExposedHeaders("Content-Disposition"));
    });

This sets the Access-Control-Expose-Headers header.

Upvotes: 14

Related Questions