Validation ErrorMessage value - c#

How to emit custom validator error messages for an entity like this?:
Receipts exeeded invoice amount of 15000
My property
[InvoiceAmountNotExeeded(ErrorMessage = "Receipts exeeded invoice amount of {0}")]
public int Amount {get; set; }
In validator:
var errorMsg = FormatErrorMessage(string.Format(validationContext.DisplayName,invoice.Amount))
Problem is I m getting: Receipts exeeded invoice amount of Amount.
Note how it is writting the property name instead of property value. Advice?
EDIT: Code added
public class InvoiceAmountNotExeededAttribute : ValidationAttribute {
public InvoiceAmountNotExeededAttribute()
{
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var factId = ....;
var db = new Entities();
var fact = db.Invoices.Find(factId);
var amountRecibos = ...;
var amount = Convert.ToInt32(value);
if (amountRecibos + amount > fact.Amount ){
var errorMsg = FormatErrorMessage(string.Format(validationContext.DisplayName,invoice.Amount));
return new ValidationResult(errorMsg);
}
return ValidationResult.Success;
}
}

The reason you have this behaviour is because you refer to validationContext.DisplayName which by default is set to property name ("Amount" in your case). So for you string.Format(validationContext.DisplayName,invoice.Amount) returns just "Amount". Instead of this try to apply this:
var errorMsg = FormatErrorMessage(invoice.Amount.ToString());
return new ValidationResult(errorMsg);
This way you will pass to FormatErroMessage not DisplayName for your property but Amount value instead and FormatErrorMessage will use it with pattern from ErrorMessage attribute property. So this should give you what you want.

Related

How to reset the formatted ErrorMessages of the validation attributes in ASP.NET MVC?

I used a Custom validation attribute -AmountShouldBeLessOrEqualAttribute- that its validation process related to value of another property and this attribute works successfully.
But in the following scenario I have a problem with it:
Start the Application
Going to the Form page
Submit the Form (POST the form for first time)
The ModelBinding process cause that the value of ErrorMessage in the AmountShouldBeLessOrEqual attribute be formatted. For example:
In the ViewModel there is an Amount property with the above attibute
and
Its ErrorMessage: Your amount should be less than {0}
Will be convert to: Your amount should be less than 23
Note: 23 is the value of MaxAmount property in the ViewModel
Now I change the MaxAmount to 83
We go to the Form page again and submit the form
The ModelBinding process will be start the validation process of AmountShouldBeLessOrEqualAttibute. Now if I watch the value of ErrorMessage property it is not Your amount should be less than {0}, it remained as the old formatted text: Your amount should be less than 23. So it can not be formatted again to Your amount should be less than 83
My question:
How should I reset the formatted ErrorMessages to its Non-Formatted version each time to be formatted with new value?
In ViewModel:
[AmountShouldBeLessOrEqual(nameof(MaxAmount), ErrorMessage = "Your amount should be less than {0}")]
public decimal Amount { get; set; }
public decimal MaxAmount { get; set; }
AmountShouldBeLessOrEqualAttribute:
public class AmountShouldBeLessOrEqualAttribute : ValidationAttribute
{
private readonly string _comparisonProperty;
public AmountShouldBeLessOrEqualAttribute(string comparisonProperty)
{
_comparisonProperty = comparisonProperty;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
ErrorMessage = ErrorMessageString;
var currentValue = (decimal)value;
var comparisonValue = GetComparisonValue(_comparisonProperty, validationContext);
if (ErrorMessage == null && ErrorMessageResourceName == null)
{
ErrorMessage = "Amount is large";
}
else
{
ErrorMessage = string.Format(ErrorMessage ?? "", comparisonValue);
}
return currentValue >= comparisonValue
? new ValidationResult(ErrorMessage)
: ValidationResult.Success;
}
public override string FormatErrorMessage(string name)
{
return base.FormatErrorMessage(name);
}
private decimal GetComparisonValue(string comparisonProperty, ValidationContext validationContext)
{
var property = validationContext.ObjectType.GetProperty(comparisonProperty);
if (property == null)
throw new ArgumentException("Not Found!");
var comparisonValue = (decimal)property.GetValue(validationContext.ObjectInstance);
return comparisonValue;
}
}
This is caused that you are setting value for ErrorMessage with string.Format(ErrorMessage ?? "", comparisonValue);. ErrorMessage is the value from [AmountShouldBeLessOrEqual(nameof(MaxAmount), ErrorMessage = "Your amount should be less than {0}")] which you should not change during IsValid.
Try to define a scoped variable in IsValid to store the formatted error message.
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
string error = "";
var currentValue = (decimal)value;
var comparisonValue = GetComparisonValue(_comparisonProperty, validationContext);
if (ErrorMessage == null && ErrorMessageResourceName == null)
{
ErrorMessage = "Amount is large";
}
else
{
error = string.Format(ErrorMessage ?? "", comparisonValue);
}
return currentValue >= comparisonValue
? new ValidationResult(error)
: ValidationResult.Success;
}

mvc-model date range annotation dynamic dates

with reference to this thread in stackoverflow
[Range(typeof(DateTime), "1/2/2004", "3/4/2004",
ErrorMessage = "Value for {0} must be between {1} and {2}")]
public DateTime EventOccurDate{get;set;}
I tried to add some dynamic dates into my model's date range validator as:
private string currdate=DateTime.Now.ToString();
private string futuredate=DateTime.Now.AddMonths(6).ToString();
[Range(typeof(DateTime),currdate,futuredate,
ErrorMessage = "Value for {0} must be between {1} and {2}")]
public DateTime EventOccurDate{get;set;}
But Error Occurs.Is there no way to set dynamic date range validation in MVC?
You cannot use dynamic values in attributes because they are metadata that is generated at compile-time. One possibility to achieve this is to write a custom validation attribute or use Fluent Validation which allows for expressing more complex validation scenarios using a fluent expressions.
Here's an example of how such custom validation attribute might look like:
public class MyValidationAttribute: ValidationAttribute
{
public MyValidationAttribute(int monthsSpan)
{
this.MonthsSpan = monthsSpan;
}
public int MonthsSpan { get; private set; }
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value != null)
{
var date = (DateTime)value;
var now = DateTime.Now;
var futureDate = now.AddMonths(this.MonthsSpan);
if (now <= date && date < futureDate)
{
return null;
}
}
return new ValidationResult(this.FormatErrorMessage(this.ErrorMessage));
}
}
and then decorate your model with it:
[MyValidation(6)]
public DateTime EventOccurDate { get; set; }

How to change MVC custom client-side validation error message at runtime using jQuery?

I am using MVC5, and I have a ViewModel for my View, which contains a simple form with the following fields:
MinFirstNameLength
FirstName
MinLastNameLength
LastName
Now, I wish to apply a validation rule on FirstName, based on the value of MinFirstNameLength, and similarly for LastName using MinLastNameLength. I want to do this on the client-side too.
So, I used MVC's unobtrusive client side validation feature. I made a custom validation attribute, implementing the IClientValidatable interface. The GetClientValidationRules method looks like this:
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
string ErrorMessage = ErrorMessageString;
ModelClientValidationRule NameMinLengthRule = new ModelClientValidationRule();
NameMinLengthRule.ErrorMessage = ErrorMessage;
NameMinLengthRule.ValidationType = "nameminlength";
NameMinLengthRule.ValidationParameters.Add("minlengthpropname", MinLengthPropName);
yield return NameMinLengthRule;
}
This validation attribute is applied on the FirstName and LastName properties like this:
[NameMinLength("FirstNameMinLength",ErrorMessage = "First Name must be at least {0} characters"]
public string FirstName { get; set; }
[NameMinLength("LastNameMinLength",ErrorMessage = "Last Name must be at least {0} characters"]
public string LastName { get; set; }
Also, I have the client side validation functions in a .js file elsewhere, which looks like this:
$.validator.addMethod("nameminlength",
function (
value,
element,
params
) {
return value.length >= parseInt($(params).val());
});
$.validator.unobtrusive.adapters.add("nameminlength", ["minlengthpropname"], function (options) {
var paramField = "#" + options.params.minlengthpropname;
options.rules["nameminlength"] = paramField;
var errormessage = options.message;
options.messages["nameminlength"] = errormessage;
});
My question is, how do I assign the value in the textbox for MinFirstNameLength to the placeholder {0} in my error message for FirstName, so that the error message reads First Name must be at least [value] characters?
I tried adding modifying my $.validator.unobtrusive.adapters.add method as follows:
$.validator.unobtrusive.adapters.add("nameminlength", ["minlengthpropname"], function (options) {
var paramField = "#" + options.params.minlengthpropname;
options.rules["nameminlength"] = paramField;
var errormessage = options.message;
options.messages["nameminlength"] = errormessage;
$(paramField).blur(function () {
// change error message in the 'data-val-nameminlength' attribute of the HTML element on which validation is applied
var newErrorMessage = options.messages["nameminlength"].replace("{0}", $(paramField).val());
$(options.element).attr("data-val-nameminlength", newErrorMessage);
// change error message inside the error message span generated for displaying this error
$(options.element).siblings(".field-validation-valid").html(newErrorMessage);
});
});
But both these tricks didn't work. The HTML markup did change to modify the error appropriately, but this change wasn't reflected in the page.
Currently, the error message I see is:
First Name must be at least #FirstNameMinLength characters
The placeholder is replaced automatically by the id of the control which is used as a parameter in the validation rule.
Where does this error message come from? How do I modify it at runtime?
I think you should just format your message on server side in the IsValid method of your NameMinLength validation attribute. Here's a small example on how to do it.
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var conditionalPropertyInfo = validationContext.ObjectType.GetProperty(this.MinLengthPropName);
var conditionalPropertyValue = conditionalPropertyInfo.GetValue(validationContext.ObjectInstance, null);
this.ErrorMessage = string.Format(this.ErrorMessage, conditionalPropertyValue.ToString())
// YOUR OTHER CODE HERE
}
This should replace the placeholder with the correct value from MinLengthPropName property. In this case the error message will be formatted before moving it to client side. So no additional logic required for this.
EDIT:
Hm, just thought that you might want to validate based on user input which is really wierd. Actually the fact that you might have different min length restrictions for the same field doesn't make sense to me but as long as it' not based on user input it is much more secure.
UPDATED WITH CLIENT SIDE SOLUTION:
I did a simple test with a similar attribute and it works for me
Edit Model:
[MinCustomLength("MinLenthDestURI", "Dest URI must be at least {0} characters")]
public string DestinationURI { get; set; }
public int MinLenthDestURI { get; set; }
Attribute code:
public class MinCustomLengthAttribute : ValidationAttribute, IClientValidatable
{
private String PropertyName { get; set; }
public MinCustomLengthAttribute(String propertyName, String errormessage)
{
this.PropertyName = propertyName;
this.ErrorMessage = errormessage;
}
protected override ValidationResult IsValid(object value, ValidationContext context)
{
// Just for test server side validation always returns Success
return ValidationResult.Success;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var modelClientValidationRule = new ModelClientValidationRule
{
ValidationType = "mincustomlength",
ErrorMessage = this.ErrorMessage
};
modelClientValidationRule.ValidationParameters.Add("prop", PropertyName);
yield return modelClientValidationRule;
}
}
Client side code:
$.validator.addMethod("mincustomlength", function (value, element, params) {
var conditionalId = $.validator.getId(element, params.prop);
var minLength = parseInt($("#" + conditionalId).val());
if (value.length < minLength) {
var message = $(element).attr('data-val-mincustomlength');
$.validator.messages.mincustomlength = $.format(message, minLength);
return false;
}
return true;
});
$.validator.unobtrusive.adapters.add('mincustomlength', ['prop'], function (options) {
options.rules['mincustomlength'] = options.params;
if (options.message != null) {
$.validator.messages.mincustomlength = options.message;
}
});
This will replace {0} with a value from MinLenthDestURI textbox when validation is done.
Hope it helps!

Custom validation attribute calling another validation attribute

I want to create a custom validation attribute that calls other validation attributes.
For example I want to create an attribute called PasswordValidationAttribute. I want it to decorate the property it is defined on with RequiredAttribute, RegularExpressionAttribute and StringLengthAttribute with various parameters defined (such as the regular expression for a password and a maximum and minimum string length).
I'm struggling on where to begin, ascertain how much work is involved and determine if it is at all possible. Once this attribute is applied to a property I would like it to process the ValidationMessageFor HtmlHelper correctly and do a serverside call. I'm hoping I don't need to redefine them (otherwise it will be too much work).
For .net 4 it could look like:
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
public class MyValidationAttribute : ValidationAttribute
{
private readonly bool isRequired;
public string Regex { get; set; }
public int StringLength { get; set; }
public MyValidationAttribute(bool isRequired)
{
this.isRequired = isRequired;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var composedAttributes = ConstructAttributes().ToArray();
if (composedAttributes.Length == 0) return ValidationResult.Success;
var errorMsgBuilder = new StringBuilder();
foreach (var attribute in composedAttributes)
{
var valRes = attribute.GetValidationResult(value, validationContext);
if (valRes != null && !string.IsNullOrWhiteSpace(valRes.ErrorMessage))
errorMsgBuilder.AppendLine(valRes.ErrorMessage);
}
var msg = errorMsgBuilder.ToString();
if (string.IsNullOrWhiteSpace(msg))
return ValidationResult.Success;
return new ValidationResult(msg);
}
private IEnumerable<ValidationAttribute> ConstructAttributes()
{
if (isRequired)
yield return new RequiredAttribute();
if (Regex != null)
yield return new RegularExpressionAttribute(Regex);
if (StringLength > 0)
yield return new StringLengthAttribute(StringLength);
}
}
Usage:
[MyValidationAttribute(true, Regex = "[a-z]*", StringLength = 3)]
public string Name { get; set; }
In .net 3.5 there is a limitation, that you cannot dynamically construct the message value from underlying attributes (at least I was not able get to through it). You can set only one message per whole attribute.
Everything changed is inside method IsValid.
public override bool IsValid(object value)
{
var composedAttributes = ConstructAttributes().ToArray();
if (composedAttributes.Length == 0) return true;
return composedAttributes.All(a => a.IsValid(value));
}
Note to ErrorMessage:
Return value of IsValid method of ValidationAttribute in .net 3.5 is not ValidationResult but bool. When I tried to set the ErrorMessage, I got the error that value can be set only once.

CompareAttribute for case insensitive comparison

I am using CompareAttribute in MVC3 and its working fine. But I want to use case insensitive classCode. Is there any way to get that working
Thanks in Advance
[CompareAttribute("ClassCode", ErrorMessageResourceName = "ClassCode_DontMatch", ErrorMessageResourceType = typeof(Resources.Class))]
public string ConfirmClassCode {get; set; }
A little late to the party, but here is an implementation I just wrote that also includes support for client-side validation using the IClientValidatable interface. You could use Darin Dimitrov's answer as a starting point as well, I just already had some of this.
Server-Side Validation:
//Create your custom validation attribute
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true, Inherited = true)]
public class CompareStrings : ValidationAttribute, IClientValidatable
{
private const string _defaultErrorMessage = "{0} must match {1}";
public string OtherPropertyName { get; set; }
public bool IgnoreCase { get; set; }
public CompareStrings(string otherPropertyName)
: base(_defaultErrorMessage)
{
if (String.IsNullOrWhiteSpace(otherPropertyName)) throw new ArgumentNullException("OtherPropertyName must be set.");
OtherPropertyName = otherPropertyName;
}
public override string FormatErrorMessage(string name)
{
return String.Format(ErrorMessage, name, OtherPropertyName);
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
string otherPropVal = validationContext.ObjectInstance.GetType().GetProperty(OtherPropertyName).GetValue(validationContext.ObjectInstance, null) as string;
//Convert nulls to empty strings and trim spaces off the result
string valString = (value as string ?? String.Empty).Trim();
string otherPropValString = (otherPropVal ?? String.Empty).Trim();
bool isMatch = String.Compare(valString, otherPropValString, IgnoreCase) == 0;
if (isMatch)
return ValidationResult.Success;
else
return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
}
Client-Side Validation
//...continuation of CompareStrings class
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
return new[] { new ModelClientValidationCompareStringsRule(FormatErrorMessage(metadata.GetDisplayName()), OtherPropertyName, IgnoreCase) };
}
}
Define ModelClientValidationCompareStringsRule which is used (above) to pass the attribute's properties to the client-side script.
public class ModelClientValidationCompareStringsRule : ModelClientValidationRule
{
public ModelClientValidationCompareStringsRule(string errorMessage, string otherProperty, bool ignoreCase)
{
ErrorMessage = errorMessage; //The error message to display when invalid. Note we used FormatErrorMessage above to ensure this matches the server-side result.
ValidationType = "comparestrings"; //Choose a unique name for your validator on the client side. This doesn't map to anything on the server side.
ValidationParameters.Add("otherprop", otherProperty); //Pass the name of the property to compare to
ValidationParameters.Add("ignorecase", ignoreCase.ToString().ToLower()); //And whether to ignore casing
}
}
Javascript:
(function ($) {
//Add an adapter for our validator. This maps the data from the ModelClientValidationCompareStringsRule
//we defined above, to the validation plugin. Make sure to use the same name as we chose for the ValidationType property ("comparestrings")
$.validator.unobtrusive.adapters.add("comparestrings", ["otherprop", "ignorecase"],
function (options) {
options.rules["comparestrings"] = {
otherPropName: options.params.otherprop,
ignoreCase: options.params.ignorecase == "true"
};
options.messages["comparestrings"] = options.message;
});
//Add the method, again using the "comparestrings" name, that actually performs the client-side validation to the page's validator
$.validator.addMethod("comparestrings", function (value, element, params) {
//element is the element we are validating and value is its value
//Get the MVC-generated prefix of element
//(E.G. "MyViewModel_" from id="MyViewModel_CompareEmail"
var modelPrefix = getModelIDPrefix($(element).prop("id"));
//otherPropName is just the name of the property but we need to find
//its associated element to get its value. So concatenate element's
//modelPrefix with the other property name to get the full MVC-generated ID. If your elements use your own, overridden IDs, you'd have to make some modifications to allow this code to find them (e.g. finding by the name attribute)
var $otherPropElem = $("#" + modelPrefix + params.otherPropName);
var otherPropValue = getElemValue($otherPropElem);
//Note: Logic for comparing strings needs to match what it does on the server side
//Trim values
value = $.trim(value);
otherPropValue = $.trim(otherPropValue);
//If ignoring case, lower both values
if (params.ignoreCase) {
value = value.toLowerCase();
otherPropValue = otherPropValue.toLowerCase();
}
//compare the values
var isMatch = value == otherPropValue;
return isMatch;
});
function getElemValue(element){
var value;
var $elem = $(element);
//Probably wouldn't use checkboxes or radio buttons with
//comparestrings, but this method can be used for other validators too
if($elem.is(":checkbox") || $elem.is(":radio") == "radio")
value = $elem.prop("checked") ? "true" : "false";
else
value = $elem.val();
return value;
}
//Gets the MVC-generated prefix for a field by returning the given string
//up to and including the last underscore character
function getModelIDPrefix(fieldID) {
return fieldID.substr(0, fieldID.lastIndexOf("_") + 1);
}
}(jQuery));
Usage is standard:
public string EmailAddress { get; set; }
[CompareStrings("EmailAddress", ErrorMessage = "The email addresses do not match", IgnoreCase=true)]
public string EmailAddressConfirm { get; set; }
This plugs into the Unobtrusive Validation framework, so you need to already have that installed and working. At the time of writing I am on Microsoft.jQuery.Unobtrusive.Validation v 3.0.0.
You could write a custom attribute that will perform the case insensitive comparison:
public class CaseInsensitiveCompareAttribute : System.Web.Mvc.CompareAttribute
{
public CaseInsensitiveCompareAttribute(string otherProperty)
: base(otherProperty)
{ }
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var property = validationContext.ObjectType.GetProperty(this.OtherProperty);
if (property == null)
{
return new ValidationResult(string.Format(CultureInfo.CurrentCulture, "Unknown property {0}", this.OtherProperty));
}
var otherValue = property.GetValue(validationContext.ObjectInstance, null) as string;
if (string.Equals(value as string, otherValue, StringComparison.OrdinalIgnoreCase))
{
return null;
}
return new ValidationResult(this.FormatErrorMessage(validationContext.DisplayName));
}
}
and then decorate your view model property with it:
[CaseInsensitiveCompare("ClassCode", ErrorMessageResourceName = "ClassCode_DontMatch", ErrorMessageResourceType = typeof(Resources.Class))]
public string ConfirmClassCode { get; set; }
For client side validation, put this code below in document ready-
jQuery.validator.addMethod("ignoredCaseEqualTo", function (value, element, param) {
return this.optional(element) || value.toLowerCase() === $(param).val().toLowerCase();
}, "__Your Validation message___");
$("#EmailAddress").rules("add", {
ignoredCaseEqualTo: "#EmailAddressConfirm"
});
this code adds new validation rule of case insensitive comparison.
Might not be an optimum way, but this will do your job for client side validation.

Categories

Resources