Setting a default ErrorMessage for a custom ASP.NET MVC ValidationAttribute
I've created a custom ASP.NET MVC ValidationAttribute and I'm trying to set my own default error message on it upon instantiation by passing a string to the base constructor:
public class BusinessLogicRegex : ValidationAttribute, IClientValidatable
{
private const string _defaultErrorMessage = "Invalid Password. {0}";
private string _description;
//Other private members
...
public BusinessLogicRegex(string getMember, Type getMemberType, string descriptionMember, Type descriptionMemberType)
: base(_defaultErrorMessage)
{
//Omitted the guts of initializing validation for brevity
...
}
public override string FormatErrorMessage(string name)
{
return String.Format(ErrorMessage, _description);
}
}
But when FormatErrorMessage is called, ErrorMessage is null. Why doesn't this base(_defaultErrorMessage)
set the ErrorMessage property and how should I set it while still giving the user of this attribute the ability to override it?
Edit - 2nd, cleaner Example:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class TestValidator : ValidationAttribute
{
public TestValidator()
: base("Test Error on {0}")
{
}
public override string FormatErrorMessage(string name)
{
return String.Format(ErrorMessage, name);
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
}
}
Here again, FormatErrorMessage throws a null reference exception because ErrorMessage
is null. However ErrorMessageString
== "Test Error on {0}". So my mistake seems to be in thinking that calling base("Test Error on {0}")
sets the ErrorMessage property. I don't understand this at all. Here how the base constructor describes its errorMessage
parameter:
The error message to associate with a validation control
Here is what MSDN says about the ErrorMessageString
property:
The error message string is obtained by evaluating the ErrorMessage property or by evaluating the ErrorMessageResourceType and ErrorMessageResourceName properties. The two cases are mutually exclusive. The second case is used if you want to display a localized error message.
Yet inside of FormatErrorMessage at runtime, ErrorMessage
, ErrorMessageResourceType
and ErrorMessageResourceName
are all null. So that description makes it sound like ErrorMessageString
should also be null to me. This leaves me very confused about the usage and interaction between all of these properties and the constructor.
So I looked at the source code and I see why it's confusing. The ValidationAttribute class maintains a private _defaultErrorMessage field of its own. When I call base(errorMessage) it forwards my string as a lambda to the constructor that accepts a Func<string> errorMessageAccessor
and sets that to a private _errorMessageResourceAccessor
field. The ErrorMessageString
getter first calls a method named SetupResourceAccessor
where it determines whether to use
The ErrorMessage
property is supported by a private _errorMessage
field that appears to only be set by the ErrorMessage
property setter. If _errorMessageResourceAccessor
is null (which it is not in my case because I set it in the base constructor), this method will decide whether to set _errorMessageResourceAccessor
to the default error message, a localized resource message, or the ErrorMessage
property. However, if it is not null, it will leave _errorMessageResourceAccessor
set to its current value. This is the case I am hitting. Lastly, the ErrorMessage
property is supported by an _errorMessage field which appears to only get set by the ErrorMessage
setter. The getter will return the default error message if ErrorMessage
has not be explicitly set. Once the setter is called, it clears _errorMessageResourceAccessor
.
So to try to summarize, calling ErrorMessage
will give you the explicitly set error message or the default error message (which I actually don't know where it gets set. Maybe by an inheriting class?). Calling ErrorMessageString
will give you a message in what looks to be the following order of presedence: