Используя ответ Рюдице и этот вопрос в качестве отправной точки, я смог решить эту проблему, используя 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.Используйте сообщения проверки в представлении
Сообщения проверки будут сохранены как ErrorMessageKey
, указанное вами в определении атрибута RequireAtLeastOne
- в этом примере OwnerPhone
и AgentPhone
.
@Html.ValidationMessage("OwnerPhone")
Предостережения
Встроенная проверка также добавляет сообщение об ошибке в коллекцию ValidationSummary
, , но только для первого атрибута, определенного в модели .Таким образом, в этом примере только сообщение для OwnerPhone
будет отображаться в ValidationSummary
, так как оно было определено первым в модели.Я не искал способ обойти это, потому что в моем случае это не имело значения.