如何根据对象使用场景,有条件设置Contact类属性为必填?
针对Contact类上下文差异化必填规则的实现方案
这是个非常典型的场景——同一个基础数据模型在不同业务上下文里需要遵守不同的校验规则,我给你整理了几个实用的实现思路,你可以根据自己项目的技术栈和复杂度来选择:
方案1:自定义数据注解+上下文感知验证
如果不想改动现有类的继承结构,可以通过自定义验证特性结合验证上下文来实现差异化校验:
首先,给Contact类添加自定义的条件必填特性:
public class Contact { [ConditionallyRequired(RequiredContexts = new[] { "Registration" })] public string FirstName { get; set; } [ConditionallyRequired(RequiredContexts = new[] { "Registration" })] public string LastName { get; set; } public string Email { get; set; } } // 自定义条件必填特性 public class ConditionallyRequiredAttribute : ValidationAttribute { public string[] RequiredContexts { get; set; } protected override ValidationResult IsValid(object value, ValidationContext validationContext) { // 从验证上下文的Items中获取当前的上下文标识 if (validationContext.Items.TryGetValue("ValidationContext", out var contextObj) && contextObj is string context && RequiredContexts.Contains(context)) { if (value == null || string.IsNullOrWhiteSpace(value.ToString())) { return new ValidationResult($"The {validationContext.DisplayName} field is required in this context."); } } return ValidationResult.Success; } }
然后在验证Registration的时候,传入对应的上下文标识:
var registration = new Registration(); var validationContext = new ValidationContext(registration) { Items = { { "ValidationContext", "Registration" } } }; var validationResults = new List<ValidationResult>(); bool isValid = Validator.TryValidateObject(registration, validationContext, validationResults, validateAllProperties: true);
验证ChildContact的时候,不传这个上下文或者传其他标识,就不会触发必填校验。
优点:不用修改原有类的结构,对现有代码侵入小;缺点:需要手动管理验证上下文,校验逻辑分散在特性里。
方案2:拆分Contact类为基础+子类(符合单一职责)
如果希望类的职责更清晰,可以把Contact拆分为基础类和带必填规则的子类:
// 基础Contact类,无必填约束 public class BaseContact { public string FirstName { get; set; } public string LastName { get; set; } public string Email { get; set; } } // 用于Registration的必填Contact类 public class RequiredContact : BaseContact { [Required] public override string FirstName { get; set; } [Required] public override string LastName { get; set; } } // 修改父类的属性类型 public class Registration { public RequiredContact ContactDetails { get; set; } public List<ChildContact> ChildContacts { get; set; } } public class ChildContact { public BaseContact Contact { get; set; } public int NoOfStudents { get; set; } }
这样Registration里的ContactDetails天然就有FirstName和LastName的必填校验,而ChildContact里的Contact则没有强制约束。
优点:类的职责清晰,校验规则和类本身绑定,无需额外上下文处理;缺点:需要新增类,对现有代码有一定的重构成本。
方案3:使用FluentValidation(灵活的第三方校验库)
如果项目可以引入第三方库,FluentValidation是个非常好的选择,它可以完全脱离实体类本身,给不同的父类配置差异化的校验规则:
首先安装NuGet包FluentValidation,然后编写对应的验证器:
// Registration的验证器 public class RegistrationValidator : AbstractValidator<Registration> { public RegistrationValidator() { RuleFor(r => r.ContactDetails.FirstName) .NotEmpty().WithMessage("FirstName is required for main contact"); RuleFor(r => r.ContactDetails.LastName) .NotEmpty().WithMessage("LastName is required for main contact"); // 可以继续配置其他规则 RuleForEach(r => r.ChildContacts).SetValidator(new ChildContactValidator()); } } // ChildContact的验证器 public class ChildContactValidator : AbstractValidator<ChildContact> { public ChildContactValidator() { // 这里不对FirstName和LastName做必填约束 RuleFor(c => c.NoOfStudents).GreaterThan(0); } }
使用的时候直接创建验证器实例即可:
var validator = new RegistrationValidator(); var result = validator.Validate(registration); bool isValid = result.IsValid;
优点:校验逻辑完全和实体类分离,配置灵活,支持复杂的校验场景;缺点:需要引入第三方库,增加项目依赖。
内容的提问来源于stack exchange,提问作者HKP
相关产品推荐
相关产品推荐

