4

我有一个带有多个提交按钮的 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();
        }
    }
}

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

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

4

3 回答 3

1

溢出和比拉尔,感谢您回答我的问题。

@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 添加密钥并稍后使用它更好的方法呢?

于 2013-06-24T16:08:22.943 回答
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)
{

}

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

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

希望有帮助

于 2013-06-22T06:09:46.393 回答
1

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

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

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

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

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

于 2013-06-24T13:09:23.470 回答