سؤال

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.

My research has led me to few solutions.

Client Side -By writing a jquery plugin

    (function ($) {
        $.fn.turnOffValidation = function (form) {
            var settings = form.validate().settings;

            for (var ruleIndex in settings.rules) {
                delete settings.rules[ruleIndex];
            }
        };
    })(jQuery); 

and invoking it like

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

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?

هل كانت مفيدة؟

المحلول 3

Overflow and Bilal, Thanks for answering my question.

@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.

Add a new action filter attribute

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

Override the 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);
    }
}

Now tell the provider to use our custom data annotation metadata and empty the validation if IgnoreValidationAttribute is present in an action method

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
        };
    }
}

Usage

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

        .......
    }

    [HttpPost]
    public ActionResult Submit(MyModel myModel)
    {
        if (ModelState.IsValid)
        {
        }
    }

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

There are couple of concerns though.

  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).

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

نصائح أخرى

You can introduce a variable in you viewModel called IsDraft.

Then derive your viewModel from IValidatableObject

Then implement its method like this : (just an example of custom server side validation)

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

This way you will have your server side validation triggered only when its not Draft.

Now for client side validation use implement IClientValidatable

This is the method :

public IEnumerable<modelclientvalidationrule> GetClientValidationRules 
(ModelMetadata metadata, ControllerContext context)
{

}

I believe this is a better approach than enabling disabling the validation.

refer to these link if you need help implementing custom client side validations:

Hope that helps

One option available is to override ModelBinder, see here.

Specifically, overriding OnPropertyValidating and returning false prevents the validation function from running as you would like.

MVC is still doing a bit of work in that it is reading the metadata (validation attributes) and iterating through them.

Either way, ModelBinder is the extensibility point you need to look at as this is what calls the validation logic.

See this link ASP.MVC Extensibility Points

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top