我有一个页面,我用它向控制器发布两个视图模型:查询和预约。预约嵌套在查询中。用户可以选择向我们提交查询,而无需创建预约。
我在视图模型属性上使用内置的 MVC 所需属性。
我的问题是,当用户选择创建没有预约的查询时,如何优雅地忽略嵌套 Appointment 视图模型上的验证器并让 ModelState.IsValid 返回 true?
if(!viewModel.CreateAppointment)
{
//ignore the nested view models validation
}
您可以创建一个自定义 IgnoreModelError 属性,如下所示,并在视图中使用 2 个单独的按钮,一个仅用于查询,一个用于预约。
// C#
public class IgnoreModelErrorAttribute : ActionFilterAttribute
{
private string keysString;
public IgnoreModelErrorsAttribute(string keys)
: base()
{
this.keysString = keys;
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
ModelStateDictionary modelState = filterContext.Controller.ViewData.ModelState;
string[] keyPatterns = keysString.Split(new char[] { ',' },
StringSplitOptions.RemoveEmptyEntries);
for (int i = 0; i < keyPatterns.Length; i++)
{
string keyPattern = keyPatterns[i]
.Trim()
.Replace(@".", @"\.")
.Replace(@"[", @"\[")
.Replace(@"]", @"\]")
.Replace(@"\[\]", @"\[[0-9]+\]")
.Replace(@"*", @"[A-Za-z0-9]+");
IEnumerable<string> matchingKeys = _
modelState.Keys.Where(x => Regex.IsMatch(x, keyPattern));
foreach (string matchingKey in matchingKeys)
modelState[matchingKey].Errors.Clear();
}
}
}
[HttpPost]
[IgnoreModelErrors("Enquiry.Appointment")]
public ActionResult CreateEnquiryOnly(Enquiry enquiry)
{
// Code for enquiry only.
}
[HttpPost]
public ActionResult CreateAppointment(Enquiry enquiry)
{
// Code for appointment.
}
嗯,在使用标准数据属性时,没有办法“优雅地”忽略错误。
不过您有多种选择。快速而肮脏(即不优雅)的方法是从控制器中的 ModelState 中清除相关错误。
if (some condition) {
ModelState["controlName"].Errors.Clear();
}
您还可以编写自己的使用条件测试的自定义数据属性。就像这里描述的那样:
http://blogs.msdn.com/b/simonince/archive/2011/02/04/conditional-validation-in-asp-net-mvc-3.aspx
第三种方法是避开属性并使用验证框架,例如 FluentValidation
最后一个选择是使用 JavaScript 来确定数据的正确状态,然后修改表单操作 URL 以发布到不同的操作方法。然后,您可以使用 Bind 属性来装饰操作方法参数,以排除您不需要的数据项。但是,我不推荐这个,因为它需要客户端参与服务器端验证过程。
这就是我最终所做的。
这使我能够清除嵌套约会 ViewModel 上的所有错误。
if (!viewModel.CreateAppointment)
{
foreach (var modelError in ModelState)
{
string propertyName = modelError.Key;
if (propertyName.Contains("AppointmentsViewModel"))
{
ModelState[propertyName].Errors.Clear();
}
}
}
另一个选项是不在查询中嵌套约会,而是为页面创建一个分别包含约会和查询模型的 ViewModel,然后您可以使用 Bind 属性和 Property Include 或 Exclude 来有选择地选择要绑定或排除的模型,如图所示下面。
Public Class EnquiryViewModel
{
public Appointment App {get; set;}
public Enquiry Enq {get; set; }
}
[HttpPost]
//Only bind Enquiry model and it's errors.
public ActionResult CreateEnquiryOnly([Bind(Include = "Enq")]EnquiryViewModel enquiry)
{
if(ModelState.IsValid)
{
// Code for enquiry only.
}
}
我做了与 Erik 类似的操作 - 从控制器的
ModelState
中删除了忽略属性的错误。
public async Task<IActionResult> AddRankAssignment(RankAssignmentVm rankAssignmentVm)
{
ModelState.Remove(nameof(rankAssignmentVm.Order));
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
...
}