向API端点发送HTTP PUT JSON字符串遇400错误,求解决
向API发送PUT请求时持续收到400错误
错误响应
{StatusCode: 400, ReasonPhrase: 'Bad Request', Version: 1.1, Content: System.Net.Http.HttpConnectionResponseContent, Headers: { Date: Thu, 27 Jul 2023 02:41:07 GMT Server: Kestrel Transfer-Encoding: chunked Content-Type: text/plain; charset=utf-8 }}
API服务器代码
app.MapPut("UpdateFruits", async (string message, IHubContext<ChatHub> context, DatabaseContext dbContext) => { //message(Param) = { "ID":"2","DisplayName":"Fruit","Leaf":null,"State":"Chopped"} try { // Convert Json to Object... TODO var fruit = dbContext.Set<Fruit>().Single(p => p.ID == 1); fruit.State = State.Chopped; fruit.DisplayName = "test"; var saved = false; while (!saved) { try { // Attempt to save changes to the database dbContext.SaveChanges(); saved = true; } catch (DbUpdateConcurrencyException ex) { foreach (var entry in ex.Entries) { if (entry.Entity is Fruit) { var proposedValues = entry.CurrentValues; var databaseValues = entry.GetDatabaseValues(); foreach (var property in proposedValues.Properties) { var proposedValue = proposedValues[property]; var databaseValue = databaseValues[property]; // TODO: decide which value should be written to database // proposedValues[property] = <value to be saved>; } // Refresh original values to bypass next concurrency check entry.OriginalValues.SetValues(databaseValues); } else { throw new NotSupportedException( "Don't know how to handle concurrency conflicts for " + entry.Metadata.Name); } } } } var jsonString = JsonConvert.SerializeObject(fruit); await context.Clients.All.SendAsync("ReceiveMessage", "FromServer", jsonString); } catch (Exception) { } return Results.NoContent(); });
WPF客户端代码
private async void sendMessage_Click(object sender, RoutedEventArgs e) { try { var client = new HttpClient(); client.BaseAddress = new Uri("https://localhost:7036"); var json = System.Text.Json.JsonSerializer.Serialize("{ \"ID\":\"2\",\"DisplayName\":\"Fruit\",\"Leaf\":null,\"State\":\"Chopped\"}"); var content = new StringContent(json, Encoding.UTF8, "application/json"); var result = await client.PutAsync("/UpdateFruits", content); } catch (System.Exception ex) { messages.Items.Add(ex.Message); } }
问题分析与修复方案
核心问题1:客户端序列化逻辑错误
你把已经是JSON格式的字符串再次进行序列化,导致最终发送的内容是转义后的嵌套字符串,比如原JSON会被转成"\"{ \\\"ID\\\":\\\"2\\\"...}\"",服务器无法解析为正常的参数。
核心问题2:API参数绑定规则不匹配
ASP.NET Core默认不会把JSON请求体绑定到普通字符串参数上,需要明确指定绑定来源。
修复步骤
步骤1:修正客户端序列化逻辑
推荐两种方式:
方式一:直接使用原始JSON字符串
private async void sendMessage_Click(object sender, RoutedEventArgs e) { try { var client = new HttpClient(); client.BaseAddress = new Uri("https://localhost:7036"); // 直接使用原始JSON,无需再次序列化 var json = "{ \"ID\":\"2\",\"DisplayName\":\"Fruit\",\"Leaf\":null,\"State\":\"Chopped\"}"; var content = new StringContent(json, Encoding.UTF8, "application/json"); var result = await client.PutAsync("/UpdateFruits", content); // 新增:读取错误详情,方便调试 if (!result.IsSuccessStatusCode) { var errorMsg = await result.Content.ReadAsStringAsync(); messages.Items.Add($"请求失败:{result.StatusCode},详情:{errorMsg}"); } } catch (System.Exception ex) { messages.Items.Add(ex.Message); } }方式二:用实体类序列化(更规范)
先定义客户端实体类:public class FruitDto { public string ID { get; set; } public string DisplayName { get; set; } public object Leaf { get; set; } public string State { get; set; } }再修改客户端代码:
private async void sendMessage_Click(object sender, RoutedEventArgs e) { try { var client = new HttpClient(); client.BaseAddress = new Uri("https://localhost:7036"); var fruit = new FruitDto { ID = "2", DisplayName = "Fruit", Leaf = null, State = "Chopped" }; // 序列化实体对象为标准JSON var json = System.Text.Json.JsonSerializer.Serialize(fruit); var content = new StringContent(json, Encoding.UTF8, "application/json"); var result = await client.PutAsync("/UpdateFruits", content); if (!result.IsSuccessStatusCode) { var errorMsg = await result.Content.ReadAsStringAsync(); messages.Items.Add($"请求失败:{result.StatusCode},详情:{errorMsg}"); } } catch (System.Exception ex) { messages.Items.Add(ex.Message); } }
步骤2:修正API参数绑定
给message参数添加[FromBody]特性,告诉ASP.NET Core从请求体中读取该参数:
app.MapPut("UpdateFruits", async ([FromBody] string message, IHubContext<ChatHub> context, DatabaseContext dbContext) => { // 原有逻辑保持不变 });
更推荐的优化:直接绑定到实体类
避免手动解析JSON,直接绑定到Fruit类,代码更简洁:
app.MapPut("UpdateFruits", async ([FromBody] Fruit fruitRequest, IHubContext<ChatHub> context, DatabaseContext dbContext) => { try { var fruit = dbContext.Set<Fruit>().Single(p => p.ID == fruitRequest.ID); fruit.State = Enum.Parse<State>(fruitRequest.State); fruit.DisplayName = fruitRequest.DisplayName; fruit.Leaf = fruitRequest.Leaf; var saved = false; while (!saved) { try { dbContext.SaveChanges(); saved = true; } catch (DbUpdateConcurrencyException ex) { foreach (var entry in ex.Entries) { if (entry.Entity is Fruit) { var proposedValues = entry.CurrentValues; var databaseValues = entry.GetDatabaseValues(); foreach (var property in proposedValues.Properties) { // 根据业务逻辑决定值的保留规则,这里用请求值覆盖 proposedValues[property] = proposedValues[property]; } entry.OriginalValues.SetValues(databaseValues); } else { throw new NotSupportedException("无法处理" + entry.Metadata.Name + "的并发冲突"); } } } } var jsonString = JsonConvert.SerializeObject(fruit); await context.Clients.All.SendAsync("ReceiveMessage", "FromServer", jsonString); } catch (Exception) { // 建议添加日志记录,方便排查异常 } return Results.NoContent(); });
额外调试建议
- 客户端新增响应内容读取,能直接获取服务器返回的具体错误信息,快速定位问题。
- API的异常捕获块不要吞掉所有异常,添加日志记录,避免排查无门。
内容的提问来源于stack exchange,提问作者Dev
相关产品推荐
相关产品推荐

