MVC3validation – 要求一个来自组
鉴于以下视图模型:
public class SomeViewModel { public bool IsA { get; set; } public bool IsB { get; set; } public bool IsC { get; set; } //... other properties }
我希望创build一个自定义属性,validation至less有一个可用的属性是真实的。 我设想能够将一个属性附加到一个属性,并像这样分配一个组名:
public class SomeViewModel { [RequireAtLeastOneOfGroup("Group1")] public bool IsA { get; set; } [RequireAtLeastOneOfGroup("Group1")] public bool IsB { get; set; } [RequireAtLeastOneOfGroup("Group1")] public bool IsC { get; set; } //... other properties [RequireAtLeastOneOfGroup("Group2")] public bool IsY { get; set; } [RequireAtLeastOneOfGroup("Group2")] public bool IsZ { get; set; } }
我想在表单提交之前在客户端validation表单中的值,如果可能的话,我更喜欢避免使用类级别的属性。
这将要求服务器端和客户端validation都定位具有相同组名称值的所有属性作为自定义属性的参数传入。 这可能吗? 任何指导,非常感谢。
这里有一个方法可行(还有其他方法,我只是说明一个与你的视图模型相匹配的方法):
[AttributeUsage(AttributeTargets.Property)] public class RequireAtLeastOneOfGroupAttribute: ValidationAttribute, IClientValidatable { public RequireAtLeastOneOfGroupAttribute(string groupName) { ErrorMessage = string.Format("You must select at least one value from group \"{0}\"", groupName); GroupName = groupName; } public string GroupName { get; private set; } protected override ValidationResult IsValid(object value, ValidationContext validationContext) { foreach (var property in GetGroupProperties(validationContext.ObjectType)) { var propertyValue = (bool)property.GetValue(validationContext.ObjectInstance, null); if (propertyValue) { // at least one property is true in this group => the model is valid return null; } } return new ValidationResult(FormatErrorMessage(validationContext.DisplayName)); } private IEnumerable<PropertyInfo> GetGroupProperties(Type type) { return from property in type.GetProperties() where property.PropertyType == typeof(bool) let attributes = property.GetCustomAttributes(typeof(RequireAtLeastOneOfGroupAttribute), false).OfType<RequireAtLeastOneOfGroupAttribute>() where attributes.Count() > 0 from attribute in attributes where attribute.GroupName == GroupName select property; } public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context) { var groupProperties = GetGroupProperties(metadata.ContainerType).Select(p => p.Name); var rule = new ModelClientValidationRule { ErrorMessage = this.ErrorMessage }; rule.ValidationType = string.Format("group", GroupName.ToLower()); rule.ValidationParameters["propertynames"] = string.Join(",", groupProperties); yield return rule; } }
现在,我们来定义一个控制器:
public class HomeController : Controller { public ActionResult Index() { var model = new SomeViewModel(); return View(model); } [HttpPost] public ActionResult Index(SomeViewModel model) { return View(model); } }
和一个观点:
@model SomeViewModel <script src="@Url.Content("~/Scripts/jquery.validate.js")" type="text/javascript"></script> <script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.js")" type="text/javascript"></script> @using (Html.BeginForm()) { @Html.EditorFor(x => x.IsA) @Html.ValidationMessageFor(x => x.IsA) <br/> @Html.EditorFor(x => x.IsB)<br/> @Html.EditorFor(x => x.IsC)<br/> @Html.EditorFor(x => x.IsY) @Html.ValidationMessageFor(x => x.IsY) <br/> @Html.EditorFor(x => x.IsZ)<br/> <input type="submit" value="OK" /> }
剩下的最后一部分是注册用于客户端validation的适配器:
jQuery.validator.unobtrusive.adapters.add( 'group', [ 'propertynames' ], function (options) { options.rules['group'] = options.params; options.messages['group'] = options.message; } ); jQuery.validator.addMethod('group', function (value, element, params) { var properties = params.propertynames.split(','); var isValid = false; for (var i = 0; i < properties.length; i++) { var property = properties[i]; if ($('#' + property).is(':checked')) { isValid = true; break; } } return isValid; }, '');
根据您的具体要求,可能会修改代码。
我实现Darin真棒的答案到我的应用程序,除了我添加它的string,而不是布尔值。 这是为了像名称/公司,或电话/电子邮件。 我喜欢它,除了一个小挑逗。
我试图在没有工作电话,手机,家庭电话或电子邮件的情况下提交表格。 我有四个单独的validation错误客户端。 这对我来说很好,因为它让用户确切地知道可以填写哪些字段以使错误消失。
我input一个电子邮件地址。 现在,电子邮件中的单个validation消失了,但三个仍然在电话号码下。 这些也不再是错误了。
所以,我重新分配了检查validation的jQuery方法来解决这个问题。 下面的代码。 希望它可以帮助别人。
jQuery.validator.prototype.check = function (element) { var elements = []; elements.push(element); var names; while (elements.length > 0) { element = elements.pop(); element = this.validationTargetFor(this.clean(element)); var rules = $(element).rules(); if ((rules.group) && (rules.group.propertynames) && (!names)) { names = rules.group.propertynames.split(","); names.splice($.inArray(element.name, names), 1); var name; while (name = names.pop()) { elements.push($("#" + name)); } } var dependencyMismatch = false; var val = this.elementValue(element); var result; for (var method in rules) { var rule = { method: method, parameters: rules[method] }; try { result = $.validator.methods[method].call(this, val, element, rule.parameters); // if a method indicates that the field is optional and therefore valid, // don't mark it as valid when there are no other rules if (result === "dependency-mismatch") { dependencyMismatch = true; continue; } dependencyMismatch = false; if (result === "pending") { this.toHide = this.toHide.not(this.errorsFor(element)); return; } if (!result) { this.formatAndAdd(element, rule); return false; } } catch (e) { if (this.settings.debug && window.console) { console.log("Exception occurred when checking element " + element.id + ", check the '" + rule.method + "' method.", e); } throw e; } } if (dependencyMismatch) { return; } if (this.objectLength(rules)) { this.successList.push(element); } } return true; };
使用jquery-validation团队中的require_from_group
:
jQueryvalidation项目在src文件夹中有一个名为additional的子文件夹。 你可以在这里检查。
在那个文件夹中,我们有很多额外的validation方法,这些方法并不常见,这就是为什么它们没有默认添加的原因。
正如你在该文件夹中看到的,它存在很多方法,你需要通过select你实际需要的validation方法来select。
根据您的问题,您需要的validation方法将从附加文件夹命名为require_from_group
。 只要下载这个位于这里的相关文件,并将其放入您的Scripts
应用程序文件夹。
这个方法的文档解释了这个:
让你说“至less有X个匹配select符Y的input必须被填充”。
最终的结果是这些input都不是:
…将validation,除非至less其中一个被填充。
partnumber:{require_from_group:[1,“。productinfo”]},description:{require_from_group:[1,“。productinfo”]}
选项[0]:必须在组选项中填充的字段数量2 :定义有条件的必填字段组的CSSselect器
为什么你需要select这个实现:
这种validation方法是通用的,适用于每个input
(文本,checkbox,广播等), textarea
和select
。 这个方法还可以让你指定需要填充的最小数量的input,例如
partnumber: {require_from_group: [2,".productinfo"]}, category: {require_from_group: [2,".productinfo"]}, description: {require_from_group: [2,".productinfo"]}
我创build了两个类RequireFromGroupAttribute
和RequireFromGroupFieldAttribute
,这将帮助您在服务器端和客户端validation
RequireFromGroupAttribute
类的定义
RequireFromGroupAttribute
只从Attribute
派生。 该类仅用于configuration,例如,设置需要填充的validation字段数。 您需要向此类提供将由validation方法使用的CSSselect器类,以获取同一组中的所有元素。 因为所需字段的缺省数量是1,所以如果spcefied组中的最小需求大于默认数字,则此属性仅用于修饰您的模型。
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] public class RequireFromGroupAttribute : Attribute { public const short DefaultNumber = 1; public string Selector { get; set; } public short Number { get; set; } public RequireFromGroupAttribute(string selector) { this.Selector = selector; this.Number = DefaultNumber; } public static short GetNumberOfRequiredFields(Type type, string selector) { var requiredFromGroupAttribute = type.GetCustomAttributes<RequireFromGroupAttribute>().SingleOrDefault(a => a.Selector == selector); return requiredFromGroupAttribute?.Number ?? DefaultNumber; } }
RequireFromGroupFieldAttribute
类的定义
RequireFromGroupFieldAttribute
从ValidationAttribute
派生并实现IClientValidatable
。 您需要在模型中的每个属性上使用此类,并参与您的组validation。 你必须通过cssselect器类。
[AttributeUsage(AttributeTargets.Property)] public class RequireFromGroupFieldAttribute : ValidationAttribute, IClientValidatable { public string Selector { get; } public bool IncludeOthersFieldName { get; set; } public RequireFromGroupFieldAttribute(string selector) : base("Please fill at least {0} of these fields") { this.Selector = selector; this.IncludeOthersFieldName = true; } protected override ValidationResult IsValid(object value, ValidationContext validationContext) { var properties = this.GetInvolvedProperties(validationContext.ObjectType); ; var numberOfRequiredFields = RequireFromGroupAttribute.GetNumberOfRequiredFields(validationContext.ObjectType, this.Selector); var values = new List<object> { value }; var otherPropertiesValues = properties.Where(p => p.Key.Name != validationContext.MemberName) .Select(p => p.Key.GetValue(validationContext.ObjectInstance)); values.AddRange(otherPropertiesValues); if (values.Count(s => !string.IsNullOrWhiteSpace(Convert.ToString(s))) >= numberOfRequiredFields) { return ValidationResult.Success; } return new ValidationResult(this.GetErrorMessage(numberOfRequiredFields, properties.Values), new List<string> { validationContext.MemberName }); } public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context) { var properties = this.GetInvolvedProperties(metadata.ContainerType); var numberOfRequiredFields = RequireFromGroupAttribute.GetNumberOfRequiredFields(metadata.ContainerType, this.Selector); var rule = new ModelClientValidationRule { ValidationType = "requirefromgroup", ErrorMessage = this.GetErrorMessage(numberOfRequiredFields, properties.Values) }; rule.ValidationParameters.Add("number", numberOfRequiredFields); rule.ValidationParameters.Add("selector", this.Selector); yield return rule; } private Dictionary<PropertyInfo, string> GetInvolvedProperties(Type type) { return type.GetProperties() .Where(p => p.IsDefined(typeof(RequireFromGroupFieldAttribute)) && p.GetCustomAttribute<RequireFromGroupFieldAttribute>().Selector == this.Selector) .ToDictionary(p => p, p => p.IsDefined(typeof(DisplayAttribute)) ? p.GetCustomAttribute<DisplayAttribute>().Name : p.Name); } private string GetErrorMessage(int numberOfRequiredFields, IEnumerable<string> properties) { var errorMessage = string.Format(this.ErrorMessageString, numberOfRequiredFields); if (this.IncludeOthersFieldName) { errorMessage += ": " + string.Join(", ", properties); } return errorMessage; } }
如何在你的视图模型中使用它?
在你的模型中,如何使用它:
public class SomeViewModel { internal const string GroupOne = "Group1"; internal const string GroupTwo = "Group2"; [RequireFromGroupField(GroupOne)] public bool IsA { get; set; } [RequireFromGroupField(GroupOne)] public bool IsB { get; set; } [RequireFromGroupField(GroupOne)] public bool IsC { get; set; } //... other properties [RequireFromGroupField(GroupTwo)] public bool IsY { get; set; } [RequireFromGroupField(GroupTwo)] public bool IsZ { get; set; } }
默认情况下,您不需要使用RequireFromGroupAttribute
来修饰模型,因为默认的必填字段数是1.但是如果您希望一些必填字段与1不同,您可以执行以下操作:
[RequireFromGroup(GroupOne, Number = 2)] public class SomeViewModel { //... }
如何在你的视图代码中使用它?
@model SomeViewModel <script src="@Url.Content("~/Scripts/jquery.validate.js")" type="text/javascript"></script> <script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.js")" type="text/javascript"></script> <script src="@Url.Content("~/Scripts/require_from_group.js")" type="text/javascript"></script> @using (Html.BeginForm()) { @Html.CheckBoxFor(x => x.IsA, new { @class="Group1"})<span>A</span> @Html.ValidationMessageFor(x => x.IsA) <br /> @Html.CheckBoxFor(x => x.IsB, new { @class = "Group1" }) <span>B</span><br /> @Html.CheckBoxFor(x => x.IsC, new { @class = "Group1" }) <span>C</span><br /> @Html.CheckBoxFor(x => x.IsY, new { @class = "Group2" }) <span>Y</span> @Html.ValidationMessageFor(x => x.IsY) <br /> @Html.CheckBoxFor(x => x.IsZ, new { @class = "Group2" })<span>Z</span><br /> <input type="submit" value="OK" /> }
请注意,使用RequireFromGroupField
属性时指定的组select器在您的视图中使用,方法是将其指定为组中涉及的每个input中的类。
这就是所有的服务器端validation。
我们来谈谈客户端validation。
如果您在RequireFromGroupFieldAttribute
类中检查GetClientValidationRules
实现,您会看到我使用stringrequirefromgroup
而不是require_from_group
作为ValidationType
属性的方法的名称。 这是因为ASP.Net MVC只允许validationtypes的名称包含字母数字字符,并且不能以数字开头。 所以你需要添加以下的JavaScript:
$.validator.unobtrusive.adapters.add("requirefromgroup", ["number", "selector"], function (options) { options.rules["require_from_group"] = [options.params.number, options.params.selector]; options.messages["require_from_group"] = options.message; });
javascript部分非常简单,因为在adaptater函数的实现中,我们只是将validation委托给正确的require_from_group
方法。
因为它适用于每种types的input
, textarea
和select
元素,所以我可能认为这种方式更通用。
希望有所帮助!
我知道这是一个古老的线程,但我刚刚遇到相同的情况,并find了一些解决scheme,看到一个解决上面的马特的问题,所以我想我会分享给那些遇到这个答案。 签出: MVC3不显眼的validation组的input