动态启用/禁用mvc服务器端validation

我有一个带有多个提交按钮的mvc表单 – “保存草稿”和“发布”。 目标是在单击“保存草稿”按钮并提交表单时跳过客户端(javascript / unobstructive)validation和服务器端validation。 但是如果单击“发布”按钮,我确实需要触发两次validation。

我的研究使我几乎没有解决方案。

客户端 – 通过编写jquery插件

(function ($) { $.fn.turnOffValidation = function (form) { var settings = form.validate().settings; for (var ruleIndex in settings.rules) { delete settings.rules[ruleIndex]; } }; })(jQuery); 

并调用它

  $('#btnSaveDraft').click(function () { $(this).turnOffValidation(jQuery('#myForm')); }); 

服务器端 –但对于服务器端,我能找到的唯一解决方案是从ModelState中删除错误。 我在Action Attribute中完成了它,因此它可以重用并且易于使用。

 [AttributeUsage(AttributeTargets.All)] public class IgnoreValidationAttribute : ActionFilterAttribute { public override void OnActionExecuting(ActionExecutingContext filterContext) { var modelState = filterContext.Controller.ViewData.ModelState; //modelState.Clear(); foreach (var modelValue in modelState.Values) { modelValue.Errors.Clear(); } } } 

但这并不完全符合我的目的。 如果可以防止这种情况发生,我们为什么要触发validation并清除错误? 这可能吗?

有没有办法阻止服务器validation首先发生,而不是清除validation结果错误?

您可以在viewModel中引入一个名为IsDraft的变量。

然后从IValidatableObject派生您的IValidatableObject

然后实现这样的方法:(只是自定义服务器端validation的一个例子)

 public IEnumerable Validate(ValidationContext validationContext) { if (!IsDraft && StartDate > EndDate) { yield return new ValidationResult("Start date should be less than end date", new[] { "StartDate" }); } } 

这样,只有在非草稿时才会触发服务器端validation。

现在,对于客户端validation,使用实现IClientValidatable

这是方法:

 public IEnumerable GetClientValidationRules (ModelMetadata metadata, ControllerContext context) { } 

我认为这比启用禁用validation更好。

如果您需要帮助实现自定义客户端validation,请参阅这些链接:

  • 链接
  • 链接

希望有所帮助

可用的一个选项是覆盖ModelBinder, 请参见此处 。

具体来说,重写OnPropertyValidating并返回false会阻止validation函数按您的意愿运行。

MVC仍在做一些工作,因为它正在读取元数据(validation属性)并迭代它们。

无论哪种方式,ModelBinder都是您需要查看的可扩展性点,因为这是调用validation逻辑的内容。

请参阅此链接ASP.MVC可扩展性点

溢出和Bilal,谢谢你回答我的问题。

@Bilal:我使用相同的模型进行保存和提交,并且不希望模型上有任何属性,而是需要控制器/操作级别的某些内容。

为了找到更好的答案,我想出了类似的东西。 我从另一篇文章中读到了这个,但丢失了链接。 一旦我得到它,我会更新相同的。

添加新的操作filter属性

 [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] public class IgnoreValidationAttribute : FilterAttribute, IAuthorizationFilter { // TODO: Try to put it on another more appropriate method such as OnActionExcecuting. // Looks like - This is the earliest method we can interpret before an action. I really dont like this! public void OnAuthorization(AuthorizationContext filterContext) { //TODO: filterContext != null && filterContext.httpContext != null var itemKey = this.CreateKey(filterContext.ActionDescriptor); if (!filterContext.HttpContext.Items.Contains(itemKey)) { filterContext.HttpContext.Items.Add(itemKey, true); } } private string CreateKey(ActionDescriptor actionDescriptor) { var action = actionDescriptor.ActionName.ToLower(); var controller = actionDescriptor.ControllerDescriptor.ControllerName.ToLower(); return string.Format("IgnoreValidation_{0}_{1}", controller, action); } } 

覆盖DataAnnotationModelMetadata

 public class IgnoreValidationModelMetaData : DataAnnotationsModelMetadata { public IgnoreValidationModelMetaData(DataAnnotationsModelMetadataProvider provider, Type containerType, Func modelAccessor, Type modelType, string propertyName, DisplayColumnAttribute displayColumnAttribute) : base(provider, containerType, modelAccessor, modelType, propertyName, displayColumnAttribute) { } public override IEnumerable GetValidators(ControllerContext context) { var itemKey = this.CreateKey(context.RouteData); if (context.HttpContext.Items[itemKey] != null && bool.Parse(context.HttpContext.Items[itemKey].ToString()) == true) { return Enumerable.Empty(); } return base.GetValidators(context); } private string CreateKey(RouteData routeData) { var action = (routeData.Values["action"] ?? null).ToString().ToLower(); var controller = (routeData.Values["controller"] ?? null).ToString().ToLower(); return string.Format("IgnoreValidation_{0}_{1}", controller, action); } } 

现在告诉提供者使用我们的自定义数据注释元数据并在操作方法中存在IgnoreValidationAttribute时清空validation

 public class IgnoreValidationModelMetaDataProvider : DataAnnotationsModelMetadataProvider { protected override ModelMetadata CreateMetadata(IEnumerable attributes, Type containerType, Func modelAccessor, Type modelType, string propertyName) { var displayColumnAttribute = new List(attributes).OfType().FirstOrDefault(); var baseMetaData = base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName); // is there any other good strategy to copy the properties? return new IgnoreValidationModelMetaData(this, containerType, modelAccessor, modelType, propertyName, displayColumnAttribute) { TemplateHint = baseMetaData.TemplateHint, HideSurroundingHtml = baseMetaData.HideSurroundingHtml, DataTypeName = baseMetaData.DataTypeName, IsReadOnly = baseMetaData.IsReadOnly, NullDisplayText = baseMetaData.NullDisplayText, DisplayFormatString = baseMetaData.DisplayFormatString, ConvertEmptyStringToNull = baseMetaData.ConvertEmptyStringToNull, EditFormatString = baseMetaData.EditFormatString, ShowForDisplay = baseMetaData.ShowForDisplay, ShowForEdit = baseMetaData.ShowForEdit, Description = baseMetaData.Description, ShortDisplayName = baseMetaData.ShortDisplayName, Watermark = baseMetaData.Watermark, Order = baseMetaData.Order, DisplayName = baseMetaData.DisplayName, IsRequired = baseMetaData.IsRequired }; } } 

用法

 [HttpPost] [IgnoreValidation] public ActionResult SaveDraft(MyModel myModel) { if (ModelState.IsValid) { // Should always reach here } ....... } [HttpPost] public ActionResult Submit(MyModel myModel) { if (ModelState.IsValid) { } } 

请不要忘记在您的Application_Start中为连线调用“ModelMetadataProviders.Current = new IgnoreValidationModelMetaDataProvider();

但是有几个问题。

  1. 我们是否可以操作HttpContext而不是OnAuthorization()? 我不喜欢重写这个做与授权无关的事情的想法。 请注意OnActionExecuting()在MVC管道中为时已太晚了(我试过这个并且不能正常工作)。

  2. 有没有比在HttpContext中添加密钥并在以后使用它更好的方法呢?