You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 01:42:46