通过 API POST 端点处理 asp.net 核心 MVC 中未完成的文件上传
Handle uncompleted file upload by API POST endpoint in asp.net core MVC
为了简化问题,假设我有一个简单的 asp.net mvc 端点,它接收一个文件。在大多数情况下,它将是 .jpg
个:
[HttpPost]
[Route("appraisal/{appraisalID}/files/{fileSubjectCode}")]
[ProducesResponseType(StatusCodes.Status201Created, Type = typeof(IEnumerable<AppraisalFileModel>))]
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(ModelStateDictionary))]
public async Task<IActionResult> UploadAppraisalFile(int appraisalID, string fileSubjectCode, [FromForm]IFormFile file)
{
file = file ?? Request.Form.Files?.FirstOrDefault();
// intermitent code change to investigate and validate Complete File Size and Actual File Size
var completeFileSizeHeader = Request.Headers["Complete-File-Size"];
int.TryParse(completeFileSizeHeader, out int completeFileSize);
if (file == null || file.Length != completeFileSize)
{
using (var stream = new MemoryStream())
{
await file.CopyToAsync(stream);
stream.Position = 0;
var inputAsString = Convert.ToBase64String(stream.ToArray());
Logger.LogDebug("Complete-File-Size header doesn't much received byteArray size", file.Length, completeFileSize, inputAsString);
}
return StatusCode(StatusCodes.Status411LengthRequired, "Complete-File-Size header doesn't much received byteArray size");
}
// some other logic..
}
我正在尝试防止有人对我的 API UploadAppraisalFile
端点执行 POST 请求并突然失去互联网连接导致发送请求的边缘情况没有完整的文件内容。
我的想法是在文件上传时计算文件大小,将有关文件大小的信息添加为额外的 HTTP-HEADER(我称之为 Complete-File-Size
),然后当请求到达后端时,计算接收到的文件大小是否与 Complete-File-Size
.
完全相同
为了产生这样的 issue/edge 案例,我尝试了:
- 正在上传一个大文件(大约 16MB)然后在提交 HTML 表单后突然关闭浏览器 window.
- 上传文件,然后在 Chrome 浏览器的“网络”窗格中,将上传速度更改为最低,然后提交表单,然后立即关闭浏览器。
当我 运行 调试模式时,在每种情况下我都发现: UploadAppraisalFile
端点从未到达,或者如果到达则总是发送完整文件。对于第二个成功案例,为了 100% 确定我将接收到的文件转换为 base64 字符串,然后我检查了文件在 https://codebeautify.org/base64-to-image-converter.
中的样子
我的问题是:发送的 POST 请求是否有可能由于发送过程中突然断网而中断且包含不完整的文件内容?如果是,那么产生问题的最佳方法是什么。干杯
您可以将 HttpContext.RequestAborted
作为 CancellationToken
传递给 "some other logic"
部分中 .NET 提供的所有异步方法。
让我们以您提供的代码为例:
await stream.CopyToAsync(memoryStream, HttpContext.RequestAborted)
我无权访问完整方法,但我假设您将其保存到某个 blob 存储或文件系统。大多数这些持久性 API 接受 CancellationToken
作为参数。
接收到不完整的文件
我能够使用此代码和 Postman 获得“部分”文件。它基本上会从响应流中读取块,直到连接中断。
一旦我关闭 Postman window TaskCancelledException
就会被引发并且流被关闭。
[HttpPost]
public async Task<IActionResult> UploadAppraisalFile([FromForm] IFormFile file)
{
var appraisalfile = file ?? Request.Form.Files.FirstOrDefault();
if (appraisalfile != null)
{
var buffer = ArrayPool<byte>.Shared.Rent(1024);
using var stream = appraisalfile.OpenReadStream();
while (await stream.ReadAsync(buffer, 0, buffer.Length, HttpContext.RequestAborted) > 0)
{
// Do something with buffer
_logger.LogInformation("Total length (bytes) {0}, partial length (bytes) {1}", stream.Length, stream.Position);
}
ArrayPool<byte>.Shared.Return(buffer);
}
return Ok();
}
为了简化问题,假设我有一个简单的 asp.net mvc 端点,它接收一个文件。在大多数情况下,它将是 .jpg
个:
[HttpPost]
[Route("appraisal/{appraisalID}/files/{fileSubjectCode}")]
[ProducesResponseType(StatusCodes.Status201Created, Type = typeof(IEnumerable<AppraisalFileModel>))]
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(ModelStateDictionary))]
public async Task<IActionResult> UploadAppraisalFile(int appraisalID, string fileSubjectCode, [FromForm]IFormFile file)
{
file = file ?? Request.Form.Files?.FirstOrDefault();
// intermitent code change to investigate and validate Complete File Size and Actual File Size
var completeFileSizeHeader = Request.Headers["Complete-File-Size"];
int.TryParse(completeFileSizeHeader, out int completeFileSize);
if (file == null || file.Length != completeFileSize)
{
using (var stream = new MemoryStream())
{
await file.CopyToAsync(stream);
stream.Position = 0;
var inputAsString = Convert.ToBase64String(stream.ToArray());
Logger.LogDebug("Complete-File-Size header doesn't much received byteArray size", file.Length, completeFileSize, inputAsString);
}
return StatusCode(StatusCodes.Status411LengthRequired, "Complete-File-Size header doesn't much received byteArray size");
}
// some other logic..
}
我正在尝试防止有人对我的 API UploadAppraisalFile
端点执行 POST 请求并突然失去互联网连接导致发送请求的边缘情况没有完整的文件内容。
我的想法是在文件上传时计算文件大小,将有关文件大小的信息添加为额外的 HTTP-HEADER(我称之为 Complete-File-Size
),然后当请求到达后端时,计算接收到的文件大小是否与 Complete-File-Size
.
为了产生这样的 issue/edge 案例,我尝试了:
- 正在上传一个大文件(大约 16MB)然后在提交 HTML 表单后突然关闭浏览器 window.
- 上传文件,然后在 Chrome 浏览器的“网络”窗格中,将上传速度更改为最低,然后提交表单,然后立即关闭浏览器。
当我 运行 调试模式时,在每种情况下我都发现: UploadAppraisalFile
端点从未到达,或者如果到达则总是发送完整文件。对于第二个成功案例,为了 100% 确定我将接收到的文件转换为 base64 字符串,然后我检查了文件在 https://codebeautify.org/base64-to-image-converter.
我的问题是:发送的 POST 请求是否有可能由于发送过程中突然断网而中断且包含不完整的文件内容?如果是,那么产生问题的最佳方法是什么。干杯
您可以将 HttpContext.RequestAborted
作为 CancellationToken
传递给 "some other logic"
部分中 .NET 提供的所有异步方法。
让我们以您提供的代码为例:
await stream.CopyToAsync(memoryStream, HttpContext.RequestAborted)
我无权访问完整方法,但我假设您将其保存到某个 blob 存储或文件系统。大多数这些持久性 API 接受 CancellationToken
作为参数。
接收到不完整的文件
我能够使用此代码和 Postman 获得“部分”文件。它基本上会从响应流中读取块,直到连接中断。
一旦我关闭 Postman window TaskCancelledException
就会被引发并且流被关闭。
[HttpPost]
public async Task<IActionResult> UploadAppraisalFile([FromForm] IFormFile file)
{
var appraisalfile = file ?? Request.Form.Files.FirstOrDefault();
if (appraisalfile != null)
{
var buffer = ArrayPool<byte>.Shared.Rent(1024);
using var stream = appraisalfile.OpenReadStream();
while (await stream.ReadAsync(buffer, 0, buffer.Length, HttpContext.RequestAborted) > 0)
{
// Do something with buffer
_logger.LogInformation("Total length (bytes) {0}, partial length (bytes) {1}", stream.Length, stream.Position);
}
ArrayPool<byte>.Shared.Return(buffer);
}
return Ok();
}