在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
参数名称必须与HTML中的name属性值匹配!所以如果HTML看起来像这样:
<input type="file" name="file" />
端点必须使用file作为参数名称:
public async Task
"file"与"file"匹配。
上述示例不会工作!那是因为需要C#属性FromForm,所以这是正确编写端点的方式(使用类版本):
public async Task
在客户端,需要这样做:
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
注意:控制器路由是"",因为不关心URL中的路径片段。 假设有c:\temp文件夹。毕竟,这是一个演示!