How do you get model metadata from within a ValidationAttribute?

MVC2 comes with a good example of a validation attribute called "PropertiesMustMatchAttribute" that will compare two fields to see if they match. Using this attribute is as follows:

[PropertiesMustMatch("NewPassword", "ConfirmPassword", ErrorMessage = "The new password and confirmation password do not match.")] public class ChangePasswordModel { public string NewPassword { get; set; } public string ConfirmPassword { get; set; } } 

The attribute is tied to a model class and uses a little reflection to do its job. You will also notice that an error message is displayed here: "New password and confirmation password do not match."

If you do not specify a message, the default message is created using some code like this (short for clarity):

 private const string _defaultErrorMessage = "'{0}' and '{1}' do not match."; public override string FormatErrorMessage(string name) { return String.Format(CultureInfo.CurrentUICulture, _defaultErrorMessage, OriginalProperty, ConfirmProperty); } 

The problem is that "OriginalProperty" and "ConfirmProperty" are hard-coded strings in the attribute - "NewPassword" and "ConfirmPassword" in this example. They actually do not receive real model metadata (e.g. DisplayNameAttribute) in order to assemble a more flexible, localizable message. I would like to have a more generally applicable comparison attribute that uses the display name information for the metadata, etc. that have been specified.

Assuming I don't want to create a custom error message for each instance of my ValidationAttribute, this means I need to get a reference to the model metadata (or at least the type of model I am checking), so I can get this metadata information and use it in your error messages.

How to get a link to model metadata for a model that I am checking inside an attribute?

(Although I found several questions that ask how to check dependent fields in a model, none of the answers include error message processing.)

+4
source share
1 answer

This is actually a subset of the question of how to get an instance decorated with an attribute from an attribute (similar question here ).

Unfortunately, the short answer is: you cannot.

Attributes are metadata. The attribute does not know and does not know anything about the class or member that it adorns. Some downstream class consumers should look for the specified user attributes and decide when / when / how to apply them.

You should think of attributes as data, not objects. Although attributes are technical classes, they are pretty dumb because they have a crucial limitation: everything about them needs to be determined at compile time. This actually means that they cannot access any runtime information unless they expose a method that accepts an instance of the runtime and the caller decides to call it.

You can do the last. You can create your own attribute, and as long as you control the validator, you can force the validator to call some method on the attribute and do something almost anything:

 public abstract class CustomValidationAttribute : Attribute { // Returns the error message, if any public abstract string Validate(object instance); } 

As long as someone uses this class, it uses the attribute correctly, this will work:

 public class MyValidator { public IEnumerable<string> Validate(object instance) { if (instance == null) throw new ArgumentNullException("instance"); Type t = instance.GetType(); var validationAttributes = (CustomValidationAttribute[])Attribute .GetCustomAttributes(t, typeof(CustomValidationAttribute)); foreach (var validationAttribute in validationAttributes) { string error = validationAttribute.Validate(instance); if (!string.IsNullOrEmpty(error)) yield return error; } } } 

If this is how you consume attributes, it becomes easier to implement your own:

 public class PasswordValidationAttribute : CustomValidationAttribute { public override string Validate(object instance) { ChangePasswordModel model = instance as ChangePasswordModel; if (model == null) return null; if (model.NewPassword != model.ConfirmPassword) return Resources.GetLocalized("PasswordsDoNotMatch"); return null; } } 

All this is fine and good, just the control flow is inverted from what you specified in the original question. The attribute knows nothing about what it was applied to; a validator that uses this attribute should provide this information (which can easily be done).

Of course, this is not the way validation really works with data annotations in MVC 2 (unless it has changed significantly since the last time I looked at it). I don’t think you can just plug this in using ValidationMessageFor and other similar features. But hey, in MVC 1, we still had to write all of our validators. There is nothing stopping you from combining DataAnnotations with your own validation attributes and validators; it just requires a bit more code. You will need to use a special validator wherever you write the verification code.

This is probably not the answer you are looking for, but unfortunately it is. the validation attribute cannot know about the class to which it was applied if this information is not provided by the validator itself.

+4
source

Source: https://habr.com/ru/post/1302821/


All Articles