ASP.NET MVC 2.0 Validation and ErrorMessages - asp.net-mvc

I need to set the ErrorMessage property of the DataAnnotation's validation attribute in MVC 2.0. For example I should be able to pass an ID instead of the actual error message for the Model property, for example...
[StringLength(2, ErrorMessage = "EmailContentID")]
[DataType(DataType.EmailAddress)]
public string Email { get; set; }
Then use this ID ("EmailContentID") to retrieve some content(error message) from a another service e.g database. Then the error error message is displayed to the user instead of the ID. In order to do this I need to set the DataAnnotation validation attribute’s ErrorMessage property.
It seems like a stright forward task by just overriding the DataAnnotationsModelValidatorProvider‘s protected override IEnumerable GetValidators(ModelMetadata metadata, ControllerContext context, IEnumerable attributes)
However it is complicated now....
A. MVC DatannotationsModelValidator’s ErrorMessage property is readonly. So I cannot set anything here
B. System.ComponentModel.DataAnnotationErrorMessage property(get and set) which is already set in MVC DatannotationsModelValidator so I cannot set it again. If I try to set it I get “The property cannot set more than once…” error message.
public class CustomDataAnnotationProvider : DataAnnotationsModelValidatorProvider
{
protected override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context, IEnumerable<Attribute> attributes)
{
IEnumerable<ModelValidator> validators = base.GetValidators(metadata, context, attributes);
foreach (ValidationAttribute validator in validators.OfType<ValidationAttribute>())
{
messageId = validator.ErrorMessage;
validator.ErrorMessage = "Error string from DB And" + messageId ;
}
//......
}
}
Can anyone please give me the right direction on this?
Thanks in advance.

I personally wouldnt create a new provider inheriting the original provider but I would create a new Attribute inheriting either the ValidationAttribute or the StringLengthAttribute.
Something Like this
public class MyNewValidationAttribute : StringLengthAttribute
{
public int ErrorMessageId
{
set {
var myErrorMessageFromDB = ""; //query database and get message.
this.ErrorMessage = myErrorMessageFromDB;
}
}
}
Now when you assign the attribute
[StringLength(2, ErrorMessageId = 1 //equals id from database
)]

Related

Custom Model Validator for Integer value in ASP.NET Core Web API

I have developed a custom validator Attribute class for checking Integer values in my model classes. But the problem is this class is not working. I have debugged my code but the breakpoint is not hit during debugging the code. Here is my code:
public class ValidateIntegerValueAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value != null)
{
int output;
var isInteger = int.TryParse(value.ToString(), out output);
if (!isInteger)
{
return new ValidationResult("Must be a Integer number");
}
}
return ValidationResult.Success;
}
}
I have also an Filter class for model validation globally in application request pipeline. Here is my code:
public class MyModelValidatorFilter: IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
if (context.ModelState.IsValid)
return;
var errors = new Dictionary<string, string[]>();
foreach (var err in actionContext.ModelState)
{
var itemErrors = new List<string>();
foreach (var error in err.Value.Errors){
itemErrors.Add(error.Exception.Message);
}
errors.Add(err.Key, itemErrors.ToArray());
}
actionContext.Result = new OkObjectResult(new MyResponse
{
Errors = errors
});
}
}
The model class with validation is below:
public class MyModelClass
{
[ValidateIntegerValue(ErrorMessage = "{0} must be a Integer Value")]
[Required(ErrorMessage = "{0} is required")]
public int Level { get; set; }
}
Can anyone please let me know why the attribute integer validation class is not working.
Model validation comes into play after the model is deserialized from the request. If the model contains integer field Level and you send value that could not be deserialized as integer (e.g. "abc"), then model will not be even deserialized. As result, validation attribute will also not be called - there is just no model for validation.
Taking this, there is no much sense in implementing such ValidateIntegerValueAttribute. Such validation is already performed by deserializer, JSON.Net in this case. You could verify this by checking model state in controller action. ModelState.IsValid will be set to false and ModelState errors bag will contain following error:
Newtonsoft.Json.JsonReaderException: Could not convert string to
integer: abc. Path 'Level', ...
One more thing to add: for correct work of Required validation attribute, you should make the underlying property nullable. Without this, the property will be left at its default value (0) after model deserializer. Model validation has no ability to distinguish between missed value and value equal to default one. So for correct work of Required attribute make the property nullable:
public class MyModelClass
{
[Required(ErrorMessage = "{0} is required")]
public int? Level { get; set; }
}

Breeze.js not returning custom validation error messages from server

I'm having trouble figuring out why Breeze.js (0.84.3) does not return error messages to the client that are set by Custom Validation Attributes applied on the server model at the class level. I am able to reproduce this with the Breeze TODO KO sample as follows:
using System;
using System.ComponentModel.DataAnnotations;
namespace Todo.Models
{
[MyCustomValidator] // NEW
public class TodoItem
{
public int Id { get; set; }
[Required, StringLength(maximumLength: 30)]
public string Description { get; set; } // Set to 'error' to trigger server error.
public System.DateTime CreatedAt { get; set; }
public bool IsDone { get; set; }
public bool IsArchived { get; set; }
}
[AttributeUsage(AttributeTargets.Class)] // NEW
public class MyCustomValidator : ValidationAttribute
{
public override Boolean IsValid(Object value)
{
var todo = value as TodoItem;
if (todo.Description == "error")
{
ErrorMessage = "The TodoItem is not valid!";
return false;
}
return true;
}
}
}
I would expect that the error message "The TodoItem is not valid!" would be returned to the client however Breeze seems to always return "Value cannot be null. Parameter name: source" from the server. It seems there is an exception happening at:
StackTrace:
at System.Linq.Enumerable.Select[TSource,TResult](IEnumerable`1 source, Func`2 selector) at Breeze.WebApi.EFContextProvider`1.SaveChangesCore(Dictionary`2 saveMap)
at Breeze.WebApi.ContextProvider.SaveChanges(JObject saveBundle)
at Todo.Controllers.TodosController.SaveChanges(JObject saveBundle) in c:\Users\RichardH\Downloads\Software\Web\breeze-runtime-plus-0.84.3\Samples\Todo\Todo\Controllers\TodosController.cs:line 41
at lambda_method(Closure , Object , Object[] )
at System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ActionExecutor.<>c__DisplayClass13.<GetExecutor>b__c(Object instance, Object[] methodParameters)
at System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ActionExecutor.Execute(Object instance, Object[] arguments)
at System.Web.Http.Controllers.ReflectedHttpActionDescriptor.<>c__DisplayClass5.<ExecuteAsync>b__4()
at System.Threading.Tasks.TaskHelpers.RunSynchronously[TResult](Func`1 func, CancellationToken cancellationToken)
Can anyone shed some light on how I can apply validations on the server to the entire model (not just to a single property/field)?
Thanks,
Richard
First, Breeze.NET server-side metadata does not communicate custom server validation attributes to the Breeze client. It only conveys certain standard ones such as Required, MaxLength, and StringLength. If you want to duplicate validations on the client, you'll have to write validators in JavaScript on the client and register them with the client-side metadata as explained in the Validation documentation
Second, I guess that the failure is taking place within EF as it attempts to use your custom validation rule. The exception is thrown inside SaveChangesCore which is where the EFContextProvider asks EF to save changes.
Per the stack trace, it's probably a LINQ exception arising from the Select statement where Breeze is trying to tell you about the validation error. I mean the Select in here
var formattedKey = key.EntitySetName + ";" +
key.EntityKeyValues.Select(v => v.ToString()).ToAggregateString(" ,");
The "value cannot be null..." message suggests that key.EntityKeyValues is null. I cannot explain how your entity got here without key values. I guess you could set a breakpoint and find out.
For our part, we need to make this line less vulnerable. I'll put that on our list of things to fix.
Ok, this was a bug and is now fixed as of breeze v1.0.0.

ASP.NET MVC: Implement client side validation with attribute without IClientValidatable

How can I create a custom validation attribute with client side validation without implementing IClientValidatable?
How does System.ComponentModel.DataAnnotations.RequiredAttribute client side validate?
The reason to do this is because I'm using objects from classes in another project as models in my views and I don't want to add the System.Web.MVC reference to that project.
EDIT to add more information:
I know that IClientValidatable is used to add custom attributes to
the HTML to be used later by the unobtrusive validation.
I know I'll need to add the javascript code to made the validation in
the client.
What I don't know is how to use the information from the custom validation attribute to add the necessary attributes to the HTML for unobtrusive validation to work.
This is my custom validation attribute:
public class RequiredGuidAttribute : ValidationAttribute
{
public override bool IsValid(object value)
{
Guid? guidValue = value as Guid?;
if (guidValue == null)
return false;
return guidValue != Guid.Empty;
}
}
This is my property with the attribute applied:
[RequiredGuid(ErrorMessageResourceType = typeof(ClientOrderResources), ErrorMessageResourceName = "RequiredShippingMethod")]
public Guid ShippingMethodId
{
get { return GetProperty(ShippingMethodIdProperty); }
set { SetProperty(ShippingMethodIdProperty, value); }
}
And finally I'm rendering a hidden input for that property in the view using Html.HiddenFor.
Now, how can I get the error message from the attribute to apply it to the HTML? Should I do it my self using Reflection or there is a better way?
And then how can I tell Html.HiddenFor to use that information to add the necessary attributes to the HTML?
We had a similar problem. We have a model we use for our account creation that uses IClientValidatable on its custom attributes. However, we created a batch account creation process that sits outside of the website that we weren't able to reference System.Web.Mvc in. Because of this, when we called Validator.TryValidateObject, any custom validator that inherited from IClientValidatable was simply skipped. Here's what we were working with that was failing to validate outside of our website:
public class AgeValidatorAttribute : ValidationAttribute, IClientValidatable
{
public int AgeMin { get; set; }
public int AgeMax { get; set; }
public override bool IsValid(object value)
{
//run validation
}
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule
{
ErrorMessage = ErrorMessageString,
ValidationType = "agevalidator"
};
rule.ValidationParameters["agemin"] = AgeMin;
rule.ValidationParameters["agemax"] = AgeMax;
yield return rule;
}
Removing System.Web.Mvc required us to also remove GetClientValidationRules and the IClientValidatable reference. In order to do this and still have client side validation, we had to create a new class:
public class AgeValidatorClientValidator : DataAnnotationsModelValidator<AgeValidatorAttribute>
{
private readonly string _errorMessage;
private readonly string _validationType;
public AgeValidatorClientValidator(ModelMetadata metadata, ControllerContext context, AgeValidatorAttribute attribute)
: base(metadata, context, attribute)
{
this._errorMessage = attribute.FormatErrorMessage(metadata.DisplayName);
this._validationType = "agevalidator";
}
public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
{
var rule = new ModelClientValidationRule
{
ErrorMessage = this._errorMessage,
ValidationType = this._validationType
};
rule.ValidationParameters["agemin"] = base.Attribute.AgeMin;
rule.ValidationParameters["agemax"] = base.Attribute.AgeMax;
yield return rule;
}
}
As you can see, it does essentially the same thing as it used to, it's just done using the DataAnnatotationsModelValidator rather than IClientValidatable. There's one more step we need to do to actually attach the DataAnnotationsModelValidator to the atttribute, and that's done in the Global.asax.cs Application_Start method
DataAnnotationsModelValidatorProvider.RegisterAdapter(
typeof(AgeValidatorAttribute), typeof(AgeValidatorClientValidator));
Now you can use this just as you would use a normal attribute:
[AgeValidator(AgeMax = 110, AgeMin = 18, ErrorMessage = "The member must be between 18 and 110 years old")]
public string DateOfBirth { get; set; }
I know this question is a year old, but I spent all day yesterday and half of today trying to figure this issue out. So I hope this helps somebody who runs into the same problem if OP hasn't figured the answer out yet.
Please note, I did not include any javascript in this writeup as it required no changes from the standard implementation of custom validation rules using jQuery.validate.
You can't have custom validation on the client unless you implement IClientValidatable. And for that you also need to add client script as well.
http://msdn.microsoft.com/en-us/vs2010trainingcourse_aspnetmvccustomvalidation_topic3.aspx
It is possible, i found this article on how to do it:
http://xhalent.wordpress.com/2011/05/12/custom-unobstrusive-jquery-validation-in-asp-net-mvc-3-using-dataannotationsmodelvalidatorprovider/
basically you have to create a DataAnnotationsModelValidator on your client an register it in Application_Start().
And don't forget that you still have to write the Javascript for client side validation.

Change default "The {0} field is required" (ultimate solution?)

Good day!
I've the following ViewModel class I use for login form:
using System.ComponentModel.DataAnnotations;
...
public class UserLogin : IDataErrorInfo
{
[Required]
[DisplayName("Login")]
public string Login { get; set; }
[Required]
[DisplayName("Password")]
public string Password { get; set; }
[DisplayName("Remember Me")]
public bool RememberMe { get; set; }
#region IDataErrorInfo Members
// This will be a Model-level error
public string Error
{
get
{
if (!WebUser.CanLogin(Login, Password))
{
return Resources.ValidationErrors.InvalidLoginPassword;
}
else
{
return String.Empty;
}
}
}
// All is handled by DataAnnotation attributes, just a stub for interface
public string this[string columnName]
{
get
{
return string.Empty;
}
}
#endregion
}
And this in Global.asax:
DefaultModelBinder.ResourceClassKey = "BinderMessages";
ValidationExtensions.ResourceClassKey = "BinderMessages";
The resource file BinderMessages.resx is placed inside App_GlobalResources it has two keys InvalidPropertyValue (which works) and PropertyValueRequired which doesn't and gives me default message.
Question: Is it possible to modify this message, or it's tied to DataAnnotations?
I've found many posts about this, but without solution. For now I just fallback to this:
[Required(ErrorMessageResourceType = typeof(Resources.ValidationErrors), ErrorMessageResourceName = "Required")]
You can create a custom ValidationAttribute that extends RequiredAttribute and sets the values there. Something like:
public class MyRequiredAttribute : RequiredAttribute
{
public MyRequiredAttribute()
{
ErrorMessageResourceType = typeof(Resources.ValidationErrors);
ErrorMessageResourceName = "Required";
}
}
Then decorate your Model with your custom attribute.
The default message is compiled into the DataAnnotations assembly in the resource file under System.ComponentModel.DataAnnotations.Resources.DataAnnotationsResources.resources and is RequiredAttribute_ValidationError=The {0} field is required.. So to answer your question, yes, that message is part of DataAnnotations.
Edit: PropertyValueRequired is used for errors on null values with non-nullable types. As mentioned below PropertyValueInvalid is used for type conversion errors.
I've done an approach using a singleton class to provide the translations. You still need to derive all attributes as suggested by #bmancini. The upside with my approach is that you can use multiple string tables (or switch translation source) without having to modify any other logic.
Since my blog entry is rather large, I'll just provide a link:
http://blog.gauffin.org/2010/11/simplified-localization-for-dataannotations/

Possible to change Data Annotations during Runtime? (ASP.NET MVC's [Range] [Required] [StringLength] etc.)

Normally, ModelBinding Validation of a class member might be done like this example:
public Class someclass
{
[StringLength(50)]
public string SomeValue { get; set; }
}
SomeValue is limited to 50 characters at a maximum.
Is it possible to have the constant (50) changed to something else at run-time, say, during the construction of each instance of that class, so that it is possible to have varying instances with different StringLength limitations?
If so, how does one do this?
Yes. But the only way is create your own implementation of the DataAnnotationsModelValidatorProvider and then register it in Global.ascx.cs. You can't simply remove attributes at runtime BUT interupt the MVC internals that read them:
public class ConventionModelValidatorProvider : DataAnnotationsModelValidatorProvider
{
protected override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context, IEnumerable<Attribute> attributes)
{
List<Attribute> newAttributes = new List<Attribute>(attributes);
if( mycondition == true )
{
//get rid of the existing attribute
newAttributes.Remove(newAttributes.OfType<StringLengthAttribute>().First());
//add a new one
newAttributes.Add( new StringLengthAttribute(5324));
}
return base.GetValidators(metadata, context, newAttributes);
}
}
Register:
ModelValidatorProviders.Providers.Clear();
ModelValidatorProviders.Providers.Add( new CustomValidatorProvider() );

Resources