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

向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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 19:23:14