MVC 3 自定义 DataAnnotation:将错误消息与特定属性相关联

发布于 2024-12-07 06:02:17 字数 845 浏览 1 评论 0原文

我定义了一个自定义 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")

如果我必须将错误消息与特定的关联起来也很好我的模型上的属性而不是任意字符串。我怎样才能做到这一点?

I've defined a custom DataAnnotation attribute similar to this one that goes on the class but ensures that at least one property is populated. It works correctly and adds an error message to the model's ValidationSummary. However, I want to be able to associate the error message with a particular property (or any string, really) so that I can display it in a particular place on my view.

Thus, if my custom attribute is used like this:

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

then I want to be able to say something like:

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

...and use it in a view like this:

@Html.ValidationMessage("my_key")

It would also be fine if I had to associate the error message with a particular property on my model instead of an arbitrary string. How can I accomplish this?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

何以畏孤独 2024-12-14 06:02:17

使用ryudice的答案和<一href="https://stackoverflow.com/questions/3400542/how-do-i-use-ivalidatableobject">这个问题作为起点,我能够使用 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 - 在此示例中、OwnerPhoneAgentPhone

@Html.ValidationMessage("OwnerPhone")

注意事项

内置验证还会向 ValidationSummary 集合添加一条错误消息,但仅限于模型上定义的第一个属性。因此,在此示例中,只有 OwnerPhone 的消息会显示在 ValidationSummary 中,因为它是首先在模型上定义的。我还没有寻找解决这个问题的方法,因为对我来说这并不重要。

Using ryudice's answer and this question as the starting point, I was able to solve this problem using IValidatableObject. For anyone interested, here is the full code I ended up with:

1. Define a custom validation attribute, RequireAtLeastOneAttribute

This attribute goes on the class to indicate that validation should check for property groups and ensure that at least one property from each group is populated. This attribute also defines the error message and an ErrorMessageKey, which will be used to keep track of the error messages and display them in the view instead of using the general-purpose ValidationSummary collection.

[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. Define a custom attribute PropertyGroupAttribute

This will be used to define which property groups need to have at least one value filled in.

[AttributeUsage(AttributeTargets.Property)]
public class PropertyGroupAttribute : Attribute {

    public PropertyGroupAttribute(int groupId) {
        this.GroupId = groupId;
    }

    public int GroupId { get; set; }

}

3. Attach the attributes to the model and properties

Group properties together using the "GroupId" integer (which can be anything, as long as it's the same for all properties among which at least one must be filled in).

[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. Implement IValidatableObject on the model

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. Use the validation messages in the view

The validation messages will be saved as whatever ErrorMessageKey you specified in the RequireAtLeastOne attribute definition - in this example, OwnerPhone and AgentPhone.

@Html.ValidationMessage("OwnerPhone")

Caveats

The built-in validation also adds an error message to the ValidationSummary collection, but only for the first attribute defined on the model. So in this example, only the message for OwnerPhone would show up in ValidationSummary, since it was defined first on the model. I haven't looked for a way around this because in my case it didn't matter.

染火枫林 2024-12-14 06:02:17

您可以在模型上实现 IValidatableObject 并在那里执行自定义逻辑,它将允许您使用您想要的任何键添加消息。

You can implement IValidatableObject on your model and do the custom logic there, it will let you add the message using whichever key you want.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文