I am working with data annotations in my MVC 4 application to handle validation. One requirement of this was to fully localise the all error messages and the regular expressions.
In order to do this, I wrote an attribute extension, as below.
View:
@Html.LabelFor(m => m.Postcode, new { @class = "input-label", @for = "valid-postcode" })
@Html.TextBoxFor(m => m.Postcode, new { type = "text", id = "valid-postcode", autocomplete = "off" })
@Html.ValidationMessageFor(m => m.Postcode)
Model:
// Postcode
[Required(ErrorMessageResourceType = typeof(Resources.FormValidation), ErrorMessageResourceName = "requiredMsg")]
[Localised(typeof(Resources.FormValidation), "postcodeRegEx", "postcodeMsg")]
[Display(Name = "postcode", ResourceType = typeof(Resources.FormLabels))]
public string Postcode { get; set; }
Attribute Extension:
public class LocalisedAttribute : RegularExpressionAttribute
{
public LocalisedAttribute(Type resource, string regularExpression, string errorMessage)
: base(Resources.FormValidation.ResourceManager.GetString(regularExpression))
{
ErrorMessageResourceType = resource;
ErrorMessageResourceName = errorMessage;
}
}
If I set a breakpoint on my Attribute Extension and start the application, I hit the break point when I view the page that contains my form elements. As I test I added an additional field that also uses the same extension and it hit the breakpoint twice. So from this, I know it's working and I know it's getting my regular expressions from my resource files.
THE PROBLEM
I have a menu to switch the culture used in the application. When I choose a new culture and the page is refreshed, all references to my resource files used in my Views and the Display Name and Error Messages in data annotations pick up the culture change and use the correct resource file.
However, the regular expression is NOT updated and the breakpoint I set is not hit again. This means my extension is still using the regex it picked up when it was hit and therefore doesn't validate correctly.
I can post more details on how the culture is changed from this menu if needed, but the basic structure is
www.site.com/en-GB/View
What I need is for my attribute extension to be hit every time the culture is switched, and not just the first time the application is started.
Is this possible, or should I be revising my whole approach?
Your menu strings are referenced at runtime, while your attribute is compiled before your app runs
I can understand why this is confusing.
Resource files are fundamentally meant to be used for dynamic behavior. And string values can be altered at run time.
But, when we dig into the usage of this particular string, you are using Resources.FormValidation.ResourceManager.GetString(regularExpression)
resource string as part of the compile instruction to create Postcode
. The Razor Framework will use this data to create annotation templates for validation.
[Required(ErrorMessageResourceType = typeof(Resources.FormValidation), ErrorMessageResourceName = "requiredMsg")]
[Localised(typeof(Resources.FormValidation), "postcodeRegEx", "postcodeMsg")]
[Display(Name = "postcode", ResourceType = typeof(Resources.FormLabels))]
public string Postcode { get; set; }
You are using this string the postcodeRegEx string at COMPILE TIME:
In some cases, compiled and pre-compiled code dependent on string literals can behave differently if the string changes. In other cases, like validation attribute behaviors, you do not get to "re-compile" your object's behavior so easily.
Possible Solutions
To achieve this kind of "end-around", you have to go outside the standard
1) implement an extension to validation attribute (ValidationAttribute
), inheriting from RegularExpressionAttribute
which reads the specific RegEx string from your resource file and passes it to the base RegEx Attribute.
// New attribute loads RegEx when needed
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)]
public class LocalisedAttribute : RegularExpressionAttribute
{
static LocalizedRegexAttribute()
{
// necessary to enable client side validation
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(LocalizedRegexAttribute), typeof(RegularExpressionAttributeAdapter));
}
public LocalisedAttribute(Type resource, string regularExpressionKey, string errorMessage)
: base(LoadRegex(regularExpressionKey))
{
ErrorMessageResourceType = resource;
ErrorMessageResourceName = errorMessage;
}
private static string LoadRegex(string key)
{
var resourceManager = new ResourceManager(typeof(Resources.FormValidation));
return resourceManager.GetString(key);
}
}
2) use JQuery to make input's data-val-regex-pattern = @ViewBag.RegEx
It will reference the JQuery function
$.validator.unobtrusive.adapters.add('Postcode ', function(options) { /*...*/ });
And I suspect the data-val-regex-pattern
for the Postcode input will be set to the value from your initial resource file.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With