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.
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…