ASP.NET Core 7上传Multipart文件时取消令牌无法传递至控制器
ASP.NET Core 7 Multipart文件上传时请求取消无法传递到端点的问题
问题重现
我有一个以Windows服务部署的ASP.NET Core控制台客户端,向远程ASP.NET Core服务器上传文件。客户端在发起请求3秒后触发取消,但服务器控制器动作中的CancellationToken并未检测到取消信号,导致文件仍被持续处理。
服务器接收端点代码:
[HttpPost] [MultipartFormData] [DisableFormValueModelBinding] [RequestSizeLimit(524_288_000)] public async Task<IActionResult> Upload(CancellationToken cancellationToken) { if (Request.ContentType is null) { return BadRequest("Invalid content type"); } var fileUploadResultCode = await _fileService.UploadFileAsync(HttpContext.Request.Body, Request.ContentType, cancellationToken); return CreatedAtAction(nameof(Upload), fileUploadResultCode); }
客户端上传代码:
public async Task<int> Upload(string file) { string fileName = Path.GetFileName(file); await using FileStream fileStream = File.OpenRead(file); using MultipartFormDataContent content = new() { { new StringContent("string1"), "string1" }, { new StringContent("string2"), "string2" }, { new StreamContent(fileStream), "file", fileName } }; // 自动取消请求以复现问题 CancellationTokenSource cts = new(); try { cts.CancelAfter(3000); using HttpResponseMessage response = await _httpClient.PostAsync("api/filetransfer", content, cts.Token); response.EnsureSuccessStatusCode(); return await response.Content.ReadFromJsonAsync<int>(cancellationToken: cts.Token); } catch (Exception) { throw; } }
关键现象:
- 移除
MultipartFormDataContent中的文件流项后,取消令牌能正常传递到服务器控制器 - 替换服务器代码为检测取消的
while循环后,Postman上传Multipart文件也出现同样问题,确认是ASP.NET Core 7的Multipart处理存在框架级缺陷
原因分析
这是ASP.NET Core 7的已知问题:Multipart请求解析阶段,框架中间件未正确将请求取消信号关联到控制器的CancellationToken,且读取请求流时未监听HttpContext.RequestAborted令牌(该令牌直接绑定请求生命周期,客户端断开/取消时会触发),导致即使客户端终止请求,服务器仍会继续读取并处理文件流。
解决方案
方案1:合并请求中止令牌(快速修复)
在服务层的文件处理逻辑中,将传入的CancellationToken与HttpContext.RequestAborted令牌合并,确保任何一方触发取消都能终止处理:
public async Task<int> UploadFileAsync(Stream requestBody, string contentType, CancellationToken cancellationToken, HttpContext httpContext) { // 合并两个取消信号:控制器令牌 + 请求中止令牌 using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource( cancellationToken, httpContext.RequestAborted); var combinedToken = linkedCts.Token; // 处理流时每一步检查取消 byte[] buffer = new byte[4096]; int bytesRead; while ((bytesRead = await requestBody.ReadAsync(buffer, combinedToken)) > 0) { combinedToken.ThrowIfCancellationRequested(); // ... 你的文件写入/处理逻辑 } return /* 返回结果 */; }
方案2:升级到ASP.NET Core 8(彻底解决)
官方在.NET 8中修复了Multipart请求的取消处理逻辑,优化了令牌传递机制,升级框架版本后该问题会自动消失。
方案3:自定义Multipart解析中间件(无法升级时使用)
自定义中间件包装请求流,在读取过程中主动监听请求取消信号:
public class MultipartCancellationMiddleware { private readonly RequestDelegate _next; public MultipartCancellationMiddleware(RequestDelegate next) { _next = next; } public async Task InvokeAsync(HttpContext context) { if (context.Request.ContentType?.StartsWith("multipart/form-data") == true) { context.Request.Body = new CancellableStream(context.Request.Body, context.RequestAborted); } await _next(context); } } public class CancellableStream : Stream { private readonly Stream _innerStream; private readonly CancellationToken _cancelToken; public CancellableStream(Stream innerStream, CancellationToken cancelToken) { _innerStream = innerStream; _cancelToken = cancelToken; } public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _cancelToken); return await _innerStream.ReadAsync(buffer, offset, count, linkedCts.Token); } // 以下委托给内部流实现 public override bool CanRead => _innerStream.CanRead; public override bool CanSeek => _innerStream.CanSeek; public override bool CanWrite => _innerStream.CanWrite; public override long Length => _innerStream.Length; public override long Position { get => _innerStream.Position; set => _innerStream.Position = value; } public override void Flush() => _innerStream.Flush(); public override int Read(byte[] buffer, int offset, int count) => _innerStream.Read(buffer, offset, count); public override long Seek(long offset, SeekOrigin origin) => _innerStream.Seek(offset, origin); public override void SetLength(long value) => _innerStream.SetLength(value); public override void Write(byte[] buffer, int offset, int count) => _innerStream.Write(buffer, offset, count); }
在Program.cs中注册中间件:
app.UseMiddleware<MultipartCancellationMiddleware>();
总结
该问题属于ASP.NET Core 7的框架缺陷,最彻底的解决方式是升级到.NET 8;若暂时无法升级,通过合并RequestAborted令牌或自定义中间件包装请求流,可实现请求取消信号的正确传递。
内容的提问来源于stack exchange,提问作者Fabius
相关产品推荐
相关产品推荐

