Search code examples
c#.net-corerazor-pages

Return File from URL as IActionResult


I am attempting to get a PDF to download upon the press of a button using .Net Core and Razor Pages. This is as close as I've gotten but am encountering the error

"ObjectDisposedException: Cannot access a closed file. System.IO.FileStream.ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)".

How do I properly return the file?

Test.cshtml

@page
@model LoanCalculator.Pages.TestModel
@{
}
<form method="post">
    <fieldset>
        <input type="submit" value="Submit" id="submitButton" />
    </fieldset>
</form>

Test.cshtml.cs

namespace LoanCalculator.Pages
{
    public class TestModel : PageModel
    {

        public void OnGet()
        {
            
        }

    public async Task<IActionResult> OnPostAsync()
    {
        using var httpClient = new HttpClient();

        var url = "https://storage.googleapis.com/a2p-v2-storage/528a02ea-a399-4901-b8d6-d0494be68331";
        byte[] imageBytes = await httpClient.GetByteArrayAsync(url);

        using var fs = new FileStream("favicon.png", FileMode.Create);
        fs.Write(imageBytes, 0, imageBytes.Length);

        return File(fs, "application/pdf", "FileDownloadName.png");
    }
}

Solution

  • The issue is you are disposing the stream and not setting the position of the stream to zero.

    You can make this much more memory efficient by just handing the stream off from the request to the response.

    Take a look at this example:

    var targetFile = new Uri("https://www.example.com/file.pdf");
    
    var resp = await _httpClientFactory.CreateClient().GetAsync(
        targetFile, HttpCompletionOption.ResponseHeadersRead);
    
    Response.ContentLength = resp.Content.Headers.ContentLength;
    
    return File(await resp.Content.ReadAsStreamAsync(),
        "application/pdf", Path.GetFileName(targetFile.LocalPath));
    

    (this is not tested and written from memory)

    Also, you should be using IHttpClientFactory and not create a new HttpClient each time.

    An example of this:

    // ...
    using System.Net.Http;
    // ...
    
    namespace LoanCalculator.Pages
    {
        public class TestModel : PageModel
        {
            private readonly IHttpClientFactory _httpClientFactory;
    
            public TestModel(IHttpClientFactory httpClientFactory)
            {
                _httpClientFactory = httpClientFactory;
            }
        }
    }
    

    In your Startup.cs, add this:

    public void ConfigureServices(IServiceCollection services)
    {
        // ...
        services.AddHttpClient();
        // ...
    }