7

I have created a custom CompareLessThan validation attribute by copying the ASP.NET MVC 3 CompareAttribute and instead of checking for equality, I check to see that one property is less than another. If there is a client side error, the message '{0} must be less than {1}' is displayed to the user.

My model is setup as follows with the Display attributes referencing a resource file.

[CompareLessThan("AmountAvailable", ErrorMessageResourceName="CompareLessThan", ErrorMessageResourceType = typeof(Resources.ValidationMessages))]
[Display(Name = "Amount", ResourceType = typeof(Resources.Labels))]
public decimal Amount { get; set; }

[Display(Name = "AmountAvailable", ResourceType = typeof(Resources.Labels))]
public decimal AmountAvailable { get; set; }

Then the custom validation GetClientValidationRules method is exactly the same as in the CompareAttribute

public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{            
    yield return new ModelClientValidationLessThanRule(FormatErrorMessage(metadata.DisplayName), FormatPropertyForClientValidation(OtherProperty), this.AllowEquality);
}

Here we are generating the error message that will be displayed to the user if there is a problem. I can get the display name from the resource file for the property that is decorated with my custom CompareLessThan attribute, but my question is how do I go about getting the display name of the 'other' property we are comparing against? In the IsValid method we have a reference to the validationContext from which I can generate a PropertyInfo object for the 'other' property and I think get the display name. But, in the GetClientValidationRules I don't have access to that.

I could always just pass in another value for the display name of the other property but I was hoping there would be a way to derive it as I'm already specifying it with data annotations.

Any ideas?

3 Answers 3

7

As of ASP.NET MVC 4 this is how I managed to get the other property:

PropertyInfo otherPropertyInfo =
                  this.Metadata.ContainerType.GetProperty(attribute.DependentProperty);

Then I got the Display attribute from the property:

var displayAttribute =
    otherPropertyInfo.GetCustomAttributes(typeof(DisplayAttribute), true).
    FirstOrDefault() as DisplayProperty;

In your case:

// GetName() is important to get the translated name if you're using a resource file...
this.otherPropertyDisplayName = displayAttribute.GetName();

GetName() reference:

http://msdn.microsoft.com/en-us/library/system.componentmodel.dataannotations.displayattribute.name%28v=vs.95%29.aspx

Sign up to request clarification or add additional context in comments.

2 Comments

Was that meant to be "as DisplayProperty" or "as DisplayAttribute"? Mine didn't work for DisplayProperty but worked fine for DisplayAttribute. Great solution otherwise.
@MVCKarl: I think you're right... Maybe it was a bit of confusion while typing the answer here. I can't record what I did exactly at that time. :)
6

The answer provided by nemesv didn't work as the metadata.Model property has a value of 0. But, through the metadata we do have the full name of the model so it is possible to create a new instance of that model and then create a new DataAnnonationsModelMetadataProvider from that create instance. From there we can get the display name of the other property.

public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
    Type type = Type.GetType(metadata.ContainerType.FullName);
    var model = Activator.CreateInstance(type);

    var provider = new DataAnnotationsModelMetadataProvider();
    var otherMetaData = provider.GetMetadataForProperty(() => model, type, this.OtherProperty);

    this.otherPropertyDisplayName = otherMetaData.DisplayName;

    yield return new ModelClientValidationLessThanRule(FormatErrorMessage(metadata.DisplayName), FormatPropertyForClientValidation(this.OtherProperty), this.AllowEquality);
}

I really don't like this solution (even though it works) as it seems there should be a better way. Does anyone else have any other ideas?

Comments

4

I haven't tried it out but you can get the model properties with the metadata.Properties property

metadata.Properties.Single(p => p.PropertyName == "OtherPropName").DisplayName;

EDIT: Because Properties is empty what you can always do (although it's very elegant). You can generate the metadata for yourself.

var provider = new DataAnnotationsModelMetadataProvider();
var otherMetaData = provider.GetMetadataForProperty(() => metaData.Model, metaData.ModelType, "OtherPropertyName");

3 Comments

Good thought but the Properties collection is always empty.
@NickOlsen That is sad. I've update my answer with some "workaround".
That doesn't work either as the metadata.Model property is just the value 0. Using your logic I was able to figure out another way to do it (see other answer) but I don't really like it. I'm hoping there is a better way that someone else can provide.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.