Пользовательский DataAnnotation MVC 3: связать сообщение об ошибке с определенным свойством - PullRequest
1 голос
/ 29 сентября 2011

Я определил пользовательский атрибут DataAnnotation, похожий на этот , который идет в классе, но гарантирует заполнение хотя бы одного свойства. Он работает правильно и добавляет сообщение об ошибке в модель ValidationSummary. Однако я хочу иметь возможность связать сообщение об ошибке с определенным свойством (или с любой строкой, на самом деле), чтобы я мог отобразить его в определенном месте в моем представлении.

Таким образом, если мой пользовательский атрибут используется так:

[RequireAtLeastOne(GroupId = 0, ErrorMessage = "You must specify at least one owner phone number.")]
public class UserViewModel: User {
    ...
}

тогда я хочу иметь возможность сказать что-то вроде:

[RequireAtLeastOne(GroupId = 0, ErrorMessage = "You must specify at least one owner phone number.", ValidationErrorKey = "my_key")]
public class UserViewModel: User {
    ...
}

... и используйте его в таком виде:

@Html.ValidationMessage("my_key")

Было бы также хорошо, если бы мне пришлось связать сообщение об ошибке с определенным свойством в моей модели вместо произвольной строки. Как мне это сделать?

Ответы [ 2 ]

4 голосов
/ 30 сентября 2011

Используя ответ Рюдице и этот вопрос в качестве отправной точки, я смог решить эту проблему, используя 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, так как оно было определено первым в модели.Я не искал способ обойти это, потому что в моем случае это не имело значения.

1 голос
/ 29 сентября 2011

Вы можете реализовать IValidatableObject на своей модели и выполнять там собственную логику, это позволит вам добавить сообщение, используя любую нужную клавишу.

...