如何在不同视图复用同一ViewModel并排除必填属性?
嘿,这个问题我之前做项目的时候也遇到过——不想新建ViewModel又要跳过特定属性的必填验证,其实有几个实用的方案可以解决,我给你详细说说:
IValidatableObject接口做场景化验证 这种方式能让你在ViewModel内部根据不同的使用场景,灵活控制是否验证那些必填属性。你只需要给ViewModel加一个场景标识属性,然后在Validate方法里判断场景,选择性执行验证逻辑。
修改后的ViewModel代码示例:
public class AddBookingsViewModel : IValidatableObject { // 新增场景标识,用来区分不同视图的使用场景 public string ValidationScenario { get; set; } // 这里可以先去掉原来的[Required]注解,或者保留也没关系,因为我们会手动控制验证逻辑 public string CustomerName { get; set; } public string ContactNo { get; set; } public string VehicleRegNo { get; set; } // 其他属性... public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) { // 如果当前场景不是AddAppointment(也就是你要复用的那个视图场景),直接跳过必填验证 if (ValidationScenario != "AddAppointment") { yield break; } // 手动执行必填项验证 if (string.IsNullOrEmpty(CustomerName)) { yield return new ValidationResult("客户姓名不能为空", new[] { nameof(CustomerName) }); } if (string.IsNullOrEmpty(ContactNo)) { yield return new ValidationResult("联系电话不能为空", new[] { nameof(ContactNo) }); } } }
然后在不需要验证必填项的Action里,设置场景标识:
public IActionResult ReuseView(AddBookingsViewModel model) { model.ValidationScenario = "ReuseScenario"; // 框架会自动调用Validate方法进行验证 if (!ModelState.IsValid) { // 处理验证失败逻辑 } // 后续业务逻辑 return View(model); }
如果你的场景比较简单,不想改ViewModel的话,可以直接在Action里把那两个必填属性的错误从ModelState中移除,这样模型就会被判定为有效。
示例代码:
public IActionResult ReuseView(AddBookingsViewModel model) { // 移除指定属性的验证错误 ModelState.Remove(nameof(model.CustomerName)); ModelState.Remove(nameof(model.ContactNo)); // 现在重新检查模型有效性 if (ModelState.IsValid) { // 执行业务逻辑 } return View(model); }
这个方法很直接,但要注意硬编码了属性名,如果后续属性改名,记得同步修改这里,不然会出问题。
你可以创建一个自定义的RequiredIfScenario特性,替代原来的[Required],这样就能根据场景动态决定是否启用必填验证,比手动写逻辑更优雅。
先定义自定义特性:
public class RequiredIfScenarioAttribute : ValidationAttribute { // 需要验证的场景名称 public string TargetScenario { get; set; } protected override ValidationResult IsValid(object value, ValidationContext validationContext) { var viewModel = validationContext.ObjectInstance as AddBookingsViewModel; if (viewModel == null) { return ValidationResult.Success; } // 如果当前场景不是目标场景,直接跳过验证 if (viewModel.ValidationScenario != TargetScenario) { return ValidationResult.Success; } // 否则执行必填验证 if (value == null || string.IsNullOrWhiteSpace(value.ToString())) { return new ValidationResult(ErrorMessage ?? $"{validationContext.DisplayName} 不能为空"); } return ValidationResult.Success; } }
然后修改ViewModel的属性注解:
public class AddBookingsViewModel { public string ValidationScenario { get; set; } [RequiredIfScenario(TargetScenario = "AddAppointment", ErrorMessage = "客户姓名不能为空")] public string CustomerName { get; set; } [RequiredIfScenario(TargetScenario = "AddAppointment", ErrorMessage = "联系电话不能为空")] public string ContactNo { get; set; } public string VehicleRegNo { get; set; } // 其他属性... }
使用方式和方案1一样,在Action里设置ValidationScenario即可,这个特性还能复用在其他ViewModel上,扩展性更好。
如果你的项目可以引入FluentValidation库,那它的条件验证能力会更强大,完全解耦验证逻辑和ViewModel,后期维护也更方便。
首先安装NuGet包FluentValidation.AspNetCore,然后创建验证器:
public class AddBookingsViewModelValidator : AbstractValidator<AddBookingsViewModel> { public AddBookingsViewModelValidator() { // 只有在AddAppointment场景下,才验证CustomerName和ContactNo为必填 RuleFor(x => x.CustomerName) .NotEmpty() .WithMessage("客户姓名不能为空") .When(x => x.ValidationScenario == "AddAppointment"); RuleFor(x => x.ContactNo) .NotEmpty() .WithMessage("联系电话不能为空") .When(x => x.ValidationScenario == "AddAppointment"); // 其他验证规则可以在这里添加 } }
然后在Startup.cs里注册验证器:
services.AddControllersWithViews() .AddFluentValidation(fv => fv.RegisterValidatorsFromAssemblyContaining<AddBookingsViewModelValidator>());
使用时同样在Action里设置ValidationScenario,FluentValidation会自动根据场景应用对应的验证规则,这种方式特别适合复杂的验证场景。
内容的提问来源于stack exchange,提问作者user9677867

