如何在ASP.NET Core中上传文档

目录

介绍

问题

解决方案

秘密调味料成分1:IFormFile

秘密调味料成分2:IFormFile参数名称

秘密调味料成分#3:FromForm参数属性

秘密调味料成分4:使用表单元素实例化FormData

源代码

客户端

服务器端

CORS

控制器代码

运行代码


介绍

这个上周末,我可能花了67个小时来弄清楚如何将文档上载到ASP.NET Core应用程序。尽管有很多示例,但它们并不能满足我的要求,更糟糕的是,还需要一定数量的魔咒,而实际上却没有人花时间去解释。我的意思是没人。我在StackOverflow上发现了一个模糊的响应,它导致了一个问题的解决,另一个问题在MozillaFormData站点上。数小时后,为什么这在Postman中起作用,但在我简单的网站上却不起作用?” 我终于有了一个可行的解决方案。

因此,这篇简短的文章的重点是描述神奇的咒语,这样您就不必经历我的痛苦。也许这对您来说很明显,但是直到现在,实际的可行解决方案还是不存在。

那我的问题是什么?你到底怎么了,马克?

问题

上载文档的常用方法是使用form标签和一个附带的submit按钮。该form标签需要一个带有指向上载终结点的URLaction属性。

为什么不?因为我不想对action属性大惊小怪,而我想使用包装在Promise中的XMLHttpRequest,所以我可以处理响应(在我的例子中,就是上载文档的ID)并捕获异常。此外,标准表单提交会执行重定向,尽管可以通过返回NoContent()来停止重定向,但这是一个令人毛骨悚然的麻烦。当然,您不需要提交按钮,可以有一个单独的按钮进行调用form.submit(),这也很棒。除了我还想添加不一定是form数据包一部分的键值对,是的,我在那里发现的纠缠涉及隐藏input元素或创建整个form元素及其子元素。

解决方案

一旦弄清了秘密的调味料,解决方案当然很简单。

秘密调味料成分1IFormFile

因此,.NET Core具有此接口IFormFile,您可以使用该接口将文档流式传输到客户端。但是您不能随便写这样的端点:

public async Task<object> UploadDocument(IFormFile fileToUpload)

秘密调味料成分2IFormFile参数名称

参数名称必须与HTML 中的name属性值匹配!因此,如果您的HTML看起来像这样:

<input type="file" name="file" />

您的端点必须用file作参数名称:

public async Task<object> UploadDocument(IFormFile file)

filefile匹配。

您还可以执行以下操作:

public async Task<object> UploadDocument(DocumentUpload docInfo)

而在类DocumentUpload上你有这个:

public class DocumentUpload
{
    public IFormFile File { get; set; }
}

在此,Filefile匹配。

多个文件也有不同的变体,如List<IFormFile>也被支持。

秘密调味料成分#3FromForm参数属性

上面的例子不起作用!那是因为我们需要C#属性FromForm,所以这是您正确编写端点(使用类版本)的方式:

public async Task<object> UploadDocument([FromForm] DocumentUpload docInfo)

秘密调味料成分4:使用表单元素实例化FormData

因此,在客户端,我们需要这样做:

let formData = new FormData(form);

form来自这样的代码:document.getElementById("uploadForm");

烦人的是,我遇到了许多例子,人们说这行得通:

let formData = new FormData();
formData.append("file", valueFromInputElement);

这行不通!!!

源代码

因此,这是完整的源代码。

客户端

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>Upload Demo</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 οnclick="doUpload();" style="margin-top:10px">Upload</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>

注意事项:

  1. 我已经硬编码"http://localhost:60192/UploadDocument",您可能需要更改端口。
  2. 请注意formData.append(key, value));,这是我要添加不属于form的键/值对的地方。
  3. 没有提交按钮,而是有一个单独的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<string> Hello()
        {
            return "Hello World!";
        }

        [HttpPost]
        [Route("UploadDocument")]
        public async Task<object> 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}");
        }
    }
}

注意事项:

  1. 请注意,控制器路由是""因为我不在乎URL中的路径片段。
  2. 我假设您有c:\temp文件夹。毕竟,这是一个演示!

运行代码

运行ASP.NET Core应用程序。它将启动一个浏览器实例:

任何提示都不要关闭它,只是忽略它。

接下来,打开项目文件夹中的index.html文件,您应该看到:

选择一个文件,输入描述,然后单击Upload按钮,您应该会看到如下所示的警报——当然,响应会有所不同,因为您输入的内容与我不同:

您应该在temp文件夹中注意到您上传的文件:

当然不是那个文件。但是,在找出所有秘密调味料之后,我几乎看起来像这样!

发布了69 篇原创文章 · 获赞 139 · 访问量 45万+

猜你喜欢

转载自blog.csdn.net/mzl87/article/details/104249237