基于表单的 WebApi 文件上传服务端与客户端
简介 之前有写过 HttpClient 上传文件 ,只是作为客户端使用,现在提供服务端演示,封装完善函数。 WebApi 下载文件参考:WebApi 下载文件 。
代码 服务端代码 服务端代码因项目而异,这段代码仅为示例。1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 [HttpPost ] public async Task<ActionResult> UploadFile ([FromForm] IFormCollection formData ) { try { var vKeys = formData.Keys; foreach (var key in vKeys) { Console.WriteLine($"Text {key} :{formData[key]} " ); } var vFiles = formData.Files; foreach (var file in vFiles) { var vFileCompletePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, file.FileName); Console.WriteLine($"File {file.Name} :{file.FileName} " ); using var stream = new FileStream(vFileCompletePath, FileMode.Create); await file.CopyToAsync(stream); } return Ok(); } catch (Exception ex) { return BadRequest($"Execute error! {ex.Message} " ); } }
帮助类代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 public static async Task <T ?> UploadPostAsync <T >(Uri baseAddress, string ? requestUrl, IDictionary<string , object >? parameters, IDictionary<string , string >? parameterFiles, Action<object ?, HttpProgressEventArgs>? progressAction = null , string ? token = null ) { ProgressMessageHandler progress = new ProgressMessageHandler(); progress.HttpSendProgress += (s, e) => { progressAction?.Invoke(s, e); }; var vHttpClient = HttpClientFactory.Create(progress); vHttpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json" )); List<FileStream> files = new (); List<StreamContent> streams = new (); try { if (!string .IsNullOrEmpty(token)) { vHttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer" , token); } vHttpClient.Timeout = TimeSpan.FromMinutes(20 ); var vBuilder = new StringBuilder(); vBuilder.Append(baseAddress); vBuilder.Append(requestUrl); var vMultipartForm = new MultipartFormDataContent(); if (parameters != null && parameters.Count >= 1 ) { foreach (var parameter in parameters) { vMultipartForm.Add(new StringContent(parameter.Value?.ToString() ?? string .Empty), parameter.Key); } } if (parameterFiles != null && parameterFiles.Count >= 1 ) { foreach (var parameterFile in parameterFiles) { string filePath = parameterFile.Value; var fileName = Path.GetFileName(filePath); var fileStream = File.OpenRead(filePath); files.Add(fileStream); var streamContent = new StreamContent(fileStream); streams.Add(streamContent); vMultipartForm.Add(streamContent, parameterFile.Key, fileName); } } using var vResponse = await vHttpClient.PostAsync(vBuilder.ToString(), vMultipartForm); if (vResponse.StatusCode == HttpStatusCode.OK) { return typeof (T).FullName.Equals("System.String" ) || typeof (T).FullName.Equals("System.Boolean" ) ? (T)Convert.ChangeType(await vResponse.Content.ReadAsStringAsync(), typeof (T)) : JsonConvert.DeserializeObject<T>(await vResponse.Content.ReadAsStringAsync()); } else { return default ; } } catch (Exception) { return default ; } finally { foreach (var item in files) { item.Dispose(); } files.Clear(); foreach (var item in streams) { item.Dispose(); } streams.Clear(); vHttpClient.Dispose(); vHttpClient = null ; GC.Collect(); } }
调用上传 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 string serverUrl = "https://localhost:7252/" ;string requestUrl = "api/File/UploadFile" ;var vParameters = new Dictionary<string , object >{ { "id" , 1 }, { "name" , "test" } }; var vParameterFiles = new Dictionary<string , string >{ { "file1" , @"1.png" }, { "file2" , @"2.png" } }; return await HttpClientHelper.UploadPostAsync<bool >(new Uri(serverUrl), requestUrl, vParameters, vParameterFiles, null , null );
注意事项 报错请求正文太大,最大请求正文大小为30000000字节 1 2 // 错误信息 Failed to read the request form. Request body too large. The max request body size is 30000000 bytes.
需要修改服务端 WebApi。
通过在 API 函数上增加属性实现,以下二选一 1 2 3 4 [DisableRequestSizeLimit ] [RequestSizeLimit(200_000_000) ]
通过在 Program.cs 中添加全局配置(Net6) 1 2 3 4 5 6 builder.WebHost.ConfigureKestrel((context, options) => { options.Limits.MaxRequestBodySize = 200 _000_000; });
报错超过了正文长度限制 134217728 1 2 // 错误信息 Failed to read the request form. Multipart body length limit 134217728 exceeded.
需要修改服务端 WebApi。
通过在 API 函数上增加属性实现 1 2 [RequestFormLimits(MultipartBodyLengthLimit = 1_000_000_000) ]
通过在 Program.cs 中添加全局配置(Net6) 1 2 3 4 5 6 builder.Services.Configure<FormOptions>(options => { options.MultipartBodyLengthLimit = 1 _000_000_000; });
经过以上两项修改,可能还会在 API 接口中抛异常超过了主体长度限制 16384 1 2 // 错误信息 System.IO.InvalidDataException:“Multipart body length limit 16384 exceeded .”
需要修改服务端 WebApi。1 2 3 4 5 6 7 8 9 10 11 public ActionResult<bool > UploadFile ( ) { var vFormData = Request.Form; } public ActionResult<bool > UploadFile ([FromForm] IFormCollection formData ) { var vFormData = formData; }