【问题标题】:Disable Required validation attribute under certain circumstances在某些情况下禁用必需的验证属性
【发布时间】:2023-03-25 15:50:01
【问题描述】:

我想知道是否可以在某些控制器操作中禁用必需的验证属性。我想知道这是因为在我的一个编辑表单中,我不需要用户为他们之前已经指定的字段输入值。但是我随后实现了逻辑,当他们输入一个值时,它会使用一些特殊的逻辑来更新模型,例如散列值等。

关于如何解决这个问题的任何建议?

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

【问题讨论】:

  • +1 好 Q。最好在这里提及客户端验证。一种选择是完全删除RequiredAttr,并在需要时进行服务器端检查。但这在客户端上会很棘手
  • 对那些还涉及从客户端验证禁用某些字段的人的积分(不删除对jquery验证的引用)
  • 也许我错过了你的观点,但如果用户已经预先指定了这些值,那么这些值已经存在,因此将通过必需的验证。你还有别的意思吗?
  • 因为这些值已经被散列,例如密码和安全答案,所以如果他们在编辑表单上输入一个新值,我想在插入之前重新散列新值,但我也想要将其留空的选项。
  • @gideon:见 Adrian Smith 的回答:stackoverflow.com/a/9781066/114029

标签: c# asp.net-mvc asp.net-mvc-3 data-annotations


【解决方案1】:

这个问题可以通过使用视图模型轻松解决。视图模型是专门针对给定视图的需求而定制的类。因此,例如,在您的情况下,您可以拥有以下视图模型:

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 集合)。你能解释一下你是如何使用模型作为参数的吗
  • 不,我们通常没有Update(FormCollection collection),至少我从来没有。我总是定义和使用特定的视图模型:Update(UpdateViewView model).
  • 不允许重载具有相同 HTTP 操作的方法,所以对我来说这似乎行不通。我错过了什么吗?
  • @edgi,不,你没有遗漏任何东西。这是我帖子中的错误。第二种操作方法显然应该称为Insert。感谢您指出这一点。
【解决方案2】:

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

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

【讨论】:

  • 通过 jQuery 对我有用的方法:$("#SomeValue").removeAttr("data-val-required");
  • 我喜欢这种方法,但我需要使用以下方法重新解析表单验证属性:$('form').removeData('unobtrusiveValidation'); $('form').removeData('validator'); $.validator.unobtrusive.parse('表单的选择器');
  • @Html.TexBoxFor(model =&gt; model.SomeValue, new { data_val = false }) - 更易于阅读 IMO。
  • 我最喜欢这种方法,让我可以对每个字段进行精细控制,但您可以在发送数据以通过 POST 保存时添加取消 ModelState.IsValid。也许会带来一些风险?
  • 如果你想通过jQuery禁用它:$(".search select").attr('data-val', false);
【解决方案3】:

我知道很久以前就已经回答了这个问题,并且接受的答案实际上会起作用。但有一件事让我很困扰:必须复制 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] 属性,则继承属性也被强制为 [Required],除非您有自定义 [Optional] 属性。
  • 我也想过这样的事情,尽管我有一个视图模型,它有一个具有多个属性的对象“项目”,我只希望在某些情况下验证这些属性中的一个。我不认为我可以覆盖对象的属性对吗?有什么建议吗?
  • 您不能覆盖该属性。基类应该只包含所有子类的公共属性。然后,你的子类应该定义他们需要的属性。
  • 最优雅、可重复使用、清晰的解决方案。复制很糟糕。多态是方法。 +1
  • 在我的例子中,一个基类有一个必需的属性,我想让它在我的父类中不是必需的。没有两个模型副本是否可能?
【解决方案4】:

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

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

@Ian's关于 MVC5 的评论

以下还是可以的

ModelState.Remove("PropertyNameInModel");

使用更新的 API 丢失静态类型有点烦人。您可以通过创建 HTML 助手实例并使用 NameExtensions Methods 来实现类似于旧方法的效果。

【讨论】:

  • 除了...ModelState 上没有与该签名匹配的方法。至少在 MVC 5 中没有。
  • 问题不在于如何删除所有验证。这是如何删除必填字段验证。您可能希望在保留其他验证规则的同时执行此操作。
  • 这确实是最好的解决方案,即使在 .net 核心中也对我有用,谢谢@jps ModelState.Remove("PropertyNameInModel");
【解决方案5】:

客户端 为了禁用表单验证,下面给出了基于我的研究的多个选项。其中之一有望为您工作。

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

这里已经描述了一种更好的方法Enable/Disable mvc server side validation dynamically

【讨论】:

  • $('输入选择器').each(function () { $(this).rules('remove');});帮助了我
  • 这个问题专门关于删除必填字段验证,而不是删除所有验证。您的回答是关于删除所有验证。
【解决方案6】:

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

另一种可能的方法是从 IValidatableObject 派生视图模型或模型。这使您可以选择实现函数 Validate。 在 validate 中,您可以返回 ValidationResult 元素列表或为您在验证中检测到的每个问题发出 yield return。

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 并在客户端复制验证逻辑。
【解决方案7】:

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

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

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

【讨论】:

  • 这正是我所需要的。我有一个视图,并且在该视图中发生了不同的操作,因此我无法使用不同的 ViewModel 来实现。这就像一个魅力
【解决方案8】:

这是其他人在 cmets 中的答案......但它应该是一个真实的答案:

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

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

答案从上面的https://stackoverflow.com/users/73382/rob 窃取

【讨论】:

  • 服务器端验证呢?
  • ModelState.Remove,对吗?无论如何,对我来说,问题是我的模型中包含了第二个实体......我想要验证的主要实体,但次要实体不需要在该页面上验证......所以,在这种情况下,只需要 JQuery。
  • 我认为链接已损坏,所以请编辑。这是部分答案
  • 你说这在 MVC6 中有效(我目前没有在 MVC6 上测试的选项)但在我目前使用的 MVC4 上不适用于我,这很奇怪。
  • 问题是针对 MVC/C# - 不是 JS,答案在服务器端不起作用
【解决方案9】:

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

我的最简单方法是使用两个字段:

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

评论是我只在编辑视图中更新的字段,没有必填属性。

ASP.NET MVC 3 实体

【讨论】:

    【解决方案10】:

    AFAIK 您不能在运行时删除属性,而只能更改它们的值(即:只读 true/false)look here for something similar。 作为另一种在不弄乱属性的情况下做你想做的事情的方法,我将为你的特定操作使用 ViewModel,这样你就可以插入所有逻辑而不会破坏其他控制器所需的逻辑。 如果您尝试获取某种向导(多步骤表单),您可以改为序列化已编译的字段,并使用 TempData 将它们带到您的步骤中。 (有关序列化反序列化的帮助,您可以使用MVC futures

    【讨论】:

      【解决方案11】:

      @Darin 所说的也是我推荐的。但是,我要补充一点(并作为对其中一个 cmets 的回应),您实际上也可以将此方法用于原始类型,如 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
      }
      

      【讨论】:

        【解决方案12】:

        从 MVC 5 开始,这可以通过将其添加到您的 global.asax 中轻松实现。

        DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false;
        

        【讨论】:

          【解决方案13】:

          我一直在寻找一种解决方案,我可以使用相同的模型在 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
              {
          
              }
          }
          

          来源:

          【讨论】:

            【解决方案14】:

            如果您不想使用另一个 ViewModel,您可以禁用视图上的客户端验证,并删除服务器上您想要忽略的那些属性的验证。请查看此答案以获得更深入的解释https://stackoverflow.com/a/15248790/1128216

            【讨论】:

              【解决方案15】:

              在我的例子中,相同的模型被用于许多页面以实现可重用性。所以我所做的是我创建了一个自定义属性来检查排除项

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

              【讨论】:

                【解决方案16】:

                是的,可以禁用必需属性。创建您自己的自定义类属性(名为 ChangeableRequired 的示例代码)以扩展 RequiredAtribute 并添加一个 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;
                

                这感觉又好又干净?

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

                【讨论】:

                • 在对此进行了几次辩论之后,我想提一下,我不建议禁用验证,而是查看规则。如果您禁用它,您将创建一个依赖项来重新启用该规则,我建议您查看该规则。
                • 更改静态值以完全禁用在同一进程中执行的该字段的所有验证听起来是个糟糕的主意。
                猜你喜欢
                • 1970-01-01
                • 2014-08-09
                • 2018-03-14
                • 2012-01-26
                • 2017-10-09
                • 1970-01-01
                • 2019-05-13
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多