C# WebClient上传大文件报“The Stream was too long”问题解决
大文件上传问题:WebClient抛"The Stream was too long",HttpClient上传无法完成
问题背景
上传小于3GB的文件时,使用WebClient抛出The Stream was too long错误;改用HttpClient后,上传始终无法完成。
客户端代码
private async Task UpdateFile(string filePath, string route) { //System.Net.ServicePointManager.DefaultConnectionLimit = 200; System.Net.ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(ValidateServerCertificate); Uri uri = new Uri(_httpClient.BaseAddress, relativeUri: route);//String.Format("{0}/api/TransferFiles/upload?loja={1}&name={2}", receiverUrl, loja, fi.Name)); bool completed = false; using MyWebClient client = new MyWebClient(); client.Headers.Add("Authorization", UserSession.Token); client.UploadFile(uri, filePath); return; } class MyWebClient : WebClient { protected override WebRequest GetWebRequest(Uri address) { WebRequest request = base.GetWebRequest(address); if (request is HttpWebRequest) { (request as HttpWebRequest).KeepAlive = false; ((HttpWebRequest)request).Timeout = System.Threading.Timeout.Infinite; } return request; } }
服务端代码
[RequestSizeLimit(10L * 1024L * 1024L * 1024L)] [RequestFormLimits(MultipartBodyLengthLimit = 10L * 1024L * 1024L * 1024L,ValueCountLimit =int.MaxValue)] [DisableFormValueModelBinding] [HttpPost("Upload/World")] public async Task<IActionResult> UploadFile(IFormFile file) { string token = Request.Headers.FirstOrDefault(x => x.Key == "Authorization").Value; var details = await _authClient.UserDetailsAsync(token); if (!HasPermission(details)) return Unauthorized(); if (file == null || file.Length == 0) { // Handle the case where no file is uploaded return UnprocessableEntity(new MensagemDto("No file selected.")); } if (file.Length > 4L * 1024 * 1024 * 1024) // 4GB Validation { return UnprocessableEntity(new MensagemDto("File can't be 4GB or higher.")); } try { // Specify a directory to save the uploaded file var uploadPath = Path.Combine(DirectoryHelper.GetBaseDirectory(), "uploads"); if (!Directory.Exists(uploadPath)) { Directory.CreateDirectory(uploadPath); } // Generate a unique file name to avoid overwriting existing files var uniqueFileName = Guid.NewGuid().ToString() + "_" + file.FileName+".zip"; // Combine the upload path with the unique file name var filePath = Path.Combine(uploadPath, uniqueFileName); // Copy the file to the specified path using (var stream = new FileStream(filePath, FileMode.Create)) { await file.CopyToAsync(stream); } var service = new SetupService(); service.SetupWorld(details, filePath); return Ok("File uploaded successfully."); } catch (ArgumentException ex) { // Handle any exceptions that occur during file upload return UnprocessableEntity(ex.Message); } catch (Exception ex) { // Handle any exceptions that occur during file upload return StatusCode(500, $"Internal server error: {ex.Message}"); } }
问题原因分析
WebClient报错原因
WebClient底层依赖的HttpWebRequest默认最大请求长度为2GB——因为HttpWebRequest.ContentLength是int类型,最大值为2^31-1字节(约2.1GB),3GB文件超出该限制直接触发"The Stream was too long"错误。
HttpClient上传失败可能原因
- 默认超时时间过短,大文件传输未完成就被中断
- 未采用流式上传,一次性将文件加载到内存导致内存溢出或传输超时
- HttpClient实例配置不合理(如连接池、KeepAlive设置)
解决方案
方案1:WebClient临时修复(仅支持≤2GB文件)
若坚持使用WebClient,可关闭请求流缓存,但无法突破2GB上限:
class MyWebClient : WebClient { protected override WebRequest GetWebRequest(Uri address) { var request = base.GetWebRequest(address) as HttpWebRequest; if (request != null) { request.KeepAlive = false; request.Timeout = System.Threading.Timeout.Infinite; // 关闭请求流缓存,避免内存加载整个文件 request.AllowWriteStreamBuffering = false; } return request; } }
注意:该方案无法支持3GB文件,仅适用于≤2GB场景。
方案2:HttpClient流式上传(推荐3GB场景)
使用HttpClient实现流式上传,避免内存过载,同时配置合理超时:
客户端表单上传代码(匹配服务端IFormFile)
private async Task UpdateFile(string filePath, string route) { System.Net.ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; ServicePointManager.ServerCertificateValidationCallback = ValidateServerCertificate; var uri = new Uri(_httpClient.BaseAddress, route); // 设置足够长的超时时间(根据文件大小调整,示例为30分钟) _httpClient.Timeout = TimeSpan.FromMinutes(30); // 以流式方式读取文件,避免加载到内存 using var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read, 8192, useAsync: true); using var formContent = new MultipartFormDataContent(); var fileContent = new StreamContent(fileStream); fileContent.Headers.ContentType = MediaTypeHeaderValue.Parse("application/octet-stream"); // 参数名"file"需与服务端IFormFile参数名一致 formContent.Add(fileContent, "file", Path.GetFileName(filePath)); // 添加Authorization头 _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", UserSession.Token); var response = await _httpClient.PostAsync(uri, formContent); response.EnsureSuccessStatusCode(); // 自动抛出HTTP错误状态码异常 }
服务端补充配置
在Program.cs中配置Kestrel的请求体大小限制,避免Kestrel提前截断大请求:
builder.WebHost.ConfigureKestrel(options => { options.Limits.MaxRequestBodySize = 10L * 1024 * 1024 * 1024; // 设置为10GB });
额外优化建议
- 客户端启用分块传输:显式设置
fileContent.Headers.ContentLength = new FileInfo(filePath).Length;,HttpClient会自动处理分块传输 - 服务端添加进度反馈:若需要上传进度,可在客户端监听
StreamContent的传输进度,或服务端返回中间状态 - 保持流式写入:服务端继续使用
await file.CopyToAsync(stream),避免一次性读取文件到内存
内容的提问来源于stack exchange,提问作者Matheus Hoske
相关产品推荐
相关产品推荐

