动态启用/禁用MVC服务器端验证

动态启用/禁用MVC服务器端验证

问题描述:

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

I have an mvc form with multiple submit buttons - 'Save Draft' and 'Publish'. The objective is to skip both client side(javascript/unobstructive) validation and server side validation when the 'Save Draft' button is clicked and the form is submitted. But I do need to trigger both validation if the 'Publish' button is clicked.

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

客户端-通过编写一个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中删除错误.我已经在动作属性"中完成了此操作,以使其可重用且易于使用.

Server Side - But for the server side, the only solution I could find is to remove the errors from the ModelState. I have done it in an Action Attribute so that it is reusable and easy to use.

[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();
        }
    }
}

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

But this does not completely serve my purpose. Why should we trigger the validation and clear the errors if we can prevent that from happening? Is this possible?

有什么方法可以防止服务器验证最初发生,而不是清除验证结果错误?

Are there any ways to prevent the server validation happening at the first place rather than clearing the validation resulted errors?

溢出和Bilal,感谢您回答我的问题.

Overflow and Bilal, Thanks for answering my question.

@Bilal:我对保存"和提交"使用相同的模型,并且不希望模型具有任何属性,而是需要控制器/操作级别的某些东西.

@Bilal: I use the same model for Save and Submit and does not want any attributes on Model, rather need something at a controller/action level.

在寻找更好的答案的过程中,我想出了类似的方法.我从另一篇文章中阅读了此文章,但丢失了链接.一旦获得,我将对其进行更新.

In a search to find a better answer I have come up with something like this. I read this from another article, but lost the link. As soon as I get it I will update the same.

添加新的操作过滤器属性

[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();

Please don't forget to call this in your Application_Start for the wireup 'ModelMetadataProviders.Current = new IgnoreValidationModelMetaDataProvider();

尽管有一些担忧.

  1. 与OnAuthorization()相比,有没有一个我们可以较早地操作HttpContext的地方?我不喜欢覆盖此内容以执行与授权无关的想法.请注意,OnActionExecuting()在MVC管道中执行此操作为时已晚(我尝试了此操作,但无法正常工作).

  1. Is there an early place we could manipulate the HttpContext than OnAuthorization() ?. I dont like the idea of overriding this to do something not related to authorization. Please note OnActionExecuting() will be too late in the MVC pipeline to do this(I tried this and is not working).

是否有比将密钥添加到HttpContext并在以后使用的方法更好的方法?

Is there a better way to do this than adding a key to HttpContext and using it later?