Localizing DataAnnotations Custom Validation Attribute - asp.net-mvc

I'm currently working in an MVC 2 app which has to have everything localized in n-languages (currently 2, none of them english btw). I validate my model classes with DataAnnotations but when I wanted to validate a DateTime field found out that the DataTypeAttribute returns always true, no matter it was a valid date or not (that's because when I enter a random string "foo", the IsValid() method checks against "01/01/0001 ", dont know why).
Decided to write my own validator extending ValidationAtribute class:
public class DateTimeAttribute : ValidationAttribute
{
public override bool IsValid(object value)
{
DateTime result;
if (value.ToString().Equals("01/01/0001 0:00:00"))
{
return false;
}
return DateTime.TryParse(value.ToString(), out result);
}
}
Now it checks OK when is valid and when it's not, but my problem starts when I try to localize it:
[Required(ErrorMessageResourceType = typeof(MSG), ErrorMessageResourceName = "INS_DATA_Required")]
[CustomValidation.DateTime(ErrorMessageResourceType = typeof(MSG), ErrorMessageResourceName = "INS_DATA_DataType")]
public DateTime INS_DATA { get; set; }
If I put nothing in the field I get a localized MSG (MSG being my resource class) for the key=INS_DATA_Required but if I put a bad-formatted date I get the "The value 'foo' is not valid for INS_DATA" default message and not the localized MSG.
What am I misssing?

It could be that your ToString() is using a 'localized' format so your hard coded string will not match.
try replacing your "if" condition with:
if((DateTime)value == DateTime.MinValue)

Related

Validation with custom Data Annotations in conflict with localization change

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
A controller that changes the culture and returns the user to the same page
Modified my routing to include the culture e.g. 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.

.NET MVC : Localizing RequiredAttribute (without resource files)

This is the problem that we're facing in .NET MVC 2.
We're trying to use DataAnnotations to take care of the Model Validation for us, like it's supposed to. The only problem that we're having is that we don't want the standard error messages (because we have multiple languages on our website).
We want to localize this, but the way the site is setup, is that all text comes from a database. So we'd like to have our error messages in the database as well.
So we wrote a custom RequiredAttribute, like this:
public class LocalizedRequiredAttribute : RequiredAttribute
{
public string LocalizedErrorMessage
{
get
{
return ErrorMessage;
}
set
{
ErrorMessage = value.Translate();
}
}
}
We wrote an extension to the String class to add the "Translate()" method, which does the necessary database lookup for the correct localized version.
We use our attribute like this:
[LocalizedRequired(LocalizedErrorMessage = "Naam is required")]
public string Name {get; set; }
This works, but only once.
If you visit the site in French first, you'll see the French error message stating that you're supposed to enter a value. If you visit the English site later, you'll still see the French error on the English page. The Setter seems to be called only once.
What can we do to prevent this behavior and refresh the error message every time the validation is run / the model is populated with values?
Thanks for any help you can give me.
Couldn't you fix this by moving your .Translate() from your setter to your getter? It makes sense that your setter is called only once.
Edit:
I assumed ErrorMessage was a virtual message, which is not the case.
Your only option might be to create Resource class (you don't need a resource file) that retrieves your values from the database.
[Required(ErrorMesageResourceName="FirstName", ErrorMessageResourceType=typeof(ABCResourceClass))]
public string Name {get; set; }
class ABCResourceClass{
public static String FirstName{
get{
return Translate("FirstName");
}
}
}
As you can infer from the example, the annotations framework calls the property with the name that matches the string you provide to ErrorMessageResourceName.
You could resort to some kindof code generation technique to create the ABCResourceClass if you have a lot of properties.
Just use method FormatErrorMessage() (whis is called everytime) to set ErrorMessage property
But it's hackish
public class ErrorLocalizedRequiredAttribute : RequiredAttribute
{
public ErrorLocalizedRequiredAttribute(string name)
{
Name = name;
}
public string Name
{ get; set; }
public override string FormatErrorMessage(string name)
{
//get translation from DB by Name
ErrorMessage = Localization.Translate(Name);
return base.FormatErrorMessage(name);
}
}
.
.
.
[ErrorLocalizedRequiredAttribute("EmailIsRequired")]
public string Email
{
get; set;
}

ASP.NET MVC Validation Help

I'm doing some simple validation inside my Controller I know this would better placed inside something like a service layer but for this I want to keep it inside the Controller
The idea is to check that a valid url is being entered into a url field to display an image, e.g. http://domain.com/myawesomeimage.png would be valid and http://domain.com/ would not be valid.
// ValidateInt has a default value of 0
int ValidateInt = 0;
// If the url entered (if one at all) does not have correct extension then increment the ValidateInt
if (!ArticleToEdit.image.Contains(".jpg"))
ValidateInt++;
if (!ArticleToEdit.image.Contains(".jpeg"))
ValidateInt++;
if (!ArticleToEdit.image.Contains(".png"))
ValidateInt++;
if (!ArticleToEdit.image.Contains(".gif"))
ValidateInt++;
if (!ArticleToEdit.image.Contains(".bmp"))
ValidateInt++;
// if ValidateInt is bigger than 0 then the url is invalid
if (ValidateInt > 0)
ModelState.AddModelError("Image", "Please enter a valid URL.");
EDITED CODE
The problem with your code
Your code is invalid, because your model state will always have at least 4 errors. Even though the URL would be correct. Your code requires that your URL must have all extensions which is of course incorrect. It can only have one. At most.
The solution
Use DataAnnotations instead and use regular expression validator. You're obviously already using some application model class called ArticleToCreate. You'll have to put data annotations attribute on the image property (one more observation: keey property names with Pascal casing so it's the same as .net):
public class ArticleToCreate
{
[RegularExpression(#"...")] // add regular expression that fulfils your requirements
public string Image { get; set; }
...
}
Then it all depends how complicated your regular expression is. The easiest one for your needs could be just that it starts with an http:// and end with the correct extension:
^http:\/\/.+\.(?:png|jpe?g|gif|bmp)$
And if you're directly providing your class instance to controller action it will get automatically validated for you without any additional code. This way you won't be able to forget to validate your objects manually.
Consider this controller action that automatically validates your model class object instance by validators defined on it (as per validator definition I've written above):
public ActionResult Create(ArticleToCreate data)
{
if (!this.ModelState.IsValid)
{
// handle invalid object
}
// handle valid object
}
This way your actions will focus on the processing part which is their main objective instead of focusing on too many aspects of your business process like validation for instance.
Shorter code = simpler code = easier to maintain = less bugs = less work = happy clients
Why not create custom ValidationAttributes (from DataAnnotations) and allow the Validation Engine do the work for you rather than worrying about where to put your logic?
I'm guessing it would look something like:
[AttributeUsage(AttributeTargets.Property |
AttributeTargets.Field, AllowMultiple = false)]
public class ValidImageUrlAttribute : ValidationAttribute
{
public string ErrorMessage { get; set; }
public override bool IsValid(object value)
{
var url = value as string;
if(!url.Contains(".jpg") || !url.Contains(".jpeg")
|| !url.Contains(".gif") || !url.Contains(".bmp")
|| !url.Contains(".png"))
{
return false;
}
return true;
}
public override string FormatErrorMessage(string name)
{
return ErrorMessage ?? base.FormatErrorMessage(name);
}
}
And then you could decorate your Model:
[Required(ErrorMessage = "Image URL is required.")]
[ValidImageUrl(ErrorMessage = "Valid Image URL is required.")]
public string ImageUrl { get; set; }
changed if (validate1 > 0) and it works fine :)

Dynamic Range Validation in ASP.NET MVC 2

I am using ASP.NET MVC2 and trying to validate my view models using the attributes in System.ComponentModel.DataAnnotations namespace.
How can I dynamically set the permitted valid range of a RangeAttribute?
For example, if I want to validate that a date entered is within an expected range.
This doesn't compile:
[Range(typeof(DateTime),
DateTime.Today.ToShortDateString(),
DateTime.Today.AddYears(1).ToShortDateString())]
public DateTime DeliveryDate { get; set; }
because "an attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type".
Do I need to resort to creating my own custom validator?
OK, found the answer. .NET Framework 4 provides a new CustomValidationAttribute which makes the following possible:
[Required]
[DisplayName("Ideal Delivery Date")]
[CustomValidation(typeof(HeaderViewModel), "ValidateDeliveryDate")]
public DateTime DeliveryDate { get; set; }
public static ValidationResult ValidateDeliveryDate(DateTime deliveryDateToValidate)
{
if (deliveryDateToValidate.Date < DateTime.Today)
{
return new ValidationResult("Delivery Date cannot be in the past.");
}
if (deliveryDateToValidate.Date > DateTime.Today.AddYears(1))
{
return new ValidationResult("Delivery Date must be within the next year.");
}
return ValidationResult.Success;
}
http://msdn.microsoft.com/en-us/library/system.componentmodel.dataannotations.customvalidationattribute%28VS.100%29.aspx
You need to create your own attribute or use a none attribute based validation framework. As the message say, all parameters to any attribute needs to be constant values.

ASP.NET MVC - Custom validation message for value types

When I use UpdateModel or TryUpdateModel, the MVC framework is smart enough to know if you are trying to pass in a null into a value type (e.g. the user forgets to fill out the required Birth Day field) .
Unfortunately, I don't know how to override the default message, "A value is required." in the summary into something more meaningful ("Please enter in your Birth Day").
There has to be a way of doing this (without writing too much work-around code), but I can't find it. Any help?
EDIT
Also, I guess this would also be an issue for invalid conversions, e.g. BirthDay = "Hello".
Make your own ModelBinder by extending DefaultModelBinder:
public class LocalizationModelBinder : DefaultModelBinder
Override SetProperty:
base.SetProperty(controllerContext, bindingContext, propertyDescriptor, value);
foreach (var error in bindingContext.ModelState[propertyDescriptor.Name].Errors.
Where(e => IsFormatException(e.Exception)))
{
if (propertyDescriptor.Attributes[typeof(TypeErrorMessageAttribute)] != null)
{
string errorMessage =
((TypeErrorMessageAttribute)propertyDescriptor.Attributes[typeof(TypeErrorMessageAttribute)]).GetErrorMessage();
bindingContext.ModelState[propertyDescriptor.Name].Errors.Remove(error);
bindingContext.ModelState[propertyDescriptor.Name].Errors.Add(errorMessage);
break;
}
}
Add the function bool IsFormatException(Exception e) to check if an Exception is a FormatException:
if (e == null)
return false;
else if (e is FormatException)
return true;
else
return IsFormatException(e.InnerException);
Create an Attribute class:
[AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = false)]
public class TypeErrorMessageAttribute : Attribute
{
public string ErrorMessage { get; set; }
public string ErrorMessageResourceName { get; set; }
public Type ErrorMessageResourceType { get; set; }
public TypeErrorMessageAttribute()
{
}
public string GetErrorMessage()
{
PropertyInfo prop = ErrorMessageResourceType.GetProperty(ErrorMessageResourceName);
return prop.GetValue(null, null).ToString();
}
}
Add the attribute to the property you wish to validate:
[TypeErrorMessage(ErrorMessageResourceName = "IsGoodType", ErrorMessageResourceType = typeof(AddLang))]
public bool IsGood { get; set; }
AddLang is a resx file and IsGoodType is the name of the resource.
And finally add this into Global.asax.cs Application_Start:
ModelBinders.Binders.DefaultBinder = new LocalizationModelBinder();
Cheers!
With the DefaultModelBinder it is possible to override the default required error message but unfortunately it would apply globally which IMHO renders it completely useless. But in case you decide to do it here's how:
Add the App_GlobalResources folder to your ASP.NET site
Add a resources file called Messages.resx
Inside the resources file declare a new string resource with the key PropertyValueRequired and some value
In Application_Start add the following line:
DefaultModelBinder.ResourceClassKey = "Messages";
As you can see there's no link between the model property you are validating and the error message.
In conclusion it is better to write custom validation logic to handle this scenario. One way would be to use a nullable type (System.Nullable<TValueType>) and then:
if (model.MyProperty == null ||
/** Haven't tested if this condition is necessary **/
!model.MyProperty.HasValue)
{
ModelState.AddModelError("MyProperty", "MyProperty is required");
}
I've been using the awesome xVal validation framework. It lets me do all my validation in the model (Even LINQ-SQL :)). It also emits the javascript required for client side validation.
EDIT: Sorry left out the link for how to get it working for LINQ-SQL
The basic workflow goes something like this.
public partial class YourClass
{
[Required(ErrorMessage = "Property is required.")]
[StringLength(200)]
public string SomeProperty{ get; set; }
}
try
{
// Validate the instance of your object
var obj = new YourClass() { SomeProperty = "" }
var errors = DataAnnotationsValidationRunner.GetErrors(obj);
// Do some more stuff e.g. Insert into database
}
catch (RulesException ex)
{
// e.g. control name 'Prefix.Title'
ex.AddModelStateErrors(ModelState, "Prefix");
ModelState.SetModelValue("Prefix.Title", new ValueProviderResult(ValueProvider["Prefix.Title"].AttemptedValue, collection["Prefix.Title"], System.Globalization.CultureInfo.CurrentCulture));
}
how about this?
[RegularExpression(#"^[a-zA-Z''-'\s]{1,40}$",
ErrorMessage = "Characters are not allowed.")]
That should allow you to tag properties with specific error messages for whatever MVC validators you want to use...
In ASP.NET MVC 1, I met this problem too.
In my project, there is a model or business object named "Entry", and its primary key EntryId is int? type, and the value of EntryId can be allowd to input by users.
So the problem is, when the field is blank or zero or some integer value that has existed, the custom error messages can be shown well, but if the value is some non-integer value like "a", i can not find a way to use the custom message to replace the default message like "The value 'a' is invalid".
when i track the error message in ModelState, i found when the value is non-integer, there will be two errors related to EntryId, and the first item's error message is blank...
Now i have to use such an ugly code to hack the problem.
if (ModelState["EntryId"].Errors.Count > 1)
{
ModelState["EntryId"].Errors.Clear(); //should not use ModelState["EntryId"].remove();
ModelState.AddModelError("EntryId", "必须为大于0的整数"); //必须为大于0的整数 means "it should be an integer value and great than 0"
}
but this makes controller fat, hope there is a real solution to solve it.
Look up ModelState.AddError.
yes, there is a way, you must use System.ComponentModel.DataAnnotations in combination with xVal and you are going to be able to set validation rules and messages (u can even use resource files for localization) for each of your property using Attributes
look here http://blog.codeville.net/2009/01/10/xval-a-validation-framework-for-aspnet-mvc/

Resources