Search code examples
asp.net-mvcazureazure-web-app-servicefilecontentresult

How to expose "Content-Disposition" response header on ASP.NET web API hosted as Azure App Service?


I'm working on a web app which uses a .NET web API to download a file. The request to the API is handled by client code generated using NSwag. This all works great locally, but when using the API that's hosted on Azure, the client code is unable to get the file name.

The client relies on the "Content-Disposition" response header to set the file name for the download. That header is automatically added by the API controller, which returns a FileContentResult. However, because "Content-Disposition" isn't a CORS-safelisted response header, the client isn't able to access it unless "Access-Control-Expose-Headers" is also provided to allow it.

Specifying this in the CORS policy in the API app's startup works great locally, but it seems the WithExposedHeaders setting isn't respected when it's hosted on Azure.

Looking in the browser dev tools the "Content-Disposition" header is there with the expected file name, but "Access-Control-Expose-Headers" is not. The same request has both headers as expected when using the local API, though.

What is the proper way to expose the "Content-Disposition" header to my client from the Azure App Service?

Details

Here's a simplified version of the relevant startup code:

public void ConfigureServices(IServiceCollection services)
{
    services.AddCors(options =>
    {
        options.AddDefaultPolicy(builder =>
        {
            builder.AllowAnyHeader();
            builder.AllowAnyMethod();
            builder.WithExposedHeaders("Content-Disposition");
        });
    });
}
public void Configure(IApplicationBuilder app)
{
    app.UseRouting();

    app.UseCors();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
    });
}

And a simplified version of the API controller:

[HttpGet]
[Route("getfile")]
public async Task<ActionResult> GetFile(long id)
{
    var file =  await _fileService.GetFile(id);

    return File(file.Content, file.MimeType, file.Name);
}

I thought the issue might be that the CORS settings defined on Azure override the app-defined policy, but clearing out all of the Azure CORS settings had no effect.

I also tried the setting suggested in this answer with "Content-Disposition" as the value, but it didn't appear to do anything. Not really sure that's even a valid setting as I can't find any other references to it besides that SO answer.

It seems like setting the exposed headers on Azure, as shown here, should be the answer but my App Service CORS config doesn't have those options. Guessing that may only apply to Azure Container Apps.


Solution

  • Additional research eventually led us to a solution. Short answer: the proper way to expose the "Content-Disposition" header from an API hosted as an Azure App Service depends on how the app is being accessed through Azure.

    • If using an App Service for hosting and nothing else, the proper way seems to be to define a CORS policy in the application code (e.g., Startup.cs) and disable CORS functionality in the App Service. App Services don't appear to offer the ability to configure this for some reason, and enabling CORS there overrides anything defined in the application code.
    • If using Azure API Management, as is the case for us, CORS must be configured as a policy in API Management (see docs here and here). This seems to override CORS policies set elsewhere, which is why we weren't having any luck trying to set it in the application code initially.
    • If the app is accessed through additional layers (like Azure Application Gateway) it's possible headers need to be configured there instead.