在特定情况下禁用必填验证属性

147

我想知道是否可以在特定的控制器操作中禁用"Required"验证属性。我之所以这样想,是因为在我的一个编辑表单中,我不需要用户为他们之前已经指定的字段输入值。但是,当他们输入一个值时,我会实现一些特殊逻辑,例如对一个值进行哈希等操作来更新模型。

有没有任何建议可以解决这个问题?

编辑:
而且是的,客户端验证也是一个问题,因为它不允许他们提交表单而不输入值。


3
+1好问题。在这里提到客户端验证会更好。一种选择是完全删除“RequiredAttr”,而在需要时进行服务器端检查。但这对于客户端来说可能会很棘手。 - gideon
4
对于那些涵盖了从客户端验证中禁用特定字段的内容(不要删除对jQuery验证的引用),将会得到加分。请注意使翻译内容更加通俗易懂,但不改变原意。 - gideon
也许我没有理解您的意思,但是如果用户已经预先指定了这些值,那么这些值已经存在,因此将通过必需验证。您是指其他方面吗? - Erik Funkenbusch
因为这些值已经被哈希了,比如密码和安全答案,所以如果他们在编辑表单中输入一个新值,我想在插入之前重新哈希新值,但我也希望有一个选项可以将其留空。 - Alex Hope O'Connor
1
@gideon:请参考Adrian Smith的回答:https://dev59.com/r2435IYBdhLWcg3whQc5#9781066 - Leniel Maccaferri
17个回答

80

这个问题可以通过使用视图模型轻松解决。视图模型是专门针对特定视图需求的类。所以例如在你的情况下,你可以有以下视图模型:

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

如果你有一个不可为空的布尔型/位(boolean/bit)的话,上述说法并非总是正确的。但这并没有什么影响,因为最终结果都会是真或假。我曾经用css来突出显示必填字段,而它错误地将CheckBoxFor项目也加入其中。我的解决方案是:$("#IsValueTrue").removeAttr("data-val-required"); - Rob Koch
4
不,通常我们不会使用Update(FormCollection collection),至少我从来没有这样做。我总是定义和使用一个特定的视图模型:Update(UpdateViewView model) - Darin Dimitrov
重载具有相同HTTP动作的方法是不允许的,所以在我看来这似乎行不通。我有什么遗漏吗? - e11s
@edgi,不,你没有错过什么。这是我帖子中的一个错误。第二个 action 方法明显应该被称为 Insert。谢谢你指出了这一点。 - Darin Dimitrov
我正在使用ViewModels,但我的VM继承了一个基类,该基类具有被标记为“必需”的“Title”属性,是否有一种方法可以在子类中禁用它? - Shimmy Weitzhandler
显示剩余2条评论

57

如果你只想在客户端禁用单个字段的验证,那么可以按照以下方式覆盖验证属性:

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

21
我通过jQuery实现的方法:$("#SomeValue").removeAttr("data-val-required"); - Rob Koch
7
我喜欢这种方法,但我需要重新解析表单验证属性,使用以下代码: $('form').removeData('unobtrusiveValidation'); $('form').removeData('validator'); $.validator.unobtrusive.parse('您的表单选择器'); - Yannick Smits
17
我理解您需要翻译的代码是:@Html.TexBoxFor(model => model.SomeValue, new { data_val = false }),我的翻译如下:@Html.TexBoxFor(model => model.SomeValue, new { data_val = false }) - 依我的看法,这段代码更易读。 - eth0
4
如果您想通过jQuery禁用它:$(".search select").attr('data-val', false); - Leniel Maccaferri
IT移除了所有验证,是否可能仅通过JavaScript删除必填字段验证? - Nayana Setty
显示剩余2条评论

46

我知道这个问题很久以前就有答案并且被接受的答案确实可以解决问题。但是有一件事情困扰着我:需要复制两个模型来禁用验证。

这是我的建议:

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]属性。 - Yorro
我也考虑过这样的事情,不过我有一个 viewModel,其中有一个名为“Project”的对象,它具有多个属性,而我只想在特定情况下验证其中一个属性。我认为我不能仅仅覆盖对象的属性,对吗?你有什么建议吗? - vincent de g
你不能覆盖该属性。基类应该只包含所有子类共有的通用属性。然后,你的子类应该定义它们需要的属性。 - PhilDulac
1
最优雅、可重用、清晰的解决方案。复制是不好的。多态是出路。+1 - T-moty
在我的情况下,一个基类有一个必需的属性,我想在我的父类中将其改为非必需。是否可以在没有两个模型副本的情况下实现? - Mando
显示剩余3条评论

38
您可以在控制器操作中使用以下代码去除属性的所有验证。
ModelState.Remove<ViewModel>(x => x.SomeProperty);

@Ian's 对于MVC5的评论

以下仍然是可能的

ModelState.Remove("PropertyNameInModel");

有点烦人的是,更新后的API使得静态类型失效了。你可以通过创建一个HTML助手实例并使用NameExtensions Methods来实现类似旧方式的功能。

除了...在MVC 5中,ModelState上没有与该签名匹配的方法。 - Ian Kemp
问题不是如何删除所有验证,而是如何删除必填字段验证。您可能希望在保留其他验证规则的同时执行此操作。 - Richard
这确实是最好的解决方案,即使在 .NET Core 中也适用,谢谢 @jps。ModelState.Remove("PropertyNameInModel"); - c-sharp-and-swiftui-devni

18

客户端 根据我的研究,以下提供了多个选项来禁用表单验证。其中一个应该可以解决您的问题。

选项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服务器端动态验证


1
$('input selector').each(function () { $(this).rules('remove');}); 帮助了我。 - Sachin Pakale
这个问题特别是关于移除必填字段验证,而不是移除所有验证。你的回答是关于移除所有验证的。 - Richard

14
个人而言,我倾向于使用Darin Dimitrov在他的解决方案中展示的方法。这样可以使您能够使用数据注释方法进行验证,并且在每个ViewModel上具有与所需任务相对应的单独数据属性。为了最大程度地减少模型和视图模型之间的复制工作,您应该查看AutoMapper或ValueInjecter。它们都有各自的优点,请检查它们两个。
另一种可能的方法是从IValidatableObject派生您的ViewModel或Model。这使您可以选择实现一个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;
}

我们能在客户端上挂钩吗? - Muhammad Adeel Zahid
客户端验证通常仅针对单个字段进行,以交互式、逐个字段的验证反馈形式为用户提供方便。由于对象验证步骤通常涉及到依赖验证(多个字段和/或外部条件),即使您可以将代码编译为JavaScript,也不能在客户端执行此操作。如果在客户端进行复杂/依赖性验证确实增加了价值,您将需要使用onsubmit回调并在客户端重复验证逻辑。 - mindplay.dk

9
我认为最干净的方法是禁用客户端验证,然后在服务器端上你需要:
  1. 在控制器中使用ModelState["SomeField"].Errors.Clear(或创建一个操作过滤器,在控制器代码执行之前删除错误)
  2. 当检测到违反已检测到的问题时,从控制器代码中添加ModelState.AddModelError。
似乎即使使用自定义视图模型也无法解决问题,因为这些“预先回答”的字段数量可能会有所不同。如果它们没有,则自定义视图模型可能确实是最简单的方法,但使用上述技术,您可以解决验证问题。

1
这正是我所需要的。我有一个视图,其中发生了不同的操作,因此我无法使用不同的ViewModel来完成它。这个方法非常有效。 - Luis Deras

7

这是评论区中别人的答案...但它应该是真正的答案:

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

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

答案来自https://stackoverflow.com/users/73382/rob


1
服务器端验证怎么样? - T-moty
ModelState.Remove,对吧?无论如何,对我来说,问题在于我的模型中包含了第二个实体...主要的实体需要进行验证,但是次要的实体在该页面上不需要进行验证...因此,在这种情况下,只需要 JQuery 就可以了。 - Mike_Matthews_II
我认为链接已经破损,请编辑。这是部分答案。 - T-moty
很奇怪你说这个在MVC6上可以运行(我目前没有测试MVC6的选项),但是在我目前使用的MVC4上却不能运行。 - eaglei22
问题是关于MVC/C#的,不是JS,答案在服务器端无法工作。 - m.t.bennett

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

我正在寻找一种解决方案,可以在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
    {

    }
}

来源:


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接