.NET MAUI中RestSharp新版上传文件遇边界错误,求新版兼容方案
我开发了一款对接第三方API的.NET MAUI应用,通过文件选择器获取文件的代码如下:
private async void file_Clicked(object sender, EventArgs e) { var result = await FilePicker.PickAsync(new PickOptions { PickerTitle = "选择上传文件..." }); if (result == null) return; var stream = await result.OpenReadAsync(); searchAndReplaceViewModel.ReplaceFile = stream; searchAndReplaceViewModel.ReplaceFileName = result.FileName; file.Text = result.FileName; file.BackgroundColor = Colors.LightGreen; }
ViewModel中保存文件流后调用上传的代码:
public object ReplaceFile { get; internal set; } // 中间省略大量代码 [RelayCommand] async Task UploadFile(dtoObjectsBase doc) { if (IsLoading) return; try { if(App.SearchAndReplaceService.UploadFile(doc, (BufferedStream)ReplaceFile, ReplaceFileName)) { await Shell.Current.DisplayAlert("成功", "文件上传成功", "确定"); } } catch (Exception ex) { Debug.WriteLine($"文件上传失败: {ex.Message}"); await Shell.Current.DisplayAlert("错误", "文件上传出错", "确定"); } finally { IsLoading = false; } }
对应的RestSharp上传方法:
public Operation<FileUploadResponse> FileUpload(BufferedStream file, Guid guid, string filename) { Operation<FileUploadResponse> fileUpload = new(); FileUpload fileUploadRequest = new() { Guid = guid, RequestStream = file.UnderlyingStream }; try { var options = new RestClientOptions(Settings.Server) { MaxTimeout = -1, }; var client = new RestClient(options); var request = new RestRequest(ApiPaths.FileUpload + "?guid=" + guid.ToString(), Method.Post); request.AddHeader("Content-Type", "multipart/form-data"); //request.AddFile("MyfactoryImport", ); byte[] data = new byte[file.Length]; file.Read(data, 0, (int)file.Length); request.AlwaysMultipartFormData = true; //request.AddFile("RequestStream", data, RestSharp.ContentType.Binary); request.AddFile(filename, data, RestSharp.ContentType.Binary); fileUpload.Value = JsonConvert.DeserializeObject<FileUploadResponse>(client.Execute(request).Content); fileUpload.Success = true; //fileUpload.Value = APICall<FileUpload, FileUploadResponse>(fileUploadRequest, ApiPaths.FileUpload); } catch (Exception ex) { fileUpload.ErrorMsg = "API请求出错: " + ex.Message; } return fileUpload; }
上传时RestSharp返回InternalServerError,API端日志显示:
HandleFileUpload (general) failed for guid 8f5ae2b3-39e0-41b8-bc18-b60c2b75fc26 | (null) | (ID:(null) System:(null))
System.ApplicationException: Cannot perform file upload. Start boundary not found.
用Postman测试上传正常,Postman生成的RestSharp代码如下:
var options = new RestClientOptions("") { MaxTimeout = -1, }; var client = new RestClient(options); var request = new RestRequest("https://servername/instance/services/fileupload?guid=6e813e27-bee8-4359-a046-04437365c903", Method.Post); request.AlwaysMultipartFormData = true; request.AddFile("file", "dOLC4eFA4/temp.pdf"); RestResponse response = await client.ExecuteAsync(request); Console.WriteLine(response.Content);
第三方API文档仅给出以下说明:
POST /server/services/fileupload HTTP/1.1 Host: localhost Accept: application/json Content-Type: application/json Content-Length: length {"Guid":"00000000000000000000000000000000"}
目前我通过降级到RestSharp 106.15.0解决了问题,但希望使用新版本,请问如何在新版RestSharp中解决这个边界错误?
解决方案
问题根源
新版RestSharp中,手动添加Content-Type: multipart/form-data头会覆盖框架自动生成的带边界标识的Content-Type,导致API无法识别请求边界,从而抛出「Start boundary not found」错误。
修复步骤
移除手动添加的Content-Type头
新版RestSharp会在使用AddFile且设置AlwaysMultipartFormData = true时,自动生成包含正确边界的multipart/form-data请求头,无需手动添加。使用正确的
AddFile重载方法
确保指定文件参数的名称(需和API期望的参数名一致,比如Postman中的file),而不是直接用文件名作为参数名。优化文件流处理
避免将流转换为字节数组后再上传,直接使用文件流可以减少内存占用,同时避免流位置错误的问题。
修复后的代码
修改FileUpload方法如下:
public async Task<Operation<FileUploadResponse>> FileUpload(Stream fileStream, Guid guid, string filename) { Operation<FileUploadResponse> fileUpload = new(); try { var options = new RestClientOptions(Settings.Server) { MaxTimeout = -1, }; using var client = new RestClient(options); var request = new RestRequest($"{ApiPaths.FileUpload}?guid={guid}", Method.Post); // 移除手动添加的Content-Type头 // request.AddHeader("Content-Type", "multipart/form-data"); request.AlwaysMultipartFormData = true; // 使用API期望的参数名(比如"file",和Postman一致),传入文件流、文件名和内容类型 request.AddFile("file", () => fileStream, filename, "application/octet-stream"); var response = await client.ExecuteAsync(request); if (response.IsSuccessful) { fileUpload.Value = JsonConvert.DeserializeObject<FileUploadResponse>(response.Content); fileUpload.Success = true; } else { fileUpload.ErrorMsg = $"API请求失败: {response.StatusCode}"; } } catch (Exception ex) { fileUpload.ErrorMsg = "API请求出错: " + ex.Message; } return fileUpload; }
同时修改ViewModel中的调用,直接传入原始流(无需转换为BufferedStream):
[RelayCommand] async Task UploadFile(dtoObjectsBase doc) { if (IsLoading) return; try { var result = await App.SearchAndReplaceService.FileUpload((Stream)ReplaceFile, doc.Guid, ReplaceFileName); if(result.Success) { await Shell.Current.DisplayAlert("成功", "文件上传成功", "确定"); } else { await Shell.Current.DisplayAlert("错误", result.ErrorMsg, "确定"); } } catch (Exception ex) { Debug.WriteLine($"文件上传失败: {ex.Message}"); await Shell.Current.DisplayAlert("错误", "文件上传出错", "确定"); } finally { IsLoading = false; } }
额外说明
- 第三方API文档给出的示例是
Content-Type: application/json,但实际Postman测试用的是multipart/form-data,说明API支持两种上传方式?但你的场景是上传文件,所以用multipart/form-data是正确的。 - 确保文件流的位置在起始处(如果之前读取过流,需要调用
fileStream.Seek(0, SeekOrigin.Begin)重置位置)。
内容的提问来源于stack exchange,提问作者Andre

