使用ryudice's answer 和this question 作为起点,我能够使用IValidatableObject 解决这个问题。对于任何感兴趣的人,这是我最终得到的完整代码:
1。定义一个自定义验证属性,RequireAtLeastOneAttribute
该属性在类上指示验证应检查属性组并确保每个组中至少填充一个属性。该属性还定义了错误消息和一个ErrorMessageKey,它将用于跟踪错误消息并将它们显示在视图中,而不是使用通用的ValidationSummary 集合。
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class RequireAtLeastOneAttribute: ValidationAttribute {
/// <summary>
/// This identifier is used to group properties together.
/// Pick a number and assign it to each of the properties
/// among which you wish to require one.
/// </summary>
public int GroupId { get; set; }
/// <summary>
/// This defines the message key any errors will be associated
/// with, so that they can be accessed via the front end using
/// @Html.ValidationMessage(errorMessageKey).
/// </summary>
public string ErrorMessageKey { get; set; }
public override bool IsValid(object value) {
// Find all properties on the class having a "PropertyGroupAttribute"
// with GroupId matching the one on this attribute
var typeInfo = value.GetType();
var propInfo = typeInfo.GetProperties();
foreach (var prop in propInfo) {
foreach (PropertyGroupAttribute attr in prop.GetCustomAttributes(typeof(PropertyGroupAttribute), false)) {
if (attr.GroupId == this.GroupId
&& !string.IsNullOrWhiteSpace(prop.GetValue(value, null).GetString())) {
return true;
}
}
}
return false;
}
}
2。定义自定义属性PropertyGroupAttribute
这将用于定义哪些属性组需要至少填写一个值。
[AttributeUsage(AttributeTargets.Property)]
public class PropertyGroupAttribute : Attribute {
public PropertyGroupAttribute(int groupId) {
this.GroupId = groupId;
}
public int GroupId { get; set; }
}
3。将属性附加到模型和属性
使用“GroupId”整数将属性组合在一起(可以是任何值,只要所有属性都相同,其中至少一个必须填写)。
[RequireAtLeastOne(GroupId = 0, ErrorMessage = "You must specify at least one owner phone number.", ErrorMessageKey = "OwnerPhone")]
[RequireAtLeastOne(GroupId = 1, ErrorMessage = "You must specify at least one authorized producer phone number.", ErrorMessageKey = "AgentPhone")]
public class User: IValidatableObject {
#region Owner phone numbers
// At least one is required
[PropertyGroup(0)]
public string OwnerBusinessPhone { get; set; }
[PropertyGroup(0)]
public string OwnerHomePhone { get; set; }
[PropertyGroup(0)]
public string OwnerMobilePhone { get; set; }
#endregion
#region Agent phone numbers
// At least one is required
[PropertyGroup(1)]
public string AgentBusinessPhone { get; set; }
[PropertyGroup(1)]
public string AgentHomePhone { get; set; }
[PropertyGroup(1)]
public string AgentMobilePhone { get; set; }
#endregion
}
4。在模型上实现 IValidatableObject
public class User: IValidatableObject {
...
#region IValidatableObject Members
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) {
var results = new List<ValidationResult>();
// This keeps track of whether each "RequireAtLeastOne" group has been satisfied
var groupStatus = new Dictionary<int, bool>();
// This stores the error messages for each group as defined
// by the RequireAtLeastOneAttributes on the model
var errorMessages = new Dictionary<int, ValidationResult>();
// Find all "RequireAtLeastOne" property validators
foreach (RequireAtLeastOneAttribute attr in Attribute.GetCustomAttributes(this.GetType(), typeof(RequireAtLeastOneAttribute), true)) {
groupStatus.Add(attr.GroupId, false);
errorMessages[attr.GroupId] = new ValidationResult(attr.ErrorMessage, new string[] { attr.ErrorMessageKey });
}
// For each property on this class, check to see whether
// it's got a PropertyGroup attribute, and if so, see if
// it's been populated, and if so, mark that group as "satisfied".
var propInfo = this.GetType().GetProperties();
bool status;
foreach (var prop in propInfo) {
foreach (PropertyGroupAttribute attr in prop.GetCustomAttributes(typeof(PropertyGroupAttribute), false)) {
if (groupStatus.TryGetValue(attr.GroupId, out status) && !status
&& !string.IsNullOrWhiteSpace(prop.GetValue(this, null).GetString())) {
groupStatus[attr.GroupId] = true;
}
}
}
// If any groups did not have at least one property
// populated, add their error messages to the
// validation result.
foreach (var kv in groupStatus) {
if (!kv.Value) {
results.Add(errorMessages[kv.Key]);
}
}
return results;
}
#endregion
}
5。在视图中使用验证消息
验证消息将保存为您在RequireAtLeastOne 属性定义中指定的任何ErrorMessageKey - 在本例中为OwnerPhone 和AgentPhone。
@Html.ValidationMessage("OwnerPhone")
注意事项
内置验证还会向ValidationSummary 集合添加错误消息,但仅限于模型上定义的第一个属性。所以在这个例子中,只有OwnerPhone 的消息会显示在ValidationSummary 中,因为它首先在模型上定义。我还没有找到解决这个问题的方法,因为在我的情况下这并不重要。