ASP.Net Core Content-Disposition attachment/inline
Asked Answered
H

14

90

I am returning a file from a WebAPI controller. The Content-Disposition header value is automatically set to "attachment". For example:

Disposition: attachment; filename="30956.pdf"; filename*=UTF-8''30956.pdf

When it is set to attachment the browser will ask to save file instead of opening it. I would like it to open it.

How can I set it to "inline" instead of "attachment"?

I am sending the file using this method:

public IActionResult GetDocument(int id)
{
    var filename = $"folder/{id}.pdf";
    var fileContentResult = new FileContentResult(File.ReadAllBytes(filename), "application/pdf")
    {
        FileDownloadName = $"{id}.pdf"
    };
    // I need to delete file after me
    System.IO.File.Delete(filename);

    return fileContentResult;
}
Hobbism answered 11/8, 2016 at 13:28 Comment(0)
U
103

With version 2.0.0 of AspNetCore and AspNetCore.Mvc, I found none of the previous answers to be acceptable. For me, simply ommitting the filename argument to File was enough to trigger an inline content disposition.

return File(fileStream, contentType, fileName); // attachment
return File(fileStream, contentType);           // inline

Update

In .NET 6, set the Content-Disposition header to inline or attachment by adding it to the response header:

// inline
Response.Headers.Add("Content-Disposition", "inline");
return File(fileStream, contentType);

// attachment
Response.Headers.Add("Content-Disposition", "attachment;filename=some.txt");
return File(fileStream, contentType);
Ulberto answered 6/7, 2018 at 19:35 Comment(6)
note that if the user save the file in his browser, the suggested file name will be the id specified in the url instead of a potentially better file name.Inquiring
In my .NET 6 webapi controller, return File(fileStream, contentType); does not trigger an inline content disposition. It doesn't produce a Content-Dispositon response header at all. Maybe this accepted answer is outdated?Replenish
Yes it seems..i have added corepolicy.https://mcmap.net/q/42087/-angular-13-download-file-from-api-in-entire-app-net-6Bialy
Dear Lord. That took me a few hours!Freida
@Replenish I would update this answer to include .NET 6 but I'm afraid I don't have any experience with it. You should post a .NET 6 answer here :)Ulberto
Right on. Thanks for your edit.Ulberto
V
105

The best way I have found is to add the content-disposition headers manually.

private IActionResult GetFile(int id)
{
       var file = $"folder/{id}.pdf";

       // Response...
       System.Net.Mime.ContentDisposition cd = new System.Net.Mime.ContentDisposition
       {
              FileName = file,
              Inline = displayInline  // false = prompt the user for downloading;  true = browser to try to show the file inline
       };
       Response.Headers.Add("Content-Disposition", cd.ToString());
       Response.Headers.Add("X-Content-Type-Options", "nosniff");

       return File(System.IO.File.ReadAllBytes(file), "application/pdf");
}
Vinasse answered 12/8, 2016 at 4:20 Comment(6)
Quick check for anyone struggling to get this to work: make sure you do not pass the fileDownloadName parameter when constructing your FileStreamResult or it will override your custom 'Content-Disposition' header!Whiny
I personnally got some problem with this when the file name begins with D and is a pdf. I recommand to use aspnet core classes to do it in aspnet core: either return a FileContentResult or set the content-disposition with ContentDispositionHeaderValueInquiring
Don't use ContentDisposition.ToString()!!! If a single special character is include all will be Base64 encoded and splitted in new lines for each 42 character chunk, e.g. "1234567890123456789012345789012345678ä.pdf""=?utf-8?B?MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTc4OTAxMjM0NTY3OMOkLnBk?=\r\n =?utf-8?B?Zg==?=" And adding new lines to Response.HeadersSystem.InvalidOperationException: 'Invalid control character in header: 0x0D'Pirzada
Note, that you can specify headers in a strongly-typed manner: Response.GetTypedHeaders().LastModified = cacheEntryLastModifyTime; Credits: learn.microsoft.com/en-us/aspnet/core/performance/caching/…Sibie
Do NOT use this method. As Marcel mentions above, System.Net.Mime.ContentDisposition can produce an invalid HTTP header value. Use Microsoft.Net.Http.Headers.ContentDispositionHeaderValue as shown in this answer. ContentDispositionHeaderValue is part of ASP and is intended for HTTP whereas ContentDisposition is part of the dotnet runtime and applies to more than just HTTP.Prostomium
what is the purpose of X-Content-Type-Options header?Amblygonite
U
103

With version 2.0.0 of AspNetCore and AspNetCore.Mvc, I found none of the previous answers to be acceptable. For me, simply ommitting the filename argument to File was enough to trigger an inline content disposition.

return File(fileStream, contentType, fileName); // attachment
return File(fileStream, contentType);           // inline

Update

In .NET 6, set the Content-Disposition header to inline or attachment by adding it to the response header:

// inline
Response.Headers.Add("Content-Disposition", "inline");
return File(fileStream, contentType);

// attachment
Response.Headers.Add("Content-Disposition", "attachment;filename=some.txt");
return File(fileStream, contentType);
Ulberto answered 6/7, 2018 at 19:35 Comment(6)
note that if the user save the file in his browser, the suggested file name will be the id specified in the url instead of a potentially better file name.Inquiring
In my .NET 6 webapi controller, return File(fileStream, contentType); does not trigger an inline content disposition. It doesn't produce a Content-Dispositon response header at all. Maybe this accepted answer is outdated?Replenish
Yes it seems..i have added corepolicy.https://mcmap.net/q/42087/-angular-13-download-file-from-api-in-entire-app-net-6Bialy
Dear Lord. That took me a few hours!Freida
@Replenish I would update this answer to include .NET 6 but I'm afraid I don't have any experience with it. You should post a .NET 6 answer here :)Ulberto
Right on. Thanks for your edit.Ulberto
S
18

You can override the default FileContentResult class so you can use it in your code with minimal changes:

public class InlineFileContentResult : FileContentResult
{
    public InlineFileContentResult(byte[] fileContents, string contentType)
        : base(fileContents, contentType)
    {
    }

    public override Task ExecuteResultAsync(ActionContext context)
    {
        var contentDispositionHeader = new ContentDispositionHeaderValue("inline");
        contentDispositionHeader.SetHttpFileName(FileDownloadName);
        context.HttpContext.Response.Headers.Add(HeaderNames.ContentDisposition, contentDispositionHeader.ToString());
        FileDownloadName = null;
        return base.ExecuteResultAsync(context);
    }
}

The same can be done for the FileStreamResult:

public class InlineFileStreamResult : FileStreamResult
{
    public InlineFileStreamResult(Stream fileStream, string contentType)
        : base(fileStream, contentType)
    {
    }

    public override Task ExecuteResultAsync(ActionContext context)
    {
        var contentDispositionHeader = new ContentDispositionHeaderValue("inline");
        contentDispositionHeader.SetHttpFileName(FileDownloadName);
        context.HttpContext.Response.Headers.Add(HeaderNames.ContentDisposition, contentDispositionHeader.ToString());
        FileDownloadName = null;
        return base.ExecuteResultAsync(context);
    }
}

Instead of returning a FileContentResult or FileStreamResult, just return InlineFileContentResult or InlineFileStreamResult. F.e.:

public IActionResult GetDocument(int id)
{
    var filename = $"folder/{id}.pdf";
    return new InlineFileContentResult(File.ReadAllBytes(filename), "application/pdf")
    {
        FileDownloadName = $"{id}.pdf"
    };
}

Warning

As pointed out by makman99, do not use the ContentDisposition class for generating the header value as it will insert new-lines in the header-value for longer filenames.

Steadfast answered 26/10, 2019 at 12:33 Comment(1)
if the inline string is set inside the ExecuteResultAsync how can I make the FileStreamResult flexible using also value like attachment?Anthracosis
M
16

Given you don't want to read the file in memory at once in a byte array (using the various File(byte[]...) overloads or using FileContentResult), you can either use the File(Stream, string, string) overload, where the last parameter indicates the name under which the file will be presented for download:

return File(stream, "content/type", "FileDownloadName.ext");

Or you can leverage an existing response type that supports streaming, such as a FileStreamResult, and set the content-disposition yourself. The canonical way to do this, as demonstrated in the FileResultExecutorBase, is to simply set the header yourself on the response, in your action method:

// Set up the content-disposition header with proper encoding of the filename
var contentDisposition = new ContentDispositionHeaderValue("attachment");
contentDisposition.SetHttpFileName("FileDownloadName.ext");
Response.Headers[HeaderNames.ContentDisposition] = contentDisposition.ToString();

// Return the actual filestream
return new FileStreamResult(@"path\to\file", "content/type");
Moneymaking answered 8/11, 2016 at 15:0 Comment(2)
OP said How can I set it to "inline" instead of "attachment"?. Both File() and your more-verbose example will force the mode to attachment, although the latter is easily hackable to inlineMonamonachal
it's not finding SetHttpFileName in my .net 4.7.2 app, so I used UrlEncode. Also, I needed mine inline instead of attachment, it works: var cd = new ContentDispositionHeaderValue("inline"); cd.FileName = HttpUtility.UrlEncode(filename); Response.Headers.Add("Content-Disposition", cd.ToString()); return new FileStreamResult(stream, contentType);Exterior
M
7

As File() would ignore Content-Disposition I used this:

Response.Headers[HeaderNames.ContentDisposition] = new MimeKit.ContentDisposition { FileName = fileName, Disposition = MimeKit.ContentDisposition.Inline }.ToString();
return new FileContentResult(System.IO.File.ReadAllBytes(filePath), "application/pdf");

and it works :-)

Maurer answered 6/10, 2017 at 10:14 Comment(0)
R
7

None of these solutions worked for me. The only thing that worked for me was updating the Cors of the backend:

        services.AddCors(o => o.AddPolicy("MyPolicy", b =>
        {
            b.AllowAnyOrigin()
                   .AllowAnyMethod()
                   .AllowAnyHeader()
                   .WithExposedHeaders("Content-Disposition");
        }));

so the header would be exposed. After this, I didn't need to add any additional header to the response.

And If you don't want to update your Startup.cs you can allow the header manually for that response:

        HttpContext.Response.Headers.Add("Access-Control-Expose-Headers", "Content-Disposition");
        HttpContext.Response.Headers.Add("Content-Disposition", <your_header_value>);
Richerson answered 21/1, 2021 at 21:48 Comment(1)
The first solution is elegant but it did not work with the capital letters. It worked when I used .WithExposedHeaders("content-disposition");. Using Angular/RxJs :)Ellyellyn
C
4

try it with HttpResponseMessage

public IActionResult GetDocument(int id)
{
    var filename = $"folder/{id}.pdf";

    Response.Headers["Content-Disposition"] = $"inline; filename={id}.pdf";
    var fileContentResult = new FileContentResult(System.IO.File.ReadAllBytes(filename), "application/pdf")
    {
        FileDownloadName = $"{id}.pdf"
    };
    // I need to delete file after me
    System.IO.File.Delete(filename);

    return fileContentResult;
}
Carthy answered 11/8, 2016 at 13:58 Comment(4)
I need for ASP.Net Core, afaic this will return a ASP.Net object. Tried it and it returns JSON serialized HttpResponseMessage object.Hobbism
I created the following issue: github.com/aspnet/Mvc/issues/5133 to track this.Fielding
Also you need NOT read the bytes yourself, you could instead do return File(System.IO.File.OpenRead("full-file-path"), contentType: "application/pdf");Fielding
I'm returning this, return File(System.IO.File.ReadAllBytes("C:/Temp/Generic.csv"), "application/octet-stream", "Generic.csv"); but absolutely nothing happens.Castanon
I
3

Based on Ashley Lee's response but using ASP.Net Core stuff which solve problems for some file name patterns. Note that inline is the default content-disposition, so if you don't need to specify the filename (will be suggested if the user hit save on his browser) you can simply omit the content-disposition as suggested by Jonathan Wilson.

private IActionResult GetFile(int id)
{
    var file = $"folder/{id}.pdf";

    // Response...
    var cd = new ContentDispositionHeaderValue("inline");
    cd.SetHttpFileName(file);
    Response.Headers[HeaderNames.ContentDisposition] = cd.ToString();
    Response.Headers.Add("X-Content-Type-Options", "nosniff");

    return File(System.IO.File.ReadAllBytes(file), "application/pdf");
}
Inquiring answered 25/2, 2020 at 19:54 Comment(0)
H
1

For ASP.NET Core, there doesn't seem to be any built-in way to return a file with 'Content-Disposition: inline' and filename. I created the following helper class that works very well. Tested with .NET Core 2.1.

public class InlineFileActionResult : Microsoft.AspNetCore.Mvc.IActionResult
{
    private readonly Stream _stream;
    private readonly string _fileName;
    private readonly string _contentType;
    private readonly int _bufferSize;

    public InlineFileActionResult(Stream stream, string fileName, string contentType, 
        int bufferSize = DefaultBufferSize)
    {
        _stream = stream ?? throw new ArgumentNullException(nameof(stream));
        _fileName = fileName ?? throw new ArgumentNullException(nameof(fileName));
        _contentType = contentType ?? throw new ArgumentNullException(nameof(contentType));
        if (bufferSize <= 0)
            throw new ArgumentOutOfRangeException(nameof(bufferSize), bufferSize,
                "Buffer size must be greater than 0");
        _bufferSize = bufferSize;
    }

    public async Task ExecuteResultAsync(Microsoft.AspNetCore.Mvc.ActionContext context)
    {
        using (_stream)
        {
            var response = context.HttpContext.Response;
            response.Headers[HeaderNames.ContentType] = _contentType;
            response.Headers[HeaderNames.ContentLength] = _stream.Length.ToString();
            response.Headers[HeaderNames.ContentDisposition] =
                new Microsoft.Net.Http.Headers.ContentDispositionHeaderValue(
                    System.Net.Mime.DispositionTypeNames.Inline) {FileName = _fileName}.ToString();
            await _stream.CopyToAsync(response.Body, _bufferSize, context.HttpContext.RequestAborted);
        }
    }

    public const int DefaultBufferSize = 81920;
}

To use, return the class from the controller (whose return method must be IActionResult). An example is shown below:

[HttpGet]
public IActionResult Index()
{
    var filepath = "C:\Path\To\Document.pdf";
    return new InlineFileActionResult(new FileStream(filepath, FileMode.Open), 
        Path.GetFileName(filepath), "application/pdf");
}
Hospitalet answered 20/8, 2020 at 12:26 Comment(2)
The filename needs to be escaped. The best way to do that is to use Microsoft.Net.Http.Headers.ContentDispositionHeaderValue rather than writing the header manually.Prostomium
Concatinating the file like that will certainly break the HTTP response when the fileName contains invalid characters.Steadfast
M
1

This simply works for me in asp.net core 5.0 and hopefully this will work for previous versions too, as I was using same in asp.net 4.8

Response.ContentType = "application/pdf";
Response.Headers.Add("pragma", "no-cache, public");
Response.Headers.Add("cache-control", "private, nocache, must-revalidate, maxage=3600");
Response.Headers.Add("content-disposition", "inline;filename=" + fileName);
return File(bytes, "application/pdf");
Manzoni answered 14/7, 2021 at 23:26 Comment(0)
D
0

An Asp.Net MVC approach using a similar approach to @ashley-lee

Note: Chrome downloads the attachment. See Ctrl-J list. But, if the user chooses 'Open' it will open 'in browser', a user would have to choose 'Open in System Viewer'. For example PDF signature fields are not visible in Browser based PDF viewers.

[HttpGet]
public ActionResult GenericForm()
{
    return new DownloadFileAsAttachmentResult(@"GenericForm.pdf", @"\Content\files\GenericForm.pdf", "application/pdf");
}

public class DownloadFileAsAttachmentResult : ActionResult
{
    private string _filenameWithExtension { get; set; }
    private string _filePath { get; set; }
    private string _contentType { get; set; }
    // false = prompt the user for downloading;  true = browser to try to show the file inline
    private const bool DisplayInline = false;

    public DownloadFileAsAttachmentResult(string FilenameWithExtension, string FilePath, string ContentType)
    {
        _filenameWithExtension = FilenameWithExtension;
        _filePath = FilePath;
        _contentType = ContentType;
    }

    public override void ExecuteResult(ControllerContext context)
    {
        HttpResponseBase response = context.HttpContext.Response;
        response.Buffer = false;
        response.ContentType = _contentType;
        response.AddHeader("Content-Disposition", "attachment; filename=" + _filenameWithExtension); // force download
        response.AddHeader("X-Content-Type-Options", "nosniff");

        response.TransmitFile(_filePath);
    }
}
Doorstop answered 7/9, 2018 at 7:17 Comment(0)
K
0

Note that when the file can't be opened in the client's browser it will be downloaded. To assure filenames with special characters are correctly handled I found the following method to be most robust to set the Content-Disposition header:

var contentDisposition = new ContentDispositionHeaderValue("inline");
contentDisposition.SetHttpFileName("éáëí.docx");
Response.Headers.Add(HeaderNames.ContentDisposition, contentDisposition.ToString());

ContentDispositionHeaderValue is located in namespace Microsoft.Net.Http.Headers.

Kramatorsk answered 23/2, 2023 at 11:46 Comment(0)
S
-1

I followed @myro's answer. For my .net core 3.1 web API, I found the ContentDisposition class and constants in the System.Net.Mime namespace.

var result = new FileContentResult(System.IO.File.ReadAllBytes(filePath), mimeType);
var dispositionType = asAttachment
    ? System.Net.Mime.DispositionTypeNames.Attachment
    : System.Net.Mime.DispositionTypeNames.Inline;
Response.Headers[HeaderNames.ContentDisposition] = new 
System.Net.Mime.ContentDisposition { FileName = "file.text", 
DispositionType = dispositionType }.ToString();
return result;
Somnambulism answered 22/4, 2020 at 18:36 Comment(1)
The ContentDisposition class should not be used for generating the header value. See this commentSteadfast
C
-2

Try this code in classic Razor page (tested in ASP.NET Core 3.1). For forced download is used query param "?download=1". As you see, necessary is add parameter "attachment" into the "Content-Disposition" header for the specific position.

public class FilesModel : PageModel
{
    IWebHostEnvironment environment;
    public FilesModel(IWebHostEnvironment environment)
    {
        this.environment = environment;
    }

    public PhysicalFileResult OnGet()
    {
        // Query params
        string fileName = Request.Query["filename"];
        bool forcedDownload = Request.Query["download"] == "1";

        // File Path
        string filePath = Path.Combine(env.ContentRootPath, "secret-files", fileName);
        if (!System.IO.File.Exists(filePath)) return null; // File not exists

        // Make sure that the user has permissions on the file...

        // File info
        string mime = "image/png"; // Choose the right mime type...
        long fileSize = new FileInfo(filePath).Length;
        string sendType = forcedDownload ? "attachment" : "inline";

        // Headers
        Response.Headers.Add("Content-Disposition", $"{sendType};filename=\"{fileName}\"");
        Response.Headers.Add("Content-Length", fileSize.ToString());
        Response.Headers.Add("X-Content-Type-Options", "nosniff");

        // Result
        return new PhysicalFileResult(filePath, mime);
    }
}
Critter answered 14/7, 2020 at 21:40 Comment(1)
Concatinating the file like that will certainly break the HTTP response when the fileName contains invalid characters.Steadfast

© 2022 - 2024 — McMap. All rights reserved.