ASP.NET Core 文件上传指南

在ASP.NET Core应用程序中上传文件是一个常见的需求,但实现起来可能会遇到一些挑战。本文将介绍如何通过客户端的JavaScript和服务器端的C#代码来实现文件上传功能。

问题描述

通常,使用HTML表单(form)和提交按钮(submit)来上传文件。表单标签需要一个action属性,指向上传端点的URL。但这种方式并不是想要的,因为不想处理action属性,而是想使用XMLHttpRequest结合Promise来处理响应(例如,上传文件的ID),并且捕获异常。此外,标准的表单提交会进行重定向,虽然可以通过返回NoContent()来阻止,但这并不是一个好的解决方案。当然,不需要提交按钮,可以有一个单独的按钮调用form.submit(),这也是可行的。但还想要添加一些键值对,这些键值对不一定是表单数据包的一部分,发现的解决方法涉及到有隐藏的input元素或者动态创建整个form元素及其子元素。这些解决方案真是让人头疼!

解决方案

解决方案一旦弄清楚了秘密配方就非常简单。

.NET Core提供了一个接口IFormFile,可以用它来将文件流传输到客户端。但是,不能随意编写端点,如下所示: public async Task UploadDocument(IFormFile fileToUpload)

参数名称必须与HTML中的name属性值匹配!所以如果HTML看起来像这样: <input type="file" name="file" /> 端点必须使用file作为参数名称: public async Task UploadDocument(IFormFile file) "file"与"file"匹配。

上述示例不会工作!那是因为需要C#属性FromForm,所以这是正确编写端点的方式(使用类版本): public async Task UploadDocument([FromForm] DocumentUpload docInfo)

在客户端,需要这样做: let formData = new FormData(form); 其中form来自这样的代码: document.getElementById("uploadForm");

源代码

以下是完整的客户端代码。

<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>上传演示</title> </head> <body> <style> html.wait, html.wait * { cursor: wait !important; } </style> <form id="uploadForm"> <div> <input type="file" name="file"> </div> <div style="margin-top: 10px"> <input name="description" placeholder="Description"> </div> </form> <button onclick="doUpload();" style="margin-top:10px">上传</button> <script> function doUpload() { let form = document.getElementById("uploadForm"); Upload("http://localhost:60192/UploadDocument", form, { clientDate: Date() }) .then(xhr => alert(xhr.response)) .catch(xhr => alert(xhr.statusText)); } async function Upload(url, form, extraData) { waitCursor(); let xhr = new XMLHttpRequest(); return new Promise((resolve, reject) => { xhr.onreadystatechange = () => { if (xhr.readyState == 4) { if (xhr.status >= 200 && xhr.status < 300) { readyCursor(); resolve(xhr); } else { readyCursor(); reject(xhr); } } }; xhr.open("POST", url, true); let formData = new FormData(form); Object.entries(extraData).forEach(([key, value]) => formData.append(key, value)); xhr.send(formData); }); } function waitCursor() { document.getElementsByTagName("html")[0].classList.add("wait"); } function readyCursor() { document.getElementsByTagName("html")[0].classList.remove("wait"); } </script> </body> </html>

注意:已经硬编码了"http://localhost:60192/UploadDocument",可能需要更改端口。 注意formData.append(key, value);这是添加不属于表单的键值对的地方。 没有提交按钮,而是有一个单独的上传按钮。

在VS2019中编写了代码,所以使用的是.NET Core 3.1,让先覆盖一些调整。

由于ASP.NET Core服务器没有提供页面,只是直接在Chrome中加载,所以请求的"origin"不是来自"服务器"。因此,添加允许跨域POST的能力是必要的。

public void ConfigureServices(IServiceCollection services) { services.AddControllers(); services.AddCors(options => { options.AddPolicy( "CorsPolicy", builder => builder.AllowAnyOrigin() .AllowAnyMethod() .AllowAnyHeader()); }); }

并在以下位置应用它: public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { app.UseCors("CorsPolicy"); // 必须在app.UseEndpoints之前调用。严肃地说。读了有关中间件管道的解释,但不得不说,为什么会有初始化顺序问题? app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); }

using System.IO; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; namespace UploadDemo.Controllers { public class DocumentUpload { public string Description { get; set; } public IFormFile File { get; set; } public string ClientDate { get; set; } } [ApiController] [Route("")] public class UploadController : ControllerBase { [HttpGet] public ActionResult Hello() { return "Hello World!"; } [HttpPost] [Route("UploadDocument")] public async Task UploadDocument([FromForm] DocumentUpload docInfo) { IFormFile iff = docInfo.File; string fn = iff.FileName; var tempFilename = $@"c:\temp\{fn}"; using (var fileStream = new FileStream(tempFilename, FileMode.Create)) { await iff.CopyToAsync(fileStream); } return Ok($"File {fn} uploaded. Description = {docInfo.Description} on {docInfo.ClientDate}"); } } }

注意:控制器路由是"",因为不关心URL中的路径片段。 假设有c:\temp文件夹。毕竟,这是一个演示!

沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485