在某些情况下禁用“必需的验证”属性


138

我想知道是否可以在某些控制器操作中禁用“必需的验证”属性。我想知道这是因为在我的一种编辑表单上,我不需要用户为他们先前已经指定的字段输入值。但是,我然后实现了一种逻辑,即当他们输入值时,它会使用一些特殊的逻辑来更新模型,例如对值进行哈希处理等。

关于如何解决此问题有任何建议吗?

编辑:
是的,客户端验证在这里是一个问题,因为它将不允许他们在不输入值的情况下提交表单。


3
+1好Q。在这里提到客户验证会很好。一种选择是RequiredAttr完全删除并在需要时进行服务器端检查但这对客户来说是棘手的
基甸

4
给任何也涵盖从客户端验证禁用某些字段(不删除对jquery验证的引用)的人的
要点

也许我想不到您的意思,但是如果用户已经预先指定了值,那么这些值已经存在,因此将通过必需的验证。你还有什么意思吗
Erik Funkenbusch 2011年

因为这些值已经过哈希处理,例如密码和安全性答案,所以如果它们在编辑表单上输入新值,我想在插入之前重新哈希新值,但我也希望将其选项留为空白那类的东西。
Alex Hope O'Connor

1
@gideon:请参阅Adrian Smith的答案:stackoverflow.com/a/9781066/114029
Leniel Maccaferri'4

Answers:


76

通过使用视图模型可以轻松解决此问题。视图模型是专门为给定视图的需求量身定制的类。因此,例如,在您的情况下,您可以具有以下视图模型:

public UpdateViewView
{
    [Required]
    public string Id { get; set; }

    ... some other properties
}

public class InsertViewModel
{
    public string Id { get; set; }

    ... some other properties
}

将在其相应的控制器操作中使用:

[HttpPost]
public ActionResult Update(UpdateViewView model)
{
    ...
}

[HttpPost]
public ActionResult Insert(InsertViewModel model)
{
    ...
}

如果您的布尔值/位不可为空,则并非总是如此。但实际上并没有什么不同,因为它最终会变成真或假。我有CSS突出显示必填字段,并且错误地突出了CheckBoxFor项目。我的解决方案:$(“#IsValueTrue”)。removeAttr(“ data-val-required”);
罗伯特·科赫

在更新中,我们通常有(FormCollection集合)。您能否解释一下如何使用模型作为参数
Raj Kumar 2012年

4
不,我们通常没有Update(FormCollection collection),至少我从来没有。我总是定义并使用特定的视图模型:Update(UpdateViewView model)
Darin Dimitrov 2012年

不允许使用相同的HTTP操作重载方法,所以在我看来,这似乎行不通。我想念什么吗?
e11s 2012年

@edgi,不,您没有丢失任何东西。这是我的帖子中的错误。第二种动作方法显然应该被调用Insert。感谢您指出了这一点。
Darin Dimitrov

55

如果只想在客户端禁用单个字段的验证,则可以按如下所示覆盖验证属性:

@Html.TexBoxFor(model => model.SomeValue, 
                new Dictionary<string, object> { { "data-val", false }})

20
通过jQuery对我有用的是:$(“#SomeValue”)。removeAttr(“ data-val-required”);
罗伯特·科赫

6
我喜欢这种方法,但是我需要使用以下方法重新解析表单验证属性:$('form')。removeData('unobtrusiveValidation'); $('form')。removeData('validator'); $ .validator.unobtrusive.parse('表单选择器');
Yannick Smits,2012年

15
@Html.TexBoxFor(model => model.SomeValue, new { data_val = false })-易于阅读IMO。
eth0 2012年

我喜欢这种方法中的大多数,可以让我对每个字段进行很好的控制,但是当发送数据以通过POST保存时,您可以添加取消ModelState.IsValid。可能会造成一些风险?
Felipe FMMobile 2012年

4
如果您想通过jQuery禁用它:$(".search select").attr('data-val', false);
Leniel Maccaferri 2013年

40

我知道这个问题已经很久以前已经回答了,接受的答案实际上可以完成工作。但是有一件事困扰着我:只复制2个模型就可以禁用验证。

这是我的建议:

public class InsertModel
{
    [Display(...)]
    public virtual string ID { get; set; }

    ...Other properties
}

public class UpdateModel : InsertModel
{
    [Required]
    public override string ID
    {
        get { return base.ID; }
        set { base.ID = value; }
    }
}

这样,您就不必为客户端/服务器端验证而烦恼,该框架将按照预期的方式运行。另外,如果您[Display]在基类上定义了一个属性,则不必在中重新定义它UpdateModel

而且您仍然可以以相同的方式使用这些类:

[HttpPost]
public ActionResult Update(UpdateModel model)
{
    ...
}

[HttpPost]
public ActionResult Insert(InsertModel model)
{
    ...
}

我更喜欢这种方法,尤其是在验证属性列表很长的情况下。在您的示例中,您可以在基类中添加更多属性,以使好处更加明显。我可以看到的唯一缺点是无法继承属性来覆盖属性。例如,如果您在基类上具有[Required]属性,则除非您具有自定义的[Optional]属性,否则也必须将继承属性也设为[Required]。
Yorro

我也想到了类似的东西,尽管我有一个viewModel,它的对象“ Project”具有多个属性,并且我只希望在某些情况下验证这些属性之一。我不认为我可以覆盖对象的属性,对不对?有什么建议吗?
森特·

您不能覆盖该属性。基类应仅包含所有子类的公共属性。然后,您的子类应定义所需的属性。
PhilDulac 2014年

1
最优雅,可重复使用的清晰解决方案。复制不好。多态就是这样。+1
T恤,2015年

在我的情况下,基类具有required属性,而我想在父类中使它成为非必需属性。没有两个模型副本是否可能?
阿列克谢·斯特拉克

27

您可以在控制器操作中使用以下命令删除属性的所有验证。

ModelState.Remove<ViewModel>(x => x.SomeProperty);

@Ian关于MVC5 评论

仍然有可能

ModelState.Remove("PropertyNameInModel");

令人烦恼的是,使用更新的API会丢失静态类型。通过创建HTML帮助器实例并使用NameExtensions方法,可以实现与旧方法类似的方法


除了...上没有ModelState匹配该签名的方法。至少在MVC 5中没有。
伊恩·肯普

问题不在于如何删除所有验证。这是如何删除必需的字段验证。您可能需要这样做,同时保留其他验证规则。
理查德

15

客户端 为了禁用表单验证,以下基于我的研究提供了多个选项。其中之一将希望为您工作。

选项1

我喜欢这个,这对我来说非常合适。

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

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

并像这样调用

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

选项2

$('your selector here').data('val', false);
$("form").removeData("validator");
$("form").removeData("unobtrusiveValidation");
$.validator.unobtrusive.parse("form");

选项3

var settings = $.data($('#myForm').get(0), 'validator').settings;
settings.ignore = ".input";

选项4

 $("form").get(0).submit();
 jQuery('#createForm').unbind('submit').submit();

选项5

$('input selector').each(function () {
    $(this).rules('remove');
});

服务器端

创建一个属性,并使用该属性标记您的操作方法。自定义此设置以适应您的特定需求。

[AttributeUsage(AttributeTargets.All)]
public class IgnoreValidationAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        var modelState = filterContext.Controller.ViewData.ModelState;

        foreach (var modelValue in modelState.Values)
        {
            modelValue.Errors.Clear();
        }
    }
}

此处介绍了一种更好的方法,可以动态启用/禁用mvc服务器端验证


$('input selection')。each(function(){$(this).rules('remove');}); 帮助了我
Sachin Pakale '16

问题特别是关于删除必需的字段验证,而不是关于删除所有验证。您的答案是关于删除所有验证。
理查德

14

我个人倾向于使用Darin Dimitrov在他的解决方案中展示的方法。这使您腾出了使用数据批注方法进行验证的能力,并使每个ViewModel上具有与手头任务相对应的单独数据属性。为了最大程度地减少在模型和视图模型之间进行复制的工作量,应查看AutoMapper或ValueInjecter。两者都有各自的长处,因此请同时检查它们。

另一种可能的方法是从IValidatableObject派生您的视图模型或模型。这为您提供了实现功能验证的选项。在验证中,您可以返回ValidationResult元素的列表,也可以针对在验证中检测到的每个问题发出收益回报。

ValidationResult由错误消息和带有字段名的字符串列表组成。错误消息将显示在输入字段附近的位置。

public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
  if( NumberField < 0 )
  {
    yield return new ValidationResult( 
        "Don't input a negative number", 
        new[] { "NumberField" } );
  }

  if( NumberField > 100 )
  {
    yield return new ValidationResult( 
        "Don't input a number > 100", 
        new[] { "NumberField" } );
  }

  yield break;
}

我们可以把它挂在客户端吗?
穆罕默德·阿德·扎希德

客户端验证通常仅针对单个字段进行,以提供交互,逐字段验证反馈,以方便用户。由于对象验证步骤通常涉及依赖验证(对象本身外部的多个字段和/或条件),因此即使您可以将代码编译为JavaScript,也不一定要在客户端执行。如果在客户端进行复杂/相关的验证确实增加了价值,那么您将需要使用onsubmit-callback并在客户端复制验证逻辑。
mindplay.dk 2012年

7

我认为这里最干净的方法是禁用客户端验证,而在服务器端,您将需要:

  1. ModelState [“ SomeField”]。Errors.Clear(在您的控制器中或创建一个动作过滤器以在执行控制器代码之前消除错误)
  2. 当检测到违反检测到的问题时,从控制器代码中添加ModelState.AddModelError。

似乎这里的自定义视图模型也无法解决问题,因为这些“预回答”字段的数量可能会有所不同。如果他们不这样做,那么自定义视图模型确实可能是最简单的方法,但是使用上述技术,您可以解决验证问题。


1
这正是我所需要的。我只有一个视图,并且此视图中发生了不同的动作,所以我无法使用不同的ViewModels来实现。这就像一个魅力
ggderas

6

这是别人在评论中的答案...但这应该是真实的答案:

$("#SomeValue").removeAttr("data-val-required")

在具有[Required]属性的字段的MVC 6上进行了测试

从上面的https://stackoverflow.com/users/73382/rob被盗的答案


1
服务器端验证呢?
T-moty 2015年

ModelState.Remove,对吧?无论如何,对我而言,问题是我的模型中包含第二个实体...我要在其上进行验证的主要实体,但是次要实体不需要在该页面上进行验证...因此,在这种情况下,只需要JQuery。
Mike_Matthews_II 2015年

我认为该链接已损坏,因此请进行编辑。这是部分答案
T-moty 2015年

奇怪的是,您说这在MVC6中有效(我目前没有选择在MVC6上进行测试),但在我当前使用的MVC4上不适合我。
eaglei22 '18

问题是针对MVC / C#的-不是JS,答案在服务器端不起作用
mtbennett

2

当我为模型创建一个“编辑视图”时我遇到了这个问题,我只想更新一个字段。

我最简单的解决方案是使用以下两个字段:

 <%: Html.HiddenFor(model => model.ID) %>
 <%: Html.HiddenFor(model => model.Name)%>
 <%: Html.HiddenFor(model => model.Content)%>
 <%: Html.TextAreaFor(model => model.Comments)%>

注释是我仅在“编辑视图”中更新的字段,没有必填属性。

ASP.NET MVC 3实体


1

AFAIK,您不能在运行时删除属性,而只能更改其值(即:只读true / false)在此处查找类似内容。作为在不弄乱属性的情况下完成所需操作的另一种方式,我将使用ViewModel进行特定操作,以便您可以插入所有逻辑而不会破坏其他控制器所需的逻辑。如果尝试获取某种向导(多步骤表单),则可以序列化已编译的字段,并使用TempData将它们带入步骤。(有关序列化反序列化的帮助,您可以使用MVC期货


1

@Darin所说的也是我的建议。但是,我要补充一点(作为对其中一项评论的回应),您实际上也可以通过简单地使它们可为空而将该方法用于诸如bit,bool甚至是Guid之类的原始类型。完成此操作后,该Required属性将按预期运行。

public UpdateViewView
{
    [Required]
    public Guid? Id { get; set; }
    [Required]
    public string Name { get; set; }
    [Required]
    public int? Age { get; set; }
    [Required]
    public bool? IsApproved { get; set; }
    //... some other properties
}

1

从MVC 5开始,可以通过在VC中添加此代码轻松实现global.asax

DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false;

1

我一直在寻找一种解决方案,可以在Web API中使用相同的模型进行插入和更新。在我看来,这始终是一种满足感。的[Requiered],如果它是一个更新方法属性必须被跳过。在我的解决方案中,您[IgnoreRequiredValidations]在方法上方放置了一个属性。如下所示:

public class WebServiceController : ApiController
{
    [HttpPost]
    public IHttpActionResult Insert(SameModel model)
    {
        ...
    }

    [HttpPut]
    [IgnoreRequiredValidations]
    public IHttpActionResult Update(SameModel model)
    {
        ...
    }

    ...

还有什么需要做的?必须在启动时创建并添加自己的BodyModelValidator。这在HttpConfiguration中,如下所示:config.Services.Replace(typeof(IBodyModelValidator), new IgnoreRequiredOrDefaultBodyModelValidator());

using Owin;
using your_namespace.Web.Http.Validation;

[assembly: OwinStartup(typeof(your_namespace.Startup))]

namespace your_namespace
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            Configuration(app, new HttpConfiguration());
        }

        public void Configuration(IAppBuilder app, HttpConfiguration config)
        {
            config.Services.Replace(typeof(IBodyModelValidator), new IgnoreRequiredOrDefaultBodyModelValidator());
        }

        ...

我自己的BodyModelValidator是从DefaultBodyModelValidator派生的。而且我发现我必须重写'ShallowValidate'方法。在此替代中,我过滤了所需的模型验证器。现在是IgnoreRequiredOrDefaultBodyModelValidator类和IgnoreRequiredValidations属性类:

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Web.Http.Controllers;
using System.Web.Http.Metadata;
using System.Web.Http.Validation;

namespace your_namespace.Web.Http.Validation
{
    public class IgnoreRequiredOrDefaultBodyModelValidator : DefaultBodyModelValidator
    {
        private static ConcurrentDictionary<HttpActionBinding, bool> _ignoreRequiredValidationByActionBindingCache;

        static IgnoreRequiredOrDefaultBodyModelValidator()
        {
            _ignoreRequiredValidationByActionBindingCache = new ConcurrentDictionary<HttpActionBinding, bool>();
        }

        protected override bool ShallowValidate(ModelMetadata metadata, BodyModelValidatorContext validationContext, object container, IEnumerable<ModelValidator> validators)
        {
            var actionContext = validationContext.ActionContext;

            if (RequiredValidationsIsIgnored(actionContext.ActionDescriptor.ActionBinding))
                validators = validators.Where(v => !v.IsRequired);          

            return base.ShallowValidate(metadata, validationContext, container, validators);
        }

        #region RequiredValidationsIsIgnored
        private bool RequiredValidationsIsIgnored(HttpActionBinding actionBinding)
        {
            bool ignore;

            if (!_ignoreRequiredValidationByActionBindingCache.TryGetValue(actionBinding, out ignore))
                _ignoreRequiredValidationByActionBindingCache.TryAdd(actionBinding, ignore = RequiredValidationsIsIgnored(actionBinding.ActionDescriptor as ReflectedHttpActionDescriptor));

            return ignore;
        }

        private bool RequiredValidationsIsIgnored(ReflectedHttpActionDescriptor actionDescriptor)
        {
            if (actionDescriptor == null)
                return false;

            return actionDescriptor.MethodInfo.GetCustomAttribute<IgnoreRequiredValidationsAttribute>(false) != null;
        } 
        #endregion
    }

    [AttributeUsage(AttributeTargets.Method, Inherited = true)]
    public class IgnoreRequiredValidationsAttribute : Attribute
    {

    }
}

资料来源:



0

在我的情况下,出于重复使用的目的,在许多页面中使用了相同的模型。所以我所做的是我创建了一个自定义属性来检查排除项

public class ValidateAttribute : ActionFilterAttribute
{
    public string Exclude { get; set; }
    public string Base { get; set; }
    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        if (!string.IsNullOrWhiteSpace(this.Exclude))
        {
            string[] excludes = this.Exclude.Split(',');
            foreach (var exclude in excludes)
            {
                actionContext.ModelState.Remove(Base + "." + exclude);
            }
        }
        if (actionContext.ModelState.IsValid == false)
        {
            var mediaType = new MediaTypeHeaderValue("application/json");
            var error = actionContext.ModelState;

            actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.OK, error.Keys, mediaType);

        }
    }
}

并在您的控制器中

[Validate(Base= "person",Exclude ="Age,Name")]
    public async Task<IHttpActionResult> Save(User person)
    {

            //do something           

    }

说模型是

public class User
{
    public int Id { get; set; }
    [Required]
    public string Name { get; set; }
    [Range(18,99)]
    public string Age { get; set; }
    [MaxLength(250)]
    public string Address { get; set; }
}

-1

是的,可以禁用必需属性。从RequiredAtribute创建您自己的自定义类属性(示例代码称为ChangeableRequired),并添加Disabled属性,并覆盖IsValid方法以检查其是否被取消平衡。使用反射来设置禁用的属性,如下所示:

自定义属性:

namespace System.ComponentModel.DataAnnotations
{
    public class ChangeableRequired : RequiredAttribute
    {
       public bool Disabled { get; set; }

       public override bool IsValid(object value)
       {
          if (Disabled)
          {
            return true;
          }

          return base.IsValid(value);
       }
    }
}

更新您的属性以使用新的自定义属性:

 class Forex
 {
 ....
    [ChangeableRequired]
    public decimal? ExchangeRate {get;set;}
 ....
 }

在需要禁用该属性的地方,请使用反射进行设置:

Forex forex = new Forex();
// Get Property Descriptor from instance with the Property name
PropertyDescriptor descriptor = TypeDescriptor.GetProperties(forex.GetType())["ExchangeRate"];
//Search for Attribute
ChangeableRequired attrib =  (ChangeableRequired)descriptor.Attributes[typeof(ChangeableRequired)];

// Set Attribute to true to Disable
attrib.Disabled = true;

感觉好干净吗?

注意:当您的对象实例处于活动状态时,以上验证将被禁用。


在对此进行了一些辩论之后,我想提到的是,我不建议禁用验证,而是查看规则。如果禁用它,则将创建一个依赖项以重新启用该规则,我建议您复查该规则。
欧内斯特·甘宁

更改静态值以完全禁用在同一过程中执行的对该字段的所有验证听起来像一个可怕的主意。
杰里米·莱克曼
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.