目录
介绍
问题
解决方案
秘密调味料成分1:IFormFile
秘密调味料成分2:IFormFile参数名称
秘密调味料成分#3:FromForm参数属性
秘密调味料成分4:使用表单元素实例化FormData
源代码
客户端
服务器端
CORS
控制器代码
运行代码
- 下载演示-4.1 KB
这个上周末,我可能花了6到7个小时来弄清楚如何将文档上载到ASP.NET Core应用程序。尽管有很多示例,但它们并不能满足我的要求,更糟糕的是,还需要一定数量的魔咒,而实际上却没有人花时间去解释。我的意思是没人。我在StackOverflow上发现了一个模糊的响应,它导致了一个问题的解决,另一个问题在Mozilla的FormData站点上。数小时后,“为什么这在Postman中起作用,但在我简单的网站上却不起作用?” 我终于有了一个可行的解决方案。
因此,这篇简短的文章的重点是描述神奇的咒语,这样您就不必经历我的痛苦。也许这对您来说很明显,但是直到现在,实际的可行解决方案还是不存在。
那我的问题是什么?你到底怎么了,马克?
问题上载文档的常用方法是使用form标签和一个附带的submit按钮。该form标签需要一个带有指向上载终结点的URL的action属性。
为什么不?因为我不想对action属性大惊小怪,而我想使用包装在Promise中的XMLHttpRequest,所以我可以处理响应(在我的例子中,就是上载文档的ID)并捕获异常。此外,标准表单提交会执行重定向,尽管可以通过返回NoContent()来停止重定向,但这是一个令人毛骨悚然的麻烦。当然,您不需要“提交”按钮,可以有一个单独的按钮进行调用form.submit(),这也很棒。除了我还想添加不一定是form数据包一部分的键值对,是的,我在那里发现的纠缠涉及隐藏input元素或创建整个form元素及其子元素。
解决方案一旦弄清了秘密的调味料,解决方案当然很简单。
秘密调味料成分1:IFormFile因此,.NET Core具有此接口IFormFile,您可以使用该接口将文档流式传输到客户端。但是您不能随便写这样的端点:
public async Task UploadDocument(IFormFile fileToUpload)
秘密调味料成分2:IFormFile参数名称
参数名称必须与HTML 中的name属性值匹配!因此,如果您的HTML看起来像这样:
您的端点必须用file作参数名称:
public async Task UploadDocument(IFormFile file)
“file”与“file”匹配。
您还可以执行以下操作:
public async Task UploadDocument(DocumentUpload docInfo)
而在类DocumentUpload上你有这个:
public class DocumentUpload
{
public IFormFile File { get; set; }
}
在此,“File”与“file”匹配。
多个文件也有不同的变体,如List也被支持。
秘密调味料成分#3:FromForm参数属性上面的例子不起作用!那是因为我们需要C#属性FromForm,所以这是您正确编写端点(使用类版本)的方式:
public async Task UploadDocument([FromForm] DocumentUpload docInfo)
秘密调味料成分4:使用表单元素实例化FormData
因此,在客户端,我们需要这样做:
let formData = new FormData(form);
form来自这样的代码:document.getElementById("uploadForm");
烦人的是,我遇到了许多例子,人们说这行得通:
let formData = new FormData();
formData.append("file", valueFromInputElement);
这行不通!!!
因此,这是完整的源代码。
客户端
Upload Demo
html.wait, html.wait * {
cursor: wait !important;
}
Upload
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");
}
注意事项:
- 我已经硬编码"http://localhost:60192/UploadDocument",您可能需要更改端口。
- 请注意formData.append(key, value));,这是我要添加不属于form的键/值对的地方。
- 没有“提交”按钮,而是有一个单独的Upload按钮。
就像我说的那样,简单!
服务器端我是在VS2019中编写的代码,因此我们使用的是.NET Core 3.1,因此让我们先介绍一些调整。
CORS必须添加允许跨域发布的功能,因为ASP.NET Core服务器不为该页面提供服务,我只是将其直接加载到Chrome中。因此,请求的“来源”不是来自“服务器”。在Startup类中,我添加了AddCors服务。
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调用之前完成。说真的,我阅读了有关中间件管道的说明,但是我必须说,WTF?为什么会有初始化顺序问题?
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
{
public UploadController()
{
}
[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文件夹。毕竟,这是一个演示!
运行ASP.NET Core应用程序。它将启动一个浏览器实例:
任何提示都不要关闭它,只是忽略它。
接下来,打开项目文件夹中的“index.html”文件,您应该看到:
选择一个文件,输入描述,然后单击“Upload”按钮,您应该会看到如下所示的警报——当然,响应会有所不同,因为您输入的内容与我不同:
您应该在temp文件夹中注意到您上传的文件:
当然不是那个文件。但是,在找出所有秘密调味料之后,我几乎看起来像这样!