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

我想知道是否有可能在某些控制器操作中禁用“必需的验证”属性。我想知道这一点,因为在我的一个编辑表单中,我不要求用户为他们以前已经指定的字段输入值。然而,我接下来实现的逻辑是,当他们输入一个值时,它使用一些特殊的逻辑来更新模型,比如散列一个值等等。

对如何解决这个问题有什么建议吗?

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

209915 次浏览

AFAIK 不能在运行时删除属性,只能改变它们的值(即: readonly true/false) 在这里寻找类似的东西。 作为另一种不影响属性的方法,我将使用针对特定操作的 ViewModel,这样您就可以插入所有逻辑,而不会破坏其他控制器所需的逻辑。 如果您尝试获取某种类型的向导(一个多步骤表单) ,您可以改为序列化已经编译的字段,并使用 TempData 将它们带到您的步骤中。(有关序列化反序列化的帮助,可以使用 MVC 期货)

这个问题很容易通过使用视图模型来解决。视图模型是专门为给定视图的需求量身定制的类。例如,在您的例子中,您可以有以下视图模型:

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

就我个人而言,我倾向于使用达林•迪米特洛夫(DarinDimitrov)在其解决方案中展示的方法。 这使您能够使用带有验证的数据注释方法,并且在每个 ViewModel 上对应于手头的任务具有单独的数据属性。 为了尽量减少模型和视图模型之间的复制工作量,您应该查看 AutoMapper 或 价值注入器。两者都有各自的长处,因此要同时检查它们。

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

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

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

  1. ModelState [“ Some Field”]。错误。清除(在控制器中或创建操作筛选器以在执行控制器代码之前删除错误)
  2. 当检测到违反检测到的问题时,从控制器代码中添加 ModelState.AddModelError。

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

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

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

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

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

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

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

NET MVC 3实体

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

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

选择一

我更喜欢这个,这个很适合我。

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

选择二

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

选择三

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

选择四

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

选择五

$('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 服务器端验证

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

我知道这个问题很久以前就已经有答案了,接受的答案实际上会起作用。但有一件事困扰着我: 必须复制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)
{
...
}

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

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

@ Ian 关于 MVC5的评论

以下仍然是可能的

ModelState.Remove("PropertyNameInModel");

有点烦人,你失去了与更新的 API 静态类型。您可以通过创建一个 HTML 助手实例并使用 名称扩展方法来实现类似于旧方法的东西。

这是其他人在评论中的回答... ... 但它应该是一个真正的答案:

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

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

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

是的,可以禁用“必需属性”。创建自己的自定义类属性(名为 ChangeableRedemand 的示例代码)以扩展 RequredAttribute,并添加一个 Disable Property 并覆盖 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;

这感觉很干净吗?

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

在 MVC 5中,这可以通过在 global.asax中添加这个来轻松实现。

DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false;

我正在寻找一个解决方案,其中我可以使用相同的模型插入和更新网络应用程序接口。在我的情况下,这总是一个身体的内容。如果是更新方法,则必须跳过 [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。我发现我不得不重写“浅验证”方法。在这个覆盖中,我过滤所需的模型验证器。 现在是 IgnoreRequredOrDefaultBodyModelValidator 类和 IgnoreRequredValidations 属性类:

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
{


}
}

资料来源:

在我的案例中,出于可重用性的目的,在许多页面中使用了相同的 Model。所以我所做的是,我已经创建了一个自定义属性,检查排除

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

这个对我很管用:

$('#fieldId').rules('remove', 'required');