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

.NET Core 7.0中Ajax Post提交Action时Model始终为空问题

问题:.NET Core 7.0中Ajax Post提交数据,Controller接收模型始终为null

在.NET Core 7.0开发场景下,通过Ajax Post向Controller的AddOrUpdateAddress Action提交数据时,无论是否添加[FromBody]特性,接收的AddressViewModel始终为null。

控制器Action方法

[HttpPost]
public async Task<JsonResult> AddOrUpdateAddress([FromBody]AddressViewModel model)
{
    var address = _mapper.Map<Address>(model);
    // 其他业务逻辑
}

Ajax请求代码

$.ajax({
    url: "/Company/AddOrUpdateAddress",
    type: "POST",
    async: false,
    contentType: "application/json;",
    data: JSON.stringify(address),
    success: function (response) {
        company.details.address.addEdit.closeDetails();
        $.notify('Saved', { position: "top right", className: "success" });
    },
    error: function (e) {
        $.notify('Error', { position: "top right", className: "error" });
        return false;
    }
});

AddressViewModel模型

public class AddressViewModel
{
    public int Id { get; set; }
    [Display(Name = "Address Type")]
    public int? AddressTypeId { get; set; }
    public AddressType? Type { get; set; }
    public string? Address1 { get; set; }
    public string? Address2 { get; set; }
    public string? City { get; set; }
    [Display(Name = RegistrationTooltip.State)]
    public int? StateId { get; set; }
    public string? StateName { get; set; }
    [Display(Name = RegistrationTooltip.Country)]
    public int CountryId { get; set; }
    [Display(Name = "Zip")]
    public string? Zip { get; set; }
    public string? FullAddress =>
        new Regex("[ ]{2,}", RegexOptions.None).Replace(
            $"{Address1?.Humanize(LetterCasing.Title)}, {Address2?.Humanize(LetterCasing.Title)}<br/> {City?.Humanize(LetterCasing.Title)} {(!StateId.HasValue ? StateName?.Humanize(LetterCasing.Title) : State?.Name?.Humanize(LetterCasing.Title))} - {Zip}<br/> <span><img src=\"/3rdParty/flags/blank.gif\" class=\"flag flag-{Country?.TwoLetterIsoCode}\" alt=\"{Country?.Name?.Humanize(LetterCasing.Title)}\"> {Country?.Name?.Humanize(LetterCasing.Title)}</span>",
            " ");
    public int? CompanyId { get; set; }
    [Display(Name = "Is Default")]
    public bool IsDefault { get; set; }
    [Display(Name = "Primary Hr Contact")]
    public string? PrimaryHrContactUserId { get; set; }
    [Display(Name = "Primary Finance Contact")]
    public string? PrimaryFinanceContactUserId { get; set; }
    [Display(Name = "Primary Facility Contact")]
    public string? PrimaryFacilityContactUserId { get; set; }
    [Display(Name = "Additional Hr Recipients")]
    public string? AdditionalHrRecipients { get; set; }
    [Display(Name = "Additional Finance Recipients")]
    public string? AdditionalFinanceRecipients { get; set; }
    [Display(Name = "Additional Facility Recipients")]
    public string? AdditionalFacilityRecipients { get; set; }
    public State? State { get; set; }
    public Country? Country { get; set; }
    public List<ShiftViewModel>? Shifts { get; set; }
    public List<OperatingHourViewModel>? OperatingHours { get; set; }
    public UserLiteDTO ? PrimaryHrContactUser { get; set; }
    public UserLiteDTO? PrimaryFinanceContactUser { get; set; }
    public UserLiteDTO? PrimaryFacilityContactUser { get; set; }
    public bool IsDeleted { get; set; }
}

提交的JSON数据

{
    "CountryId": "2",
    "AddressTypeId": "9",
    "Address1": "7th Floor, South Wing, ",
    "Address2": "Krishe Sapphire",
    "Zip": "500081",
    "StateId": "92",
    "City": "Hyderabad",
    "StateName": "",
    "Id": "607",
    "PrimaryHrContactUserId": "",
    "PrimaryFinanceContactUserId": "",
    "PrimaryFacilityContactUserId": "",
    "IsDefault": false,
    "IsDeleted": false,
    "OperatingHours": [
        {
            "Id": 0,
            "Day": "Sunday",
            "OpeningTime": "",
            "ClosingTime": "",
            "IsClosed": true,
            "CompanyId":1,
            "AddressId":1
        },
        {
            "Id": 0,
            "Day": "Monday",
            "OpeningTime": "11:00 AM",
            "ClosingTime": "4:30 PM",
            "IsClosed": false,
            "CompanyId":1,
            "AddressId":1
        },
        {
            "Id": 0,
            "Day": "Tuesday",
            "OpeningTime": "",
            "ClosingTime": "",
            "IsClosed": false,
            "CompanyId":1,
            "AddressId":1
        },
        {
            "Id": 0,
            "Day": "Wednesday",
            "OpeningTime": "",
            "ClosingTime": "",
            "IsClosed": false,
            "CompanyId":1,
            "AddressId":1
        },
        {
            "Id": 0,
            "Day": "Thursday",
            "OpeningTime": "",
            "ClosingTime": "",
            "IsClosed": false,
            "CompanyId":1,
            "AddressId":1
        },
        {
            "Id": 0,
            "Day": "Friday",
            "OpeningTime": "",
            "ClosingTime": "",
            "IsClosed": false,
            "CompanyId":1,
            "AddressId":1
        },
        {
            "Id": 0,
            "Day": "Saturday",
            "OpeningTime": "",
            "ClosingTime": "",
            "IsClosed": false,
            "CompanyId":1,
            "AddressId":1
        }
    ],
    "Shifts": [
        {
            "Id": "0",
            "ShiftName": "Regular",
            "OpeningTime": "",
            "ClosingTime": "",
            "IsActive": false,
            "CompanyId": 1,
            "AddressId":1
        },
        {
            "Id": "0",
            "ShiftName": "Morning",
            "OpeningTime": "",
            "ClosingTime": "",
            "IsActive": false,
            "CompanyId": 1,
            "AddressId":1
        },
        {
            "Id": "0",
            "ShiftName": "Night",
            "OpeningTime": "",
            "ClosingTime": "",
            "IsActive": false,
            "CompanyId": 1,
            "AddressId":1
        }
    ]
}

复现步骤

  • 在Visual Studio中运行上述代码
  • 使用PostMan或其他REST工具,以上述JSON为请求体发起HTTPS Post请求

解决方案

1. 修复JSON数据类型不匹配问题

提交的JSON中多个数值类型字段被序列化为字符串(如"CountryId": "2"、"AddressTypeId": "9"),但ViewModel中这些字段是int/int?类型,.NET Core默认JSON序列化器不会自动转换字符串到数值,导致模型绑定失败。

处理方式:

  • 前端传递JSON时,将数值类型字段改为数值格式,比如"CountryId": 2、"AddressTypeId":9;
  • 或在ViewModel的数值字段上添加[JsonConverter(typeof(JsonStringEnumConverter))]特性(需引用System.Text.Json.Serialization命名空间),允许序列化器转换字符串到数值。

2. 修正Ajax的contentType参数

Ajax代码中contentType: "application/json;"多了一个分号,正确格式应为contentType: "application/json",格式错误会影响Content-Type头的识别,导致模型绑定失败。

修改后的Ajax代码:

$.ajax({
    url: "/Company/AddOrUpdateAddress",
    type: "POST",
    async: false,
    contentType: "application/json",
    data: JSON.stringify(address),
    success: function (response) {
        company.details.address.addEdit.closeDetails();
        $.notify('Saved', { position: "top right", className: "success" });
    },
    error: function (e) {
        $.notify('Error', { position: "top right", className: "error" });
        return false;
    }
});

3. 检查嵌套模型的可访问性

确保ShiftViewModel、OperatingHourViewModel等嵌套模型的属性为public,且有无参数构造函数,否则模型绑定无法实例化这些嵌套对象。

4. 启用模型绑定错误日志排查

在Program.cs中添加配置,开启详细模型绑定错误日志:

builder.Services.AddControllersWithViews()
    .AddJsonOptions(options =>
    {
        options.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
    })
    .AddMvcOptions(options =>
    {
        options.ModelBindingMessageProvider.SetValueMustNotBeNullAccessor(
            _ => "该字段为必填项");
    });

同时在Action中添加模型验证,输出错误信息:

[HttpPost]
public async Task<JsonResult> AddOrUpdateAddress([FromBody]AddressViewModel model)
{
    if (!ModelState.IsValid)
    {
        var errors = ModelState.Values.SelectMany(v => v.Errors)
                                      .Select(e => e.ErrorMessage);
        return Json(new { success = false, errors = errors });
    }
    var address = _mapper.Map<Address>(model);
    // 其他业务逻辑
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 01:23:06