【问题标题】:How to download a file in ASP.NET Core?如何在 ASP.NET Core 中下载文件?
【发布时间】:2017-08-17 06:18:49
【问题描述】:

在 MVC 中,我们使用以下代码下载文件。在 ASP.NET core 中,如何实现呢?

    HttpResponse response = HttpContext.Current.Response;                 
    System.Net.WebClient net = new System.Net.WebClient();
    string link = path;
    response.ClearHeaders();
    response.Clear();
    response.Expires = 0;
    response.Buffer = true;
    response.AddHeader("Content-Disposition", "Attachment;FileName=a");
    response.ContentType = "APPLICATION/octet-stream";
    response.BinaryWrite(net.DownloadData(link));
    response.End();

【问题讨论】:

    标签: c# asp.net-core


    【解决方案1】:

    您的控制器应该返回一个IActionResult,并使用File 方法,例如:

    [HttpGet("download")]
    public IActionResult GetBlobDownload([FromQuery] string link)
    {
        var net = new System.Net.WebClient();
        var data = net.DownloadData(link);
        var content = new System.IO.MemoryStream(data);
        var contentType = "APPLICATION/octet-stream";
        var fileName = "something.bin";
        return File(content, contentType, fileName);
    }
    

    【讨论】:

    • 我收到来自应用程序的 HTTP 500,但如果我将文件保存到磁盘,它执行正常(但仍然是 http 500)这是一个 PDF 文件...有什么想法吗?
    • @Daniel HTTP 500 的字面意思是“任何事情”,所以由于服务器不知道发生了什么,它只是说“内部服务器错误”。解决此问题的唯一方法是找出真正的错误,这通常与业务逻辑无关,很可能是 NullReferenceException 之类的编程错误或不兼容类型之间的转换等。日志文件和调试是解决此问题的一些有效方法一种问题。
    • 我正在下载 excel 文件。它会下载,但是当我尝试打开文件时,它说文件已损坏。即使是服务器上的文件和我下载的文件的大小也不相等。它在服务器上是 2.7kb。但是当我下载它是10kb。这是什么问题?
    • 这是一个旧答案,但我的评论仍然适用。此代码泄漏了 MemoryStream。 MemoryStream 应该嵌套在 using 块中,以便可以释放托管资源。如果添加了他使用,则该方法无法返回文件,因为 MemoryStream 超出范围并在文件返回之前被释放。
    • ASP.NET Core 基础架构负责在发送响应时处理流,因此不会发生泄漏(请参阅FileResultExecutorBase.WriteFileAsync 方法)。如果您尝试用 using 语句包围此代码,则不会发送任何文件。
    【解决方案2】:

    您可以尝试以下代码来下载文件。它应该返回FileResult

    public ActionResult DownloadDocument()
    {
    string filePath = "your file path";
    string fileName = "your file name";
        
    byte[] fileBytes = System.IO.File.ReadAllBytes(filePath);
        
    return File(fileBytes, "application/force-download", fileName);
        
    }
    

    【讨论】:

      【解决方案3】:

      一个相对简单的方法是使用内置的PhysicalFile结果,它适用于所有控制器:MS Docs: PhysicalFile

      一个简单的例子:

      public IActionResult DownloadFile(string filePath)
      {
           return PhysicalFile(filePath, MimeTypes.GetMimeType(filePath), Path.GetFileName(filePath));
      }
      

      当然,出于安全考虑,您永远不应该公开这种 API。

      我通常将实际文件路径隐藏在友好标识符后面,然后我用它来查找真实文件路径(如果传入无效 ID,则返回 404),即:

      [HttpGet("download-file/{fileId}")]
      public IActionResult DownloadFile(int fileId)
      {
          var filePath = GetFilePathFromId(fileId);
          if (filePath == null) return NotFound();
              
          return PhysicalFile(filePath, MimeTypes.GetMimeType(filePath), Path.GetFileName(filePath));
      }
      

      对于那些好奇的人,MimeTypes 助手是来自 MimeKit 的人们提供的一个很棒的小 Nuget 包

      【讨论】:

        【解决方案4】:

        这是我的 Medium 文章,逐步描述所有内容(其中还包括一个 GitHub 存储库): https://medium.com/@tsafadi/download-a-file-with-asp-net-core-e23e8b198f74

        控制器应该是这样的:

        [HttpGet]
        public IActionResult DownloadFile()
        {
            // Since this is just and example, I am using a local file located inside wwwroot
            // Afterwards file is converted into a stream
            var path = Path.Combine(_hostingEnvironment.WebRootPath, "Sample.xlsx");
            var fs = new FileStream(path, FileMode.Open);
        
            // Return the file. A byte array can also be used instead of a stream
            return File(fs, "application/octet-stream", "Sample.xlsx");
        }
        

        视图内部:

        $("button").click(function () {
            var xhr = new XMLHttpRequest();
            xhr.open("GET", "Download/DownloadFile", true);
            xhr.responseType = "blob";
            xhr.onload = function (e) {
                if (this.status == 200) {
                    var blob = this.response;
        
                    /* Get filename from Content-Disposition header */
                    var filename = "";
                    var disposition = xhr.getResponseHeader('Content-Disposition');
                    if (disposition && disposition.indexOf('attachment') !== -1) {
                        var filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
                        var matches = filenameRegex.exec(disposition);
                        if (matches != null && matches[1]) filename = matches[1].replace(/['"]/g, '');
                    }
        
                    // This does the trick
                    var a = document.createElement('a');
                    a.href = window.URL.createObjectURL(blob);
                    a.download = filename;
                    a.dispatchEvent(new MouseEvent('click'));
                }
            }
            xhr.send();
        });
        

        【讨论】:

        • 注意:ocTet-stream 中缺少一个“t”。
        • 这是我真正需要的!因为在其他解决方案中,当文件是浏览器无法解码的内容时,它只是下载了一个没有扩展名的文件。
        • 很高兴它有帮助!如果您觉得它有用,请给它一个赞:) @SarahLissachell
        【解决方案5】:

        创建一个服务,比如 FileService。

        public class FileService
        {
            private readonly IHostingEnvironment _hostingEnvironment;
            constructor(IHostingEnvironment hostingEnvironment)
            {
                this._hostingEnvironment = hostingEnvironment;
            }
        }
        

        为文件的 FileService MimeType 添加方法

        private string GetMimeType(string fileName)
        {
            // Make Sure Microsoft.AspNetCore.StaticFiles Nuget Package is installed
            var provider = new FileExtensionContentTypeProvider();
            string contentType;
            if (!provider.TryGetContentType(fileName, out contentType))
            {
                contentType = "application/octet-stream";
            }
            return contentType;
        }
        

        现在添加一个下载文件的方法,

        public FileContentResult GetFile(string filename) 
        {
            var filepath = Path.Combine($"{this._environment.WebRootPath}\\path-to-required-folder\\{filename}");
        
            var mimeType = this.GetMimeType(filename); 
        
            byte[] fileBytes;
        
            if (File.Exists(filepath))
            {
                fileBytes = File.ReadAllBytes(filepath); 
            } 
            else
            {
                // Code to handle if file is not present
            }
        
            return new FileContentResult(fileBytes, mimeType)
            {
                FileDownloadName = filename
            };
        }
        

        现在添加控制器方法并在FileService中调用GetFile方法,

         public IActionResult DownloadFile(string filename) 
         {
            // call GetFile Method in service and return       
         }
        

        【讨论】:

        • 此方法将所有文件内容读入内存,不适合较大的文件。
        【解决方案6】:

        Asp.net Core 2.1+ 示例(最佳实践)

        Startup.cs:

        private readonly IHostingEnvironment _env;
        
        public Startup(IConfiguration configuration, IHostingEnvironment env)
        {
            Configuration = configuration;
            _env = env;
        }
        
        services.AddSingleton(_env.ContentRootFileProvider); //Inject IFileProvider
        

        SomeService.cs:

        private readonly IFileProvider _fileProvider;
        
        public SomeService(IFileProvider fileProvider)
        {
            _fileProvider = fileProvider;
        }
        
        public FileStreamResult GetFileAsStream()
        {
            var stream = _fileProvider
                .GetFileInfo("RELATIVE PATH TO FILE FROM APP ROOT")
                .CreateReadStream();
        
            return new FileStreamResult(stream, "CONTENT-TYPE")
        }
        

        控制器将返回IActionResult

        [HttpGet]
        public IActionResult Get()
        {
            return _someService.GetFileAsStream() ?? (IActionResult)NotFound();
        }
        

        【讨论】:

        • 如何创建_someService?
        • 为什么这是最佳实践?
        • @Daniel 例如在 ConfigureServices 中注册:services.AddTransident<SomeService>() 然后通过构造函数参数将 SomeService 注入您的控制器。 public MyController(SomeService someService)
        【解决方案7】:

        Action 方法需要返回带有文件流、字节[] 或虚拟路径的 FileResult。您还需要知道正在下载的文件的内容类型。这是一个示例(快速/脏)实用程序方法。示例视频链接 How to download files using asp.net core

        [Route("api/[controller]")]
        public class DownloadController : Controller
        {
            [HttpGet]
            public async Task<IActionResult> Download()
            {
                var path = @"C:\Vetrivel\winforms.png";
                var memory = new MemoryStream();
                using (var stream = new FileStream(path, FileMode.Open))
                {
                    await stream.CopyToAsync(memory);
                }
                memory.Position = 0;
                var ext = Path.GetExtension(path).ToLowerInvariant();
                return File(memory, GetMimeTypes()[ext], Path.GetFileName(path));
            }
        
            private Dictionary<string, string> GetMimeTypes()
            {
                return new Dictionary<string, string>
                {
                    {".txt", "text/plain"},
                    {".pdf", "application/pdf"},
                    {".doc", "application/vnd.ms-word"},
                    {".docx", "application/vnd.ms-word"},
                    {".png", "image/png"},
                    {".jpg", "image/jpeg"},
                    ...
                };
            }
        }
        

        【讨论】:

        • 这是复制内存中的整个文件还是只复制正在传输的文件的一部分?
        【解决方案8】:
            [HttpGet]
            public async Task<FileStreamResult> Download(string url, string name, string contentType)
            {
                var stream = await new HttpClient().GetStreamAsync(url);
        
                return new FileStreamResult(stream, contentType)
                {
                    FileDownloadName = name,
                };
            }
        

        【讨论】:

          【解决方案9】:

          我的路很短,我认为它适合大多数人的需要。

            [HttpPost]
            public ActionResult Download(string filePath, string fileName)
            {
                var fileBytes = System.IO.File.ReadAllBytes(filePath);
                new FileExtensionContentTypeProvider().TryGetContentType(Path.GetFileName(filePath), out var contentType);
                return File(fileBytes, contentType ?? "application/octet-stream", fileName);
            }
          

          【讨论】:

          • 这会将整个文件读入内存。不适合更大的文件或更大的服务器负载。
          猜你喜欢
          • 2018-07-14
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-03-15
          • 1970-01-01
          • 2020-02-07
          • 2022-02-01
          相关资源
          最近更新 更多