ASP.NET Core – Upload and Download Files (上传和下载文件)

前言

以前得文章 Asp.net core 学习笔记 ( upload/download files 文件上传与下载 ), 这篇是修订版.

 

Handle Upload File (处理上传文件)

我的例子是基于 Web API 和 Ajax 的.

前端 Ajax

const btn = document.querySelector('button')!;
btn.addEventListener('click', async () => {
  const formData = new FormData();
  formData.append('file', createTextBlob('Hello World'), 'text1.txt');
  // formData.append('file', createTextBlob('Derrick'), 'text2.txt'); // for multiple file

  await fetch('http://localhost:5221/Upload', {
    method: 'POST',
    body: formData,
  });

  function createTextBlob(text: string): Blob {
    const textEncoder = new TextEncoder();
    const bytes = textEncoder.encode(text);
    return new Blob([bytes], {
      type: 'text/plain',
    });
  }
});

upload 一个/多个 text file.

Web API

DTO

public class UploadDto
{
    public IFormFile File { get; set; } = null!;
}

关键就是这个 IFormFile, 它就是用来接收前端 File 的.

Controller

[ApiController]
public class UploadController : ControllerBase
{
    [HttpPost("Upload")]
    [Consumes("multipart/form-data")]
    [ProducesResponseType(StatusCodes.Status200OK)]
    public async Task<ActionResult> Upload([FromForm] UploadDto dto)
    {
        return Ok("ok");
    }
}

把 upload file 写入 disk

var fileRootFullPath = Path.Combine(AppContext.BaseDirectory, $@"..\..\..\{dto.File.FileName}");
using var fileStream = System.IO.File.Create(fileRootFullPath);
await dto.File.CopyToAsync(fileStream);

IFormFile 用法很简单, 它通过 .FileName 拿到文件名, 通过 CopyToAsync 把 stream 传给别人. 比如上面是把 stream 导入 file stream, 这样就写入磁盘了.

把 upload file stream 传给任何人使用

// 直接读 string 出来
using var stream = dto.File.OpenReadStream();
using var streamReader = new StreamReader(stream, encoding: Encoding.UTF8);
var text = await streamReader.ReadToEndAsync(); // Hello World

通过 OpenReadStream() 导出 stream 就可以做任何 stream 处理了.

handle multiple files

// JavaScript
const formData = new FormData();
formData.append('file', createTextBlob('Hello World'), 'text1.txt');
formData.append('file', createTextBlob('Derrick'), 'text2.txt'); // for multiple file

// C#
public class UploadDto
{
    public List<IFormFile> File { get; set; } = null!;
}

没什么提别的, 就只是换成了 List<IFormFile>

file size limit

ASP.NET Core 默认的 max upload file size 是 28.6 MB。

在 Program.cs

修改 Kestrel

builder.WebHost.ConfigureKestrel((context, serverOptions) =>
{
  serverOptions.Limits.MaxRequestBodySize = 157286400; // 150MB
});

修改 FormOptions

builder.Services.Configure<FormOptions>(options =>
{
  options.MultipartBodyLengthLimit = 157286400; // 150 MB
});

Production IIS 的话,修改 web.config

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.webServer>
    <security>
      <requestFiltering>
        <!-- Set the maximum allowed content length to 150 MB (in bytes) -->
        <requestLimits maxAllowedContentLength="157286400" />
      </requestFiltering>
    </security>
    <!-- Add other configurations as needed -->
  </system.webServer>
</configuration>

 

Download File (下载文件)

前端 HTML

<a href="http://localhost:5221/Download" download="">download</a>

假如是 same origin 同域,那么 ASP.NET Core 的 statis files 就可以处理了。不需要什么特比的配置。

那如果是 cors origin 的话就比较麻烦。

首先,必须开启 cros origin 咯,另外呢,还需要有 response header Content-Disposition,像这样

 

通常我们会用 Web Api 另开一个接口处理。

Web API Controller

[ApiController]
public class UploadController : ControllerBase
{
  [HttpGet("Download")]
  [Produces(MediaTypeNames.Application.Octet)]
  [ProducesResponseType(StatusCodes.Status200OK)]
  public ActionResult Download()
  {
    var filePath = @"C:\TestDownload\wwwroot\yangmi.jpg";

    // 第一招 PhysicalFile + 自己添加 header "Content-Disposition"
    Response.Headers.Append("Content-Disposition", "attachment;filename=yangmi.jpg");
    return PhysicalFile(filePath, "image/jpeg");

    // 第二招,使用 File,它自带 header "Content-Disposition": attachment
    return File(
      fileContents: System.IO.File.ReadAllBytes(filePath),
      contentType: "image/jpeg",
      fileDownloadName: "yangmi.jpg"
    );

    // 或这样也是等价的
    // return new FileContentResult(System.IO.File.ReadAllBytes(filePath), "image/jpeg")
    // {
    //     FileDownloadName = "yangmi.jpg"
    // };
  }
}

重点就是要有 bytes,mime type, file name 还有 header "Content-Disposition" attachment。

Force open save as dialog (showSaveFilePicker)

用 Chrome 下载文件的体验是,它会直接放到 default location folder 里,除非用户开启 “Ask where to save each file before downloading” 设置

Firefox 也是这样。无意间发现 pixlr.com 在 Chrome 可以 force open save as dialog。

我猜它用了一个比较新的 File System API : showSaveFilePicker。目前只有 Chrome 支持。

参考: Stack Overflow – Force showing the "Save as" dialog box when downloading a file

代码大致长这样

const canvas = document.querySelector<HTMLCanvasElement>('.img')!;
const ctx = canvas.getContext('2d')!;
const response = await fetch('https://192.168.1.152:4200/src/images/nana.jpg');
const blob = await response.blob();
const img = await createImageBitmap(blob);
[canvas.width, canvas.height] = [img.width, img.height];
ctx.drawImage(img, 0, 0);

const btn = document.querySelector<HTMLElement>('.btn')!;

btn.addEventListener('click', () => {
  canvas.toBlob(async blob => {
    // 关键:
    const handle = await showSaveFilePicker({
      suggestedName: 'nana.jpg',
      types: [
        {
          description: 'Jpeg file',
          accept: {
            'image/jpeg': ['.jpeg', '.jpg'],
          },
        },
      ],
    });
    const writable = await handle.createWritable();
    await writable.write(blob!);
    writable.close();
  }, 'image/jpeg');
});

不过这个方式下载的文件,不会出现在 Chrome 的 recently download 记入里面,体验有点差。

另外,目前 TypeScript 还不支持哦,需要

yarn add @types/wicg-file-system-access --dev

 

posted @ 2023-04-08 23:11  兴杰  阅读(1464)  评论(0编辑  收藏  举报