84

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;
}
CodeCaster
  • 147,647
  • 23
  • 218
  • 272
Tedd Hansen
  • 12,074
  • 14
  • 61
  • 97

14 Answers14

99

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");
}
Ashley Lee
  • 3,810
  • 1
  • 18
  • 26
  • 64
    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! – mark.monteiro Nov 08 '17 at 15:49
  • 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 ContentDispositionHeaderValue – Yepeekai Mar 16 '18 at 14:12
  • 15
    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.Headers` → *System.InvalidOperationException*: 'Invalid control character in header: 0x0D' – Marcel Dec 17 '19 at 18:35
  • Note, that you can specify headers in a strongly-typed manner: `Response.GetTypedHeaders().LastModified = cacheEntryLastModifyTime;` Credits: https://learn.microsoft.com/en-us/aspnet/core/performance/caching/middleware?view=aspnetcore-5.0 – Stanislav Berkov Dec 01 '20 at 07:37
  • 3
    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](https://stackoverflow.com/a/40490135/578644). `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. – makman99 Apr 21 '21 at 19:17
92

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);
kimbaudi
  • 13,655
  • 9
  • 62
  • 74
Jonathan Wilson
  • 4,138
  • 1
  • 24
  • 36
  • 10
    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. – Yepeekai Feb 25 '20 at 20:10
  • 4
    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? – kimbaudi Feb 24 '22 at 21:55
  • 1
    Yes it seems..i have added corepolicy.https://stackoverflow.com/a/71282131/7273263 – Ajt Feb 27 '22 at 06:59
  • 1
    Dear Lord. That took me a few hours! – Jens Mander Apr 03 '22 at 04:50
  • @kimbaudi 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 :) – Jonathan Wilson Apr 07 '22 at 18:42
  • Right on. Thanks for your edit. – Jonathan Wilson Apr 09 '22 at 02:08
16

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.

huysentruitw
  • 27,376
  • 9
  • 90
  • 133
  • if the inline string is set inside the ExecuteResultAsync how can I make the FileStreamResult flexible using also value like attachment? – HelloWorld Nov 08 '22 at 11:40
13

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");
CodeCaster
  • 147,647
  • 23
  • 218
  • 272
  • 4
    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 `inline` – quetzalcoatl May 19 '18 at 14:04
6

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 :-)

myro
  • 1,158
  • 2
  • 25
  • 44
6

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>);
Silvestre
  • 804
  • 11
  • 25
  • 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 :) – Hypenate Oct 21 '21 at 08:16
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;
}
Ja9ad335h
  • 4,995
  • 2
  • 21
  • 29
  • I need for ASP.Net Core, afaic this will return a ASP.Net object. Tried it and it returns JSON serialized HttpResponseMessage object. – Tedd Hansen Aug 11 '16 at 14:10
  • 2
    I created the following issue: https://github.com/aspnet/Mvc/issues/5133 to track this. – Kiran Aug 11 '16 at 18:27
  • Also you need NOT read the bytes yourself, you could instead do `return File(System.IO.File.OpenRead("full-file-path"), contentType: "application/pdf");` – Kiran Aug 11 '16 at 18:29
  • I'm returning this, `return File(System.IO.File.ReadAllBytes("C:/Temp/Generic.csv"), "application/octet-stream", "Generic.csv");` but absolutely nothing happens. – Daniel Jackson Jul 13 '18 at 21:59
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");
}
Yepeekai
  • 2,545
  • 29
  • 22
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");
}
Jeremy Morren
  • 615
  • 9
  • 23
  • 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. – makman99 Apr 21 '21 at 19:26
  • Concatinating the file like that will certainly break the HTTP response when the fileName contains invalid characters. – huysentruitw Apr 23 '21 at 09:17
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");
Sam Carlson
  • 1,891
  • 1
  • 17
  • 44
Zafar
  • 441
  • 4
  • 9
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);
    }
}
OzBob
  • 4,227
  • 1
  • 39
  • 48
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.

JKL
  • 978
  • 7
  • 21
-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;
Danwize
  • 141
  • 11
  • The ContentDisposition class should not be used for generating the header value. See [this comment](https://stackoverflow.com/questions/38897764/asp-net-core-content-disposition-attachment-inline/40490135#comment104952405_38909848) – huysentruitw Apr 23 '21 at 09:16
-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);
    }
}
Petr Voborník
  • 1,249
  • 1
  • 14
  • 11
  • Concatinating the file like that will certainly break the HTTP response when the fileName contains invalid characters. – huysentruitw Apr 23 '21 at 09:17