.NET Framework4.8中Newtonsoft.Json实现属性条件必填的方案
现有如下类定义:
public class Foo { [JsonProperty] public int Number { get; set; } [JsonProperty] public string Text { get; set; } }
需要为Text属性实现条件必填逻辑:当Number > 10时,JSON字符串中必须包含Text属性;当Number ≤ 10时,该属性可省略。无法直接使用[JsonProperty(Required = Required.Always)]这种非条件的必填属性配置。合法与不合法示例如下:
{ "Number": 5 } // 合法 { "Number": 20 } // 不合法 { "Number": 20, "Text": "Bar" } // 合法
项目基于.NET Framework v4.8,使用Newtonsoft.Json,API部署在IIS上,通过中间件完成序列化,未手动调用JsonConvert.Deserialize<T>方法,请问如何实现该条件必填校验?
针对你的场景,有三种实用方案可以实现条件必填校验,适配.NET Framework 4.8 + Newtonsoft.Json的Web API环境:
方案一:实现IValidatableObject做模型级验证
这是最直接的方案,利用ASP.NET Web API内置的模型验证机制,无需修改Json序列化配置:
using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using Newtonsoft.Json; public class Foo : IValidatableObject { [JsonProperty] public int Number { get; set; } [JsonProperty] public string Text { get; set; } public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) { if (Number > 10 && string.IsNullOrEmpty(Text)) { yield return new ValidationResult( "当Number大于10时,Text属性为必填项", new[] { nameof(Text) }); } } }
Web API会自动在模型绑定完成后触发验证,若校验失败,会返回400 Bad Request响应,包含对应的错误信息。
方案二:自定义ContractResolver在反序列化阶段校验
如果需要在Json反序列化过程中就拦截非法数据(更早触发校验),可以自定义契约解析器:
using Newtonsoft.Json; using Newtonsoft.Json.Serialization; using System.Reflection; public class ConditionalRequiredContractResolver : DefaultContractResolver { protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) { JsonProperty property = base.CreateProperty(member, memberSerialization); // 针对Foo类的Text属性设置条件规则 if (member.DeclaringType == typeof(Foo) && member.Name == nameof(Foo.Text)) { property.Required = Required.Default; // 控制是否需要反序列化该属性 property.ShouldDeserialize = instance => { var foo = instance as Foo; return foo == null || foo.Number <= 10 || !string.IsNullOrEmpty(foo.Text); }; // 校验赋值过程,确保Number>10时Text不为空 property.ValueProvider = new ConditionalRequiredValueProvider(property.ValueProvider); } return property; } } public class ConditionalRequiredValueProvider : IValueProvider { private readonly IValueProvider _originalProvider; public ConditionalRequiredValueProvider(IValueProvider originalProvider) { _originalProvider = originalProvider; } public object GetValue(object target) => _originalProvider.GetValue(target); public void SetValue(object target, object value) { var foo = target as Foo; if (foo != null && foo.Number > 10 && (value == null || string.IsNullOrWhiteSpace(value.ToString()))) { throw new JsonSerializationException("当Number大于10时,Text属性不能为空"); } _originalProvider.SetValue(target, value); } }
然后在App_Start/WebApiConfig.cs中全局注册这个契约解析器:
using Newtonsoft.Json; using System.Web.Http; public static class WebApiConfig { public static void Register(HttpConfiguration config) { // 其他路由/配置... config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new ConditionalRequiredContractResolver(); } }
反序列化时若不符合规则,会抛出JsonSerializationException,可通过全局异常过滤器统一处理这类异常并返回友好响应。
方案三:自定义可复用的RequiredIfAttribute
如果需要在多个模型中复用条件必填逻辑,可以自定义一个验证特性:
using System.ComponentModel.DataAnnotations; using System.Reflection; public class RequiredIfAttribute : ValidationAttribute { private readonly string _dependentProperty; private readonly int _threshold; public RequiredIfAttribute(string dependentProperty, int threshold) { _dependentProperty = dependentProperty; _threshold = threshold; } protected override ValidationResult IsValid(object value, ValidationContext validationContext) { var dependentProp = validationContext.ObjectType.GetProperty(_dependentProperty); if (dependentProp == null) { return new ValidationResult($"依赖属性「{_dependentProperty}」不存在"); } var dependentValue = dependentProp.GetValue(validationContext.ObjectInstance); if (dependentValue is int number && number > _threshold && (value == null || string.IsNullOrWhiteSpace(value.ToString()))) { return new ValidationResult(FormatErrorMessage(validationContext.DisplayName)); } return ValidationResult.Success; } }
在Foo类中直接使用该特性:
using Newtonsoft.Json; using System.ComponentModel.DataAnnotations; public class Foo { [JsonProperty] public int Number { get; set; } [JsonProperty] [RequiredIf(nameof(Number), 10, ErrorMessage = "当Number大于10时,Text为必填项")] public string Text { get; set; } }
这种方式同样依赖Web API的模型验证机制,优势是代码复用性强,适合多模型场景。
内容的提问来源于stack exchange,提问作者Wouter Vandenputte

