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

ASP.NET Core Web API返回400 Bad Request问题排查求助

问题:Blazor调用ASP.NET Core API返回400 Bad Request,无法触发控制器断点

我们有一个Blazor前端应用,后端是采用C#服务层+仓储模式的ASP.NET Core Web API。向IssueHold控制器发送POST请求时,仅返回400 "Bad Request",API控制器方法的断点完全触发不了,也没法查看发送前的请求内容。使用VS 2022开发,相关代码如下:

初始服务调用

await IssueHoldService.CreateIssueHold(issueHold);

服务层方法

public async Task<Warrant> CreateIssueHold(Warrant warrant)
{   
    var warrantJson = new StringContent(JsonSerializer.Serialize(warrant), Encoding.UTF8, "application/json");
    string x = warrantJson.ToString();

    var response = await _httpClient.PostAsync("api/IssueHold", warrantJson);

    Warrant result = null;

    if (response.IsSuccessStatusCode)
    {
        result = await JsonSerializer.DeserializeAsync<Warrant>(await response.Content.ReadAsStreamAsync());
    }

    return result;
}

返回的响应内容

StatusCode: 400, ReasonPhrase: 'Bad Request', Version: 1.1, Content: System.Net.Http.HttpConnectionResponseContent, Headers:
{
  Transfer-Encoding: chunked
  Server: Microsoft-IIS/10.0
  X-SourceFiles: =?UTF-8?B?QzpcVXNlcnNcMzIzNjA2XHNvdXJjZVxFZ2lkLlJlY29uXEVnaWQuUmVjb24uQXBpXGFwaVxJc3N1ZUhvbGQ=?=
  Persistent-Auth: true
  X-Powered-By: ASP.NET
  Date: Tue, 13 Jun 2023 18:11:39 GMT
  Content-Type: application/problem+json; charset=utf-8
}

API控制器方法

[HttpPost]
public async Task<IActionResult> Create([FromBody] Warrant warrant)
{
    if (warrant == null)
        return BadRequest();

    if (!ModelState.IsValid)
        return BadRequest(ModelState);

    try
    {
        var newId = await _issueHoldRepository.CreateAsync(warrant);
        var createdWarrant = await _issueHoldRepository.GetByIdAsync(newId);
        return Created("warrant", createdWarrant);
    }
    catch
    {
        return BadRequest();
    }
}

仓储层方法

public async Task<int> CreateAsync(Warrant entity)
{
        System.Diagnostics.Debugger.Break();
        string sql = @"INSERT INTO [dbo].[IssueHold]
                           ([CheckNumber]
                           ,[IssueSuffix]
                           ,[ControlNumber]
                           ,[AgencyNumber]
                           ,[Name]
                           ,[SSN]
                           ,[Addr1]
                           ,[Addr2]
                           ,[City]
                           ,[St]
                           ,[Zip]
                           ,[Reason]
                           ,[DateIssued]
                           ,[IssuedBy]
                           ,[AmountPaid]
                           ,[AmountDue]
                           ,[RefundAmount]
                           ,[ClaimNumber]
                           ,[DateMailed]
                           ,[DateCreated]
                           ,[CreatedBy]
                           ,[PrintIssue]
                           ,[PrintAmount]
                           ,[Beneficiary]
                           ,[WarrantType]
                           ,[Void]
                           ,[DateRequested]
                           ,[OrigCheckNum])
                     OUTPUT Inserted.HoldKey
                     VALUES
                           (@CheckNumber
                           , @IssueSuffix
                           , @ControlNumber
                           , @AgencyNumber
                           , @Name
                           , @SSN
                           , @Addr1
                           , @Addr2
                           , @City
                           , @St
                           , @Zip
                           , @Reason
                           , @DateIssued
                           , @IssuedBy
                           , @AmountPaid
                           , @AmountDue
                           , @RefundAmount
                           , @ClaimNumber
                           , @DateMailed
                           , @DateCreated
                           , @CreatedBy
                           , @PrintIssue
                           , @PrintAmount
                           , @Beneficiary
                           , @WarrantType
                           , @Void
                           , @DateRequested
                           , @OrigCheckNum)";
        try
        {
            using (var connection = CreateConnection())
            {
                return (await connection.ExecuteScalarAsync<int>(sql, entity));
            }
        }
        catch (Exception ex)
        {

            throw new Exception(ex.Message, ex);
        }
}

Warrant实体类

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Egid.Recon.Shared.Entities
{
    public class Warrant : EntityBase
    {
        public int HoldKey { get; set; }

        [MaxLength(10)]
        public string CheckNumber { get; set; }

        [MaxLength(2)]
        public string IssueSuffix { get; set; }

        [MaxLength(10)]
        public string ControlNumber { get; set; }

        [MaxLength(10)]
        public string AgencyNumber { get; set; }

        [MaxLength(60)]
        public string Name { get; set; }

        [MaxLength(9)]
        public string SSN { get; set; }

        [MaxLength(50)]
        public string Addr1 { get; set; }

        [MaxLength(50)]
        public string Addr2 { get; set; }

        [MaxLength(30)]
        public string City { get; set; }

        [MaxLength(2)]
        public string St { get; set; }

        [MaxLength(10)]
        public string Zip { get; set; }

        public string Reason { get; set; }

        public DateTime? DateIssued { get; set; }

        [MaxLength(15)]
        public string IssuedBy { get; set; }
        //[RegularExpression(@"^\$?\-?([1-9]{1}[0-9]{0,2}(\,\d{3})*(\.\d{0,2})?|[1-9]{1}\d{0,}(\.\d{0,2})?|0(\.\d{0,2})?(\.\d{1,2}))$|^\-?\$?([1-9]{1}\d{0,2}(\,\d{3})*(\.\d{0,2})?|[1-9]{1}\d{0,}(\.\d{0,2})?|0(\.\d{0,2})?(\.\d{1,2}))$|^\(\$?([1-9]{1}\d{0,2}(\,\d{3})*(\.\d{0,2})?|[1-9]{1}\d{0,}(\.\d{0,2})?|0(\.\d{0,2})?(\.\d{1,2}))\)$", ErrorMessage = "Invalid Amount. Please use the format of XXXX.XX.")]
        public decimal AmountPaid { get; set; }
        public string AmountPaidDisplay
        {
            get { return String.Format("{0:C2}", AmountPaid); }
        }
        //[RegularExpression(@"^\$?\-?([1-9]{1}[0-9]{0,2}(\,\d{3})*(\.\d{0,2})?|[1-9]{1}\d{0,}(\.\d{0,2})?|0(\.\d{0,2})?(\.\d{1,2}))$|^\-?\$?([1-9]{1}\d{0,2}(\,\d{3})*(\.\d{0,2})?|[1-9]{1}\d{0,}(\.\d{0,2})?|0(\.\d{0,2})?(\.\d{1,2}))$|^\(\$?([1-9]{1}\d{0,2}(\,\d{3})*(\.\d{0,2})?|[1-9]{1}\d{0,}(\.\d{0,2})?|0(\.\d{0,2})?(\.\d{1,2}))\)$", ErrorMessage = "Invalid Amount. Please use the format of XXXX.XX.")]
        public decimal AmountDue { get; set; }
        public string AmountDueDisplay
        {
            get { return String.Format("{0:C2}", RefundAmount); }
        }

        public decimal RefundAmount { get; set; }

        public string RefundAmountDisplay
        {
            get { return String.Format("{0:C2}", RefundAmount); }
        }

        [MaxLength(15)]
        public string ClaimNumber { get; set; }

        public DateTime? DateMailed { get; set; }

        public DateTime? DateCreated { get; set; }

        [MaxLength(15)]
        public string CreatedBy { get; set; }

        public bool PrintIssue { get; set; }

        [MaxLength(250)]
        public string PrintAmount { get; set; }

        [MaxLength(40)]
        public string Beneficiary { get; set; }

        [MaxLength(9)]
        public string WarrantType { get; set; }

        public bool Void { get; set; }

        public DateTime? DateRequested { get; set; }

        [MaxLength(9)]
        public string OrigCheckNum { get; set; }
    }
}

已经尝试在API控制器的!ModelState.IsValid分支添加控制台日志,但没有输出。想问下怎么查看实际请求内容找格式问题?还有哪些排查方向?


排查方案

一、查看实际请求内容的方法

  • 在服务层直接输出序列化后的JSON:不要用warrantJson.ToString()(仅返回类型名),直接获取序列化后的字符串并打印:
    var warrantJsonStr = JsonSerializer.Serialize(warrant);
    Console.WriteLine("请求JSON:" + warrantJsonStr);
    var warrantJson = new StringContent(warrantJsonStr, Encoding.UTF8, "application/json");
    
  • 启用API的HTTP日志:在API项目的appsettings.json中添加日志配置,开启ASP.NET Core的调试级日志:
    "Logging": {
      "LogLevel": {
        "Default": "Information",
        "Microsoft.AspNetCore": "Debug"
      }
    }
    
    启动API后,VS输出窗口会显示完整的请求头和请求体。
  • 抓包工具查看请求:
    • Blazor WebAssembly:用浏览器F12打开网络面板,找到目标POST请求,查看Request Payload;
    • Blazor Server:用Fiddler抓本地请求,过滤api/IssueHold地址,查看完整请求详情。

二、其他排查思路

  • 统一序列化配置:ASP.NET Core默认用System.Text.Json的驼峰命名策略,服务层序列化时要保持一致,避免属性名大小写不匹配导致绑定失败:
    var options = new JsonSerializerOptions
    {
      PropertyNamingPolicy = JsonNamingPolicy.CamelCase
    };
    var warrantJsonStr = JsonSerializer.Serialize(warrant, options);
    
  • 捕获模型绑定错误:添加全局过滤器,在模型绑定阶段就捕获错误(因为断点没进控制器,说明绑定在进入方法前就失败了):
    public class ModelValidationFilter : IActionFilter
    {
        public void OnActionExecuting(ActionExecutingContext context)
        {
            if (!context.ModelState.IsValid)
            {
                var errors = context.ModelState.Values.SelectMany(v => v.Errors)
                                                    .Select(e => e.ErrorMessage);
                foreach (var error in errors)
                {
                    Console.WriteLine("模型绑定错误:" + error);
                }
            }
        }
    
        public void OnActionExecuted(ActionExecutedContext context) { }
    }
    
    在Program.cs注册过滤器:
    builder.Services.AddControllers(options =>
    {
        options.Filters.Add<ModelValidationFilter>();
    });
    
  • 检查路由匹配:确认服务层调用的api/IssueHold和控制器路由一致,控制器是否有[Route("api/[controller]")]特性,避免拼写错误。
  • 验证实体类约束:检查实体类的[MaxLength]、[Required]等注解,比如字符串长度是否超限、必填字段是否为空,这些都会触发模型验证失败。
  • 直接测试API:用Postman或Swagger构造JSON请求直接调用API,排除Blazor服务层的问题,确认API本身是否正常。

内容的提问来源于stack exchange,提问作者jthorn

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 21:11:59