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

ASP.NET Core 3.1中验证请求体非可空必填属性的问题

解决ASP.NET Core中请求体非可空类型未提供时ModelState不标记无效的问题

我完全理解你的困惑——.NET Core 3.0+的验证逻辑确实有点反直觉:它把非可空参数/属性视为隐含[Required],但实际验证时,因为值类型永远不会为null,就算请求里完全省略这个字段,反序列化后会被赋值为类型默认值(比如int是0),导致[Required]特性根本不会触发错误。

下面提供几种可行的解决方案,既能保持模型类型的准确性(不用改成可空类型),又能在字段未提供时让ModelState标记为无效:

方案1:使用JSON序列化特有的必填特性

这个方案针对JSON请求体,分别适配System.Text.Json和Newtonsoft.Json两种序列化器:

对于System.Text.Json(ASP.NET Core 3.0+默认)

在模型属性上添加[JsonRequired]特性(来自System.Text.Json.Serialization命名空间):

using System.Text.Json.Serialization;

public class NonNullablesRequest
{
    [JsonRequired]
    public int NonNullableInt { get; set; }
}

当请求体中完全省略NonNullableInt字段时,JSON反序列化会直接抛出JsonException,ASP.NET Core会自动将这个异常转化为ModelState错误,返回400 BadRequest,同时会包含类似"Required property 'NonNullableInt' not found in JSON."的错误信息。

对于Newtonsoft.Json(如果项目配置了使用它)

使用[JsonProperty(Required = Required.Always)]特性:

using Newtonsoft.Json;

public class NonNullablesRequest
{
    [JsonProperty(Required = Required.Always)]
    public int NonNullableInt { get; set; }
}

效果和上面一致,反序列化时缺少字段会抛出异常,最终转化为ModelState错误。

方案2:自定义验证属性

如果你希望不依赖序列化器特性,或者需要更灵活的验证逻辑,可以自定义一个验证属性,结合模型绑定的上下文来判断字段是否被提交:

using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
using System.ComponentModel.DataAnnotations;

public class RequiredWhenMissingAttribute : ValidationAttribute, IClientModelValidator
{
    public override bool IsValid(object value)
    {
        // 基础判断:排除默认值
        return value != null && !value.Equals(default);
    }

    public override void Validate(ValidationContext validationContext)
    {
        base.Validate(validationContext);
        
        // 获取模型绑定元数据,判断属性是否在请求中被提交
        var modelMetadata = validationContext.Items["ModelMetadata"] as ModelMetadata;
        if (modelMetadata != null && !modelMetadata.IsBound)
        {
            validationContext.AddValidationError(ErrorMessage ?? $"The field {modelMetadata.DisplayName} is required.");
        }
    }

    public void AddValidation(ClientModelValidationContext context)
    {
        // 可选:添加客户端验证规则
        context.Attributes.Add("data-val", "true");
        context.Attributes.Add("data-val-requiredwhenmissing", ErrorMessage ?? $"The field {context.ModelMetadata.DisplayName} is required.");
    }
}

然后在模型中使用:

public class NonNullablesRequest
{
    [RequiredWhenMissing(ErrorMessage = "NonNullableInt is required.")]
    public int NonNullableInt { get; set; }
}

这个属性会同时检查两个条件:属性值是否为类型默认值,以及该属性是否在请求中被绑定(即是否存在于请求体),如果两者都满足,就标记为验证错误。

方案3:全局配置JSON序列化行为(谨慎使用)

如果你希望所有非可空值类型在未提供时都触发验证错误,可以全局配置JSON序列化器的必填规则:

System.Text.Json全局配置

在Program.cs中修改:

builder.Services.AddControllers()
    .AddJsonOptions(options =>
    {
        options.JsonSerializerOptions.PropertyRequired = JsonRequired.Always;
    });

这个配置会让所有非可空属性都被视为必填,任何未提供的非可空字段都会导致反序列化失败。但要注意,这是全局设置,会影响所有模型,可能不符合某些场景的需求。

补充说明

为什么默认情况下[Required]对非可空值类型无效?

.NET Core 3.0+的验证系统确实会将非可空参数/属性视为隐含[Required],但[Required]特性的核心逻辑是检查值是否为null。对于值类型(比如int、decimal),它们永远不可能为null——就算请求中省略字段,反序列化也会赋值为类型默认值,所以[Required]永远不会触发错误。这就是你遇到的矛盾点:框架认为非可空类型"必填",但验证逻辑又因为值不为null而判定有效。

而上面的解决方案,要么通过序列化器强制检查字段存在性,要么通过自定义验证补充了"字段是否被提交"的判断,完美解决了这个问题,同时保持了模型类型的准确性,不用改成可空类型。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.07 22:52:44