【问题标题】:Enable/Disable mvc server side validation dynamically动态启用/禁用 mvc 服务器端验证
【发布时间】:2013-06-07 21:22:40
【问题描述】:

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

我的研究让我找到了几个解决方案。

客户端 - 通过编写 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();
        }
    }
}

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

有什么方法可以防止服务器验证首先发生而不是清除验证导致的错误?

【问题讨论】:

    标签: jquery validation model-view-controller data-annotations action-filter


    【解决方案1】:

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

    然后从IValidatableObject派生你的viewModel

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

    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)
    {
    
    }
    

    我相信这是一种比启用禁用验证更好的方法。

    如果您在实施自定义客户端验证方面需要帮助,请参阅这些链接:

    希望有帮助

    【讨论】:

      【解决方案2】:

      一个可用的选项是覆盖 ModelBinder,see here

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

      MVC 仍在做一些工作,因为它正在读取元数据(验证属性)并遍历它们。

      无论哪种方式,ModelBinder 都是您需要查看的扩展点,因为这就是所谓的验证逻辑。

      查看此链接ASP.MVC Extensibility Points

      【讨论】:

        【解决方案3】:

        Overflow 和 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(); 调用它;

        不过有几个问题。

        1. 有没有比 OnAuthorization() 更早的地方我们可以操纵 HttpContext ?我不喜欢重写它来做与授权无关的事情的想法。请注意 OnActionExecuting() 在 MVC 管道中为时已晚,无法执行此操作(我尝试过此操作但无法正常工作)。

        2. 有没有比向 HttpContext 添加一个键并在以后使用它更好的方法?

        【讨论】:

        • 请不要忘记在您的 Application_Start 中为连接 'ModelMetadataProviders.Current = new IgnoreValidationModelMetaDataProvider();'调用它
        猜你喜欢
        • 1970-01-01
        • 2013-10-09
        • 1970-01-01
        • 2013-01-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多