我有一个带有多个提交按钮的mvc表单 - “保存草稿”和“发布”。目标是在单击“保存草稿”按钮并提交表单时跳过客户端(javascript / unobstructive)验证和服务器端验证。但是如果单击“发布”按钮,我确实需要触发两次验证。
我的研究使我找不到什么解决方案。
客户端 - 编写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();
}
}
}
但这并不完全符合我的目的。如果可以防止这种情况发生,我们为什么要触发验证并清除错误?这可能吗?
有没有办法阻止服务器验证首先发生,而不是清除验证结果错误?
答案 0 :(得分:1)
您可以在viewModel中引入一个名为IsDraft
的变量。
然后从IValidatableObject
然后实现这样的方法:(只是自定义服务器端验证的一个例子)
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (!IsDraft && StartDate > EndDate)
{
yield return new ValidationResult("Start date should be less than end date", new[] { "StartDate" });
}
}
这样,只有在非草稿时才会触发服务器端验证。
现在,对于客户端验证,请使用实现IClientValidatable
这是方法:
public IEnumerable<modelclientvalidationrule> GetClientValidationRules
(ModelMetadata metadata, ControllerContext context)
{
}
我认为这比启用禁用验证更好。
如果您需要有关实施自定义客户端验证的帮助,请参阅以下链接:
希望有所帮助
答案 1 :(得分:1)
可用的一个选项是覆盖ModelBinder,see here。
具体来说,重写OnPropertyValidating并返回false会阻止验证函数按您的意愿运行。
MVC仍在做一些工作,因为它正在读取元数据(验证属性)并迭代它们。
无论哪种方式,ModelBinder都是您需要查看的可扩展点,因为这是调用验证逻辑的。
答案 2 :(得分:1)
溢出和Bilal,感谢您回答我的问题。
@Bilal:我使用相同的模型进行保存和提交,并且不希望模型上有任何属性,而是需要控制器/操作级别的某些内容。
为了找到更好的答案,我想出了类似的东西。我从另一篇文章中读到了这个,但丢失了链接。一旦我得到它,我会更新相同的。
添加新的操作过滤器属性
[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<object> modelAccessor, Type modelType, string propertyName,
DisplayColumnAttribute displayColumnAttribute) :
base(provider, containerType, modelAccessor, modelType, propertyName, displayColumnAttribute)
{
}
public override IEnumerable<ModelValidator> 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<ModelValidator>();
}
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,则清空验证
public class IgnoreValidationModelMetaDataProvider : DataAnnotationsModelMetadataProvider
{
protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes,
Type containerType, Func<object> modelAccessor, Type modelType, string propertyName)
{
var displayColumnAttribute = new List<Attribute>(attributes).OfType<DisplayColumnAttribute>().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();
但是有几个问题。
我们是否可以操作HttpContext而不是OnAuthorization()?我不喜欢重写这个做与授权无关的事情的想法。请注意OnActionExecuting()在MVC管道中为时已太晚了(我试过这个并且无法正常工作)。
有没有更好的方法来为HttpContext添加密钥并在以后使用它?