Validate String Array In ASP.NET MVC in client side - asp.net-mvc

This is my first post.
I need string array validation such like below.
[Required(ErrorMessage = "Content name is required")]
public string[] ContentName { get; set; }
I found a post which has the same situation.
This answer and following code helped me so much and I could solve my problem.
public class StringArrayRequiredAttribute : ValidationAttribute
{
protected override ValidationResult IsValid (object value, ValidationContext validationContext)
{
string[] array = value as string[];
if(array == null || array.Any(item => string.IsNullOrEmpty(item)))
{
return new ValidationResult(this.ErrorMessage);
}
else
{
return ValidationResult.Success;
}
}
}
And
[StringArrayRequired(ErrorMessage = "Content name is required")]
public string[] ContentName { get; set; }
But now I found an another problem. This validation works only server side. I wish I could have a client validation too. Because it would make my client much happier!!
So would you give me a nice way for this? Waiting for your answers!!
Thank you for your help.
I write a short code in my view.
$.validator.addMethod('stringarrayrequired', function (value, element, params) {
let array = value;
if (array == null) {
return false;
}
for (var i = 0; i < array.length; i++) {
if (!array[i]) {
return false;
}
}
return true;
}, '');
$.validator.unobtrusive.adapters.add("stringarrayrequired", function (options) {
options.rules["stringarrayrequired"] = "#" + options.element.name.replace('.', '_'); // mvc html helpers
options.messages["stringarrayrequired"] = options.message;
});
(Sorry, I'm not fluent in JS...)
And I add id="stringarrayrequired" to my . But it doesn't work.
I also checked html code. When I click the submit button, there should be a class="input-validation-error" or "valid" in input tag for "ContentName", but I couldn't find both of them.
I still need more info... Anyone help?
I found a way to solve my problem.
(I changed property name ContextName to Selection)
[Display(Name = "Selections")]
public Selection[] Selections { get; set; }
public class Selection
{
[Required(ErrorMessage = "SelectionItem is empty")]
public string SelectionItem { get; set; }
}
I use Selections for , SelectionItem for and .
As you know, [Required] attribute doesn't work for string[]. So I created a Selection class and changed string[] to Selection[], and applied [Required] attribute to string.
I know that this's not a clean way... I'll use foolproof or something.

Add the following javaScript code in your view:
$.validator.addMethod('stringarrayrequired', function (value, element, params) {
// here return true or false based on checking the input value
},'');
$.validator.unobtrusive.adapters.add("stringarrayrequired", function (options) {
options.rules["stringarrayrequired"] = "#" + options.element.name.replace('.', '_'); // mvc html helpers
options.messages["stringarrayrequired"] = options.message;
});

Related

ASP .Net Core Custom Tag Helper to Convert CamelCase Properties to spaces

Is it possible in ASP.Net Core to automatically convert camel case property names in view models to insert spaces into the corresponding labels when using tag helpers?
If my view model looks like this...
[Display(Name = "First Name")]
public string FirstName { get; set; }
[Display(Name = "Last Name")]
public string LastName { get; set; }
[Display(Name = "Referral Date")]
public DateTime ReferralDate { get; set; }
It seems like a lot of extra configuration applying data annotations such as
[Display(Name = "First Name")]
to simply insert a space between words. It would make sense that Tag Helpers would insert the space by default to avoid this manual configuration and potential typos.
If not could a custom tag helper assist in this situation and if so how would it work?
If you only care about label, you can easily override LabelTagHelper.
[HtmlTargetElement("label", Attributes = "title-case-for")]
public class TitleCaseTagHelper : LabelTagHelper
{
public TitleCaseTagHelper(IHtmlGenerator generator) : base(generator)
{
}
[HtmlAttributeName("title-case-for")]
public new ModelExpression For { get; set; }
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
if (context == null)
throw new ArgumentNullException("context");
if (output == null)
throw new ArgumentNullException("output");
string name = For.ModelExplorer.Metadata.DisplayName ?? For.ModelExplorer.Metadata.PropertyName;
name = name.Humanize(LetterCasing.Title);
TagBuilder tagBuilder = this.Generator.GenerateLabel(
this.ViewContext,
this.For.ModelExplorer,
this.For.Name,
name,
(object) null);
if (tagBuilder == null)
return;
output.MergeAttributes(tagBuilder);
if (output.IsContentModified)
return;
TagHelperContent childContentAsync = await output.GetChildContentAsync();
if (childContentAsync.IsEmptyOrWhiteSpace)
output.Content.SetHtmlContent((IHtmlContent) tagBuilder.InnerHtml);
else
output.Content.SetHtmlContent((IHtmlContent) childContentAsync);
}
}
Usage
<label title-case-for="RememberMe" class="col-md-2 control-label"></label>
Please ensure to place using statement and #addTagHelper inside _ViewImports.cshtml.
#using YourNameSpace.Helpers
#addTagHelper *, YourNameSpace
Note
I use Humanizer English Only NuGet Package - Humanizer.Core. It is more robust than writing my own method. If you doesn't like overhead, you can just use Regular Expression.
You can achieve this by building and registering a custom display metadata provider. There are libraries that will perform more elaborate "humanization" to the property names, but you can achieve something pretty useful with some trusty regular expressions.
public class HumanizerMetadataProvider : IDisplayMetadataProvider
{
public void CreateDisplayMetadata(DisplayMetadataProviderContext context)
{
var propertyAttributes = context.Attributes;
var modelMetadata = context.DisplayMetadata;
var propertyName = context.Key.Name;
if (IsTransformRequired(propertyName, modelMetadata, propertyAttributes))
{
modelMetadata.DisplayName = () => SplitCamelCase(propertyName);
}
}
private static string SplitCamelCase(string str)
{
return Regex.Replace(
Regex.Replace(
str,
#"(\P{Ll})(\P{Ll}\p{Ll})",
"$1 $2"
),
#"(\p{Ll})(\P{Ll})",
"$1 $2"
);
}
private static bool IsTransformRequired(string propertyName, DisplayMetadata modelMetadata, IReadOnlyList<object> propertyAttributes)
{
if (!string.IsNullOrEmpty(modelMetadata.SimpleDisplayProperty))
return false;
if (propertyAttributes.OfType<DisplayNameAttribute>().Any())
return false;
if (propertyAttributes.OfType<DisplayAttribute>().Any())
return false;
if (string.IsNullOrEmpty(propertyName))
return false;
return true;
}
}
The IsTransformRequired method ensures that you can still override the provider with a decorated property when you need to.
Register the provider on startup through the AddMvcOptions method on IMvcBuilder:
builder.AddMvcOptions(m => m.ModelMetadataDetailsProviders.Add(new HumanizerMetadataProvider()));
how about using a custom attribute and overriding DisplayNameAttribute
public class DisplayWithSpace : DisplayNameAttribute
{
public DisplayWithSpace([System.Runtime.CompilerServices.CallerMemberName] string memberName ="")
{
Regex r = new Regex(#"(?!^)(?=[A-Z])");
DisplayNameValue = r.Replace(memberName, " ");
}
}
and your property will be
[DisplayWithSpace]
public string FatherName { get; set; }

How to specify that at least one checkbox must be selected?

I would like to use a DataAnnotation Attribute that tells the user that he must select one checkbox of the two following checkbox groups. My model is:
//group T
public bool T0 {get;set;}
public bool T1 {get;set;}
public bool T2 {get;set;}
//group P
public bool P0 {get;set;}
public bool P1 {get;set;}
The user must select at least one of the T properties, and one of the P properties. IS there something that do that on some customized dataannotations or i need to create one from beggining?
Thanks
You may use Fluent Validation
[FluentValidation.Attributes.Validator(typeof(CustomValidator))]
public class YourModel
{
public bool T0 { get; set; }
public bool T1 { get; set; }
public bool T2 { get; set; }
}
public class CustomValidator : AbstractValidator<YourModel>
{
public CustomValidator()
{
RuleFor(x => x.T0).NotEqual(false)
.When(t => t.T1.Equals(false))
.When(t => t.T2.Equals(false))
.WithMessage("You need to select one");
}
}
Here 2 solutions you can choice anyone to use.
1.Use action rule:
a) Set “False” as the check box’s default value.
b) Add following action rule to that check box field.
If check_box_field = “False”
Set check_box_field (itself) = “true”
Then this field can’t be unchecked anymore.
2.Use validation rule. Add following validation rule to that check box field.
If check_box_field = “False”
Show ScreenTip and Message: “Need to be checked”
With this validation rule, if check box has not selected, validation error will be displayed and stop prevent form submission. Let me know if you have any question.
I figure out this solution that worked as I expected but i cant display the error messages on the view.
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class AtLeastOnePropertyAttribute : ValidationAttribute
{
public AtLeastOnePropertyAttribute(string otherProperties)
{
if (otherProperties == null)
{
throw new ArgumentNullException("otherProperties");
}
OtherProperties = otherProperties;
}
public string OtherProperties { get; private set; }
public string OtherPropertyDisplayName { get; internal set; }
public override string FormatErrorMessage(string name)
{
return String.Format(CultureInfo.CurrentCulture, ErrorMessageString, name, OtherPropertyDisplayName ?? OtherProperties);
}
public override bool IsValid(object value)
{
var typeInfo = value.GetType();
var propertiesToGet = OtherProperties.Split(',');
var values = propertiesToGet.Select(propertyName => (bool) typeInfo.GetProperty(propertyName).GetValue(value)).ToList();
return values.Any(v => v);
}
public override object TypeId
{
get
{
return new object();
}
}
}
And in the DTO class:
[AtLeastOneProperty("T0 ,T1,T2", ErrorMessage = #"At least one field should be marked as true.")]
[AtLeastOneProperty("P0,P1", ErrorMessage = #"At least one field should be marked as true.")]
public class TestDTO
{
//Properties
}
Here is another way with FluentValidation.
RuleFor(x => x).Must(x =>
{
if (!x.checkbox1 &&
!x.checkbox2 &&
!x.checkbox3)
{
return false;
}
return true;
})
.WithMessage("Please select at least one checkbox.");
You could also run a foreach loop on a list of checkboxes and verify all boxes are not checked and throw the error.

How to make a property required based on multiple condition?

I have a list of Pair of radio buttons (Yes/No):
Q1.(Y)(N)
Q2.(Y)(N)
Q3.(Y)(N)
Q4.(Y)(N)
and I have one property in my model
public string MedicalExplanation { get; set; }
My goal is to make Explanation required if any of the radio button has been set to true.
My first try was to use [Required] but it does not handle conditions.
Then I decided to use third party tool like MVC Foolproof Validation
I used it like this:
[RequiredIf("Q1", true, ErrorMessage = "You must explain any \"Yes\" answers!")]
Now the problem is I don't know how to make it required if any of the other Q2, Q3, Q4 is checked.
Please advice
In your ViewModel, create a bool property like this:
public bool IsMedicalExplanationRequired
{
get
{
return Q1 || Q2 || Q3 || Q4;
}
}
Then, use your RequiredIf attribute like this:
[RequiredIf("IsMedicalExplanationRequired", true, ErrorMessage = "You must explain any \"Yes\" answers!")]
UPDATE:
If your Q1 - Q4 properties are of type bool?, just change the IsMedicalExplanationRequired property like below:
public bool IsMedicalExplanationRequired
{
get
{
return Q1.GetValueOrDefault() || Q2.GetValueOrDefault() || Q3.GetValueOrDefault() || Q4.GetValueOrDefault();
}
}
This is how I did it:
First I created a custom validation attribute which gets a string array of fields to check passed in:
public class ValidateAtLeastOneChecked : ValidationAttribute {
public string[] CheckBoxFields {get; set;}
public ValidateAtLeastOneChecked(string[] checkBoxFields) {
CheckBoxFields = checkBoxFields;
}
protected override ValidationResult IsValid(Object value, ValidationContext context) {
Object instance = context.ObjectInstance;
Type type = instance.GetType();
foreach(string s in CheckBoxFields) {
Object propertyValue = type.GetProperty(s).GetValue(instance, null);
if (bool.Parse(propertyValue.ToString())) {
return ValidationResult.Success;
}
}
return new ValidationResult(base.ErrorMessageString);
}
}
Then I use it like this (I am using resource files to localize my error messages):
[ValidateAtLeastOneChecked(new string[] { "Checkbox1", "Checkbox2", "Checkbox3", "Checkbox4" }, ErrorMessageResourceType=typeof(ErrorMessageResources),ErrorMessageResourceName="SelectAtLeastOneTopic")]
public bool Checkbox1{ get; set; }
public bool Checkbox2{ get; set; }
public bool Checkbox3{ get; set; }
public bool Checkbox4{ get; set; }
It is only actually setting the error on the first checkbox. If you are using the built in css highlighting to highlight fields in error you will need to modify this slightly to make it look right, but I felt this was a clean solution which was reusable and allowed me to take advantage of the support for resource files in validation attributes.

How to apply knockout validation to properties every item List passed by MVC?

The following is a view models in ASP.NET MVC:
public class Email
{
public string Selected { get; set; }
public string Name { get; set; }
}
public class User
{
public string UserName { get; set; }
public IList<Email> Emails { get; set; }
}
It is passed into the view and looks like this while defining the knockout view model:
var viewModel = {
UserName: ko.observable("#Model.UserName"),
Emails: ko.observableArray(#Html.Json(#Model.Emails) || []),
// many other things
}
I want to make sure that Email.Selected is required. How to do it having the list, rather than single objects. I know it must be the easiest question, but I couldn't find anything on the topic.
I use knockout validation on the client, since Fluent and data annotation aren't working with knockout unfortunately.
As i mentioned on my comment knockout custom validation can helps, here i am adding an example of how to use knockout custom validation in your case :
var viewModel = {
UserName: ko.observable("#Model.UserName"),
Emails: ko.observableArray(#Html.Json(#Model.Emails) || []).
extend({
customRequired: { message: 'Selected is required' }
});
// many other things
}
ko.validation.rules['customRequired'] = {
validator: function (emailList) {
var pass = true;
ko.utils.arrayFirst(emailList(), function(email){
if(email.Selected == '') //if Selected property is empty
{
pass = false;
return true; //break the loop
}
});
return pass;
}
};

MVC3 Validation - Require One From Group

Given the following viewmodel:
public class SomeViewModel
{
public bool IsA { get; set; }
public bool IsB { get; set; }
public bool IsC { get; set; }
//... other properties
}
I wish to create a custom attribute that validates at least one of the available properties are true. I envision being able to attach an attribute to a property and assign a group name like so:
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; }
}
I would like to validate on the client-side prior to form submission as values in the form change which is why I prefer to avoid a class-level attribute if possible.
This would require both the server-side and client-side validation to locate all properties having identical group name values passed in as the parameter for the custom attribute. Is this possible? Any guidance is much appreciated.
Here's one way to proceed (there are other ways, I am just illustrating one that would match your view model as is):
[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;
}
}
Now, let's define a controller:
public class HomeController : Controller
{
public ActionResult Index()
{
var model = new SomeViewModel();
return View(model);
}
[HttpPost]
public ActionResult Index(SomeViewModel model)
{
return View(model);
}
}
and a view:
#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" />
}
The last part that's left would be to register adapters for the client side 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;
}, '');
Based on your specific requirements the code might be adapted.
Use of require_from_group from jquery-validation team:
jQuery-validation project has a sub-folder in src folder called additional.
You can check it here.
In that folder we have a lot of additional validation methods that are not common that is why they're not added by default.
As you see in that folder it exists so many methods that you need to choose by picking which validation method you actually need.
Based on your question, the validation method you need is named require_from_group from additional folder.
Just download this associated file which is located here and put it into your Scripts application folder.
The documentation of this method explains this:
Lets you say "at least X inputs that match selector Y must be filled."
The end result is that neither of these inputs:
...will validate unless at least one of them is filled.
partnumber: {require_from_group: [1,".productinfo"]},
description: {require_from_group: [1,".productinfo"]}
options[0]: number of fields that must be filled in the group
options2: CSS selector that defines the group of conditionally required fields
Why you need to choose this implementation :
This validation method is generic and works for every input (text, checkbox, radio etc), textarea and select. This method also let you specify the minimum number of required inputs that need to be filled e.g
partnumber: {require_from_group: [2,".productinfo"]},
category: {require_from_group: [2,".productinfo"]},
description: {require_from_group: [2,".productinfo"]}
I created two classes RequireFromGroupAttribute and RequireFromGroupFieldAttribute that will help you on both server-side and client-side validations
RequireFromGroupAttribute class definition
RequireFromGroupAttribute only derives from Attribute. The class is use just for configuration e.g. setting the number of fields that need to be filled for the validation. You need to provide to this class the CSS selector class that will be used by the validation method to get all elements on the same group. Because the default number of required fields is 1 then this attribute is only used to decorate your model if the minimum requirement in the spcefied group is greater than the default number.
[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 class definition
RequireFromGroupFieldAttribute which derives from ValidationAttribute and implements IClientValidatable. You need to use this class on each property in your model that participates to your group validation. You must pass the css selector class.
[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;
}
}
How to use it in your view model?
In your model here is how to use it :
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; }
}
By default you don't need to decorate your model with RequireFromGroupAttribute because the default number of required fields is 1. But if you want a number of required fields to be different to 1 you can do the following :
[RequireFromGroup(GroupOne, Number = 2)]
public class SomeViewModel
{
//...
}
How to use it in your view code?
#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" />
}
Notice the group selector you specified when using RequireFromGroupField attribute is use in your view by specifing it as a class in each input involved in your groups.
That is all for the server side validation.
Let's talk about the client side validation.
If you check the GetClientValidationRules implementation in RequireFromGroupFieldAttribute class you will see I'm using the string requirefromgroup and not require_from_group as the name of method for the ValidationType property. That is because ASP.Net MVC only allows the name of the validation type to contain alphanumeric char and must not start with a number. So you need to add the following 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;
});
The javascript part is really simple because in the implementation of the adaptater function we just delegate the validation to the correct require_from_group method.
Because it works with every type of input, textarea and select elements, I may think this way is more generic.
Hope that helps!
I implemented Darin's awesome answer into my application, except I added it for strings and not boolean values. This was for stuff like name/company, or phone/email. I loved it except for one minor nitpick.
I tried to submit my form without a work phone, mobile phone, home phone, or email. I got four separate validation errors client side. This is fine by me because it lets the users know exactly what field(s) can be filled in to make the error go away.
I typed in an email address. Now the single validation under email went away, but the three remained under the phone numbers. These are also no longer errors anymore.
So, I reassigned the jQuery method that checks validation to account for this. Code below. Hope it helps someone.
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;
};
I know this is an old thread but I just came across the same scenario and found a few solutions and saw one that solves Matt's question above so I thought I would share for those who come across this answer. Check out: MVC3 unobtrusive validation group of inputs

Resources