目录
介绍
为什么使用流?
我是怎么想出来的
服务器网址
服务器
文档上传类
处理大文件
一个C#客户端
客户网页
用XHR上传实现替换标准提交流程
将数据作为 Blob 上传
通过拖放上传文件
结论
- 下载源代码 - 823.4 KB
试图找到一个关于如何将客户端文件流式上传到服务器的权威参考指南是一项艰巨的任务,因此这篇文章。
本文演示:
- 从C#客户端上传文件
- 从浏览器页面上传文件:
- 使用Form元素
- 将XHR与Form元素一起使用
- 上传“blob”数据
- 拖放文件
为了简单起见:
- 描述的所有变体都由单个后端端点处理。
- 前端只使用简单的JavaScript。该演示实际上只是一个HTML文件。
- 我还演示了如何向正在上传的文件/blob添加其他元数据。
虽然答案应该很明显,但主要原因是客户端和服务器端都不必将整个文件拉入内存——相反,流将大文件中的数据分解成小块。从客户端读取文件到服务器将内容保存到文件的整个过程都作为“流数据”进行管理,并且两端最多只需要一个流块大小的缓冲区。
我是怎么想出来的将这些拼凑在一起涉及大量的Google搜索。这些是我发现最有用的链接:
- 增加允许的内容长度
- 增加允许的表单内容大小
- 拖放实现
- 将数据作为Blob上传
- 通过API调用上传(C#客户端)
服务器设置为使用IIS,因此到处使用的URL是http://localhost/FileStreamServer/file/upload并且因为这是一篇演示文章,所以在示例中它是硬编码的。显然,人们会在现实生活中以不同的方式实现这一点!
服务器服务器使用.NET Core 3.1实现。API端点很简单:
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace FileStreamServer.Controllers
{
[ApiController]
[Route("[controller]")]
public class FileController : ControllerBase
{
public FileController()
{
}
[HttpPost("upload")]
public async Task Upload([FromForm] DocumentUpload docInfo)
{
IActionResult ret;
if (docInfo == null || docInfo.File == null)
{
ret = BadRequest("Filename not specified.");
}
else
{
var fn = Path.GetFileNameWithoutExtension(docInfo.File.FileName);
var ext = Path.GetExtension(docInfo.File.FileName);
string outputPathAndFile = $@"c:\projects\{fn}-{docInfo.Id}{ext}";
using (FileStream output = System.IO.File.Create(outputPathAndFile))
{
await docInfo.File.CopyToAsync(output);
}
ret = Ok();
}
return ret;
}
}
}
该实现的要点如下:
- 该属性[FromForm]通知将接收表单数据的端点处理程序。
- 该类DocumentUpload是“文件”和表单元数据的容器。
public class DocumentUpload
{
public IFormFile File { get; set; }
public string Id { get; set; }
}
属性名称必须与前端使用的命名约定相匹配!此示例说明了仅指定一个文件且元数据仅包含“Id”值的期望。
处理大文件其中更复杂的部分实际上是配置ASP.NET Core以接受大文件。首先,必须修改web.config文件。在该system.webServer部分中,我们必须增加请求限制:
其次,需要设置表单选项。我选择在启动代码中执行此操作:
public void ConfigureServices(IServiceCollection services)
{
...
services.Configure(x =>
{
// int.MaxValue is 2GB.
x.ValueLengthLimit = int.MaxValue;
x.MultipartBodyLengthLimit = int.MaxValue;
});
...
}
因为int.MaxValue最大值为2GB,所以上传文件的大小被限制在该限制附近。由于编码开销,一个可以上传的实际文件大小小于2GB,但我还没有计算出少了多少。
一个C#客户端一个非常简单的C#控制台客户端,它可以上传我的一只猫的图片(文件包含在文章下载中),它的全部内容是这样的:
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
namespace FileStreamClient
{
class Program
{
static void Main(string[] args)
{
var task = Task.Run(async () => await UploadAsync
("http://localhost/FileStreamServer/file/upload", "cat.png"));
task.Wait();
}
// https://stackoverflow.com/a/16925159
// This was an alternate that is a lot more complicated:
// https://stackoverflow.com/a/2996904
// and that I couldn't get to work on the server-side.
private async static Task UploadAsync(string url, string filename)
{
using var fileStream = new FileStream("cat.png", FileMode.Open, FileAccess.Read);
using var fileStreamContent = new StreamContent(fileStream);
using var stringContent = new StringContent("13");
using var client = new HttpClient();
using var formData = new MultipartFormDataContent();
formData.Add(stringContent, "Id");
formData.Add(fileStreamContent, "File", filename);
var response = await client.PostAsync(url, formData);
Stream ret = await response.Content.ReadAsStreamAsync();
return ret;
}
}
}
请注意内容字符串“Id”和文件流内容“File”名称如何匹配DocumentUpload服务器上定义的类中的属性。
客户网页对于Web客户端,我想演示支持几种不同的东西:
- 带有提交按钮的直接表单上传
- 用XHR上传实现替换标准提交流程
- 将数据作为blob上传
- 通过拖放上传文件
为简单起见,不支持多个文件。
文章下载中提供的HTML文件可以直接在浏览器中打开,例如:file:///C:/projects/FileStreaming/FileStreamClient/upload.html
带有提交按钮的直接表单上传
这是一个非常简单的过程,但该操作会将浏览器重定向到上传URL,这确实不是我们想要的,除非您想显示“您的文档已上传”之类的页面。
HTML:
Upload
这里的所有都是它的。请注意,服务器上的DocumentUpload类的name标签匹配(不区分大小写)。
用XHR上传实现替换标准提交流程此实现需要更改form标签并实现XHR上传代码。
HTML:
Upload
Upload using XHR
请注意,使用XHR上传的按钮不是表单的一部分!
JavaScript实现:
function xhrUpload() {
const form = document.getElementById("uploadForm");
const xhr = new XMLHttpRequest();
responseHandler(xhr);
xhr.open("POST", "http://localhost/FileStreamServer/file/upload");
const formData = new FormData(form);
xhr.send(formData);
}
function responseHandler(xhr) {
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
uploadResponse(xhr);
}
}
}
function uploadResponse(xhr) {
if (xhr.status >= 200 && xhr.status < 300) {
alert("Upload successful.");
} else {
alert("Upload failed: " + xhr.responseText);
}
}
这段代码最有趣的部分是:
const form = document.getElementById("uploadForm");
...
const formData = new FormData(form);
在实例化FormData对象时应用输入的任何id值和选择的文件。
将数据作为 Blob 上传HTML:
Upload Data
JavaScript:
function uploadData() {
const id = document.getElementById("id").value;
const data = document.getElementById("data").value;
const blob = new Blob([data]);
var xhr = new XMLHttpRequest();
responseHandler(xhr);
xhr.open("POST", "http://localhost/FileStreamServer/file/upload");
var formData = new FormData();
formData.append("Id", id);
formData.append("File", blob, "data.txt");
xhr.send(formData);
}
注意这里FormData是在不引用表单的情况下实例化的,而是以编程方式应用表单数据。另请注意,文件名是硬编码的。此代码还重用了responseHandler之前定义的。
通过拖放上传文件
HTML:
Drag & drop file here
这里重要的是,要使拖放工作,ondrop和ondragover必须都有实现。
JavaScript:
function allowDrop(e) {
e.preventDefault();
}
function dropFile(e) {
e.preventDefault();
const dt = e.dataTransfer;
// We could implement multiple files here.
const file = dt.files[0];
const id = document.getElementById("id").value;
uploadFile(id, file);
}
function uploadFile(id, file) {
var xhr = new XMLHttpRequest();
responseHandler(xhr);
xhr.open("POST", "http://localhost/FileStreamServer/file/upload");
var formData = new FormData();
formData.append("Id", id);
formData.append("File", file, file.name);
xhr.send(formData);
}
请注意,我们调用preventDefault它是为了防止浏览器实际尝试呈现文件。
这段代码另一个有趣的部分是我们如何获取文件对象:
const dt = e.dataTransfer;
const file = dt.files[0];
我当然不会通过在网上搜索一个例子来解决这个问题,因为我很少在我构建的前端实现拖放。
结论这就是结果。一篇关于使用表单、XHR或拖放上传文件/数据的参考文章。https://www.codeproject.com/Articles/5320154/Client-to-Server-File-Data-Streaming