I have got a class like this
Model:
public class Circle
{
[Required(ErrorMessage = "Diameter is required")]
public int Diameter { get; set; }
[Required(ErrorMessage = "Name is required")]
public string Color { get; set; }
}
Testing:
[TestMethod]
public void TestCircle()
{
Circle circle = new Circle();
circle.Diameter = 5;
circle.Color = "Black";
ValidationContext contex = new ValidationContext(circle, null, null);
Validator.ValidateObject(circle , contex);
}
I was expecting it'd fail whenever Diameter or Color is null. However, the above testing only failed when the string parameter, Color, is null. Why? How should I do in order to validate Diameter as well?
You shouldn't use the Required attribute with numeric properties. Use the Range attribute instead:
The RequiredAttribute attribute specifies that when a field on a form
is validated, the field must contain a value. A validation exception
is raised if the property is null, contains an empty string (""), or
contains only white-space characters.
RequiredAttribute only validates against null (and empty strings), but an int is non-nullable and becomes 0 by default.
You could make it nullable (with int?) or you could use a different kind of attribute. As DmitryG says, you could use the RangeAttribute if there's a certain range of numbers that are acceptable to you, but if not I think the only way would be a CustomValidationAttribute with a function to compare the value to zero.
EDIT: Given that it's a diameter, I guess you need to make sure it's positive, and not just unequal to zero. In this case a RangeAttribute may indeed be best, with 1 as the minimum and Int32.MaxValue as the maximum.
Related
I have a [Range] annotation that looks like this:
[Range(0, 100)]
public int AvailabilityGoal { get; set; }
My webpage looks like this:
<%=Html.TextBoxFor(u => u.Group.AvailabilityGoal)%>
It works as it should, I can only enter values between 0 and 100 but I also want the input box to be optional, the user shouldn't get an validation error if the input box is empty. This has nothing to do with the range but because the type is an integer. If the user leaves it empty it should make AvailabilityGoal = 0 but I don't want to force the user to enter a zero.
I tried this but it (obviously) didn't work:
[Range(typeof(int?), null, "100")]
Is it possible to solve this with Data Annotations or in some other way?
Thanks in advance.
Bobby
You shouldn't have to change the [Range] attribute, as [Range] and other built-in DataAnnotations validators no-op when given an empty value. Just make the property itself of type int? rather than int. Non-nullable ValueType properties (like int) are always automatically required.
I guess you could override the Range object and add this behaviour.
public class OptionalRange : RangeAttribute {
public override bool IsValid(object value) {
if (value == null || (int)value == 0) return true;
return base.IsValid(value);
}
}
This seems to work as (pretty) well:
[Range(Double.NaN, 20)]
public byte? Amount { get; set; }
The lower limit is not checked upon. Not so handy if you want to check null || >= 0. Off course server-side validation goes hand-in-hand with client-side validation where this (< 0) can be checked upon.
I have a viewmodel with a property defined like so:
public decimal decProperty { get; set; }
I have found this enforced "required" validation when I would like to allow nulls. To me specifying 0.00 is explicitly different to null ie the user is stating that the value is 0.00 while the latter means that the value is n/a.
The only way around this that I have found is to redefine the property as a string with regex validation:
[RegularExpression(#"\d+(\.\d{1,2})?", ErrorMessage = "Invalid decimal")]
public string strProperty { get; set; }
I am correct in my thinking and solution?
Thanks in advance.
A decimal property cannot ever be null though, try decimal? instead:
public decimal? decProperty { get; set; }
You could use a nullable decimal:
public decimal? decProperty { get; set; }
Doing so, if the user doesn't provide any value for the decProperty, it's value would be null. Otherwise, it would contain the value that the user provided.
This cannot be done with decimal, because null is not a valid decimal value. On the other hand a nullable decimal can has a a value the null or any valid decimal value.
If you want to read more about nullable types, please have a look here.
When validating data annotations, why does it only seem to validate (correctly) when using a get accessor on the property you want to validate?
Given the example below, the Name property will always be considered valid, even though it is unassigned, however the Address property will only be considered valid if it assigned a non-empty string value, all because it's using a get accessor. Why is this?
Surely TryValidateObject will basically just use test.Address to access the value whether it's through a get accessor or not.
class Program
{
static void Main(string[] args)
{
var test = new Test();
var _errors = new List<ValidationResult>();
bool isValid = Validator.TryValidateObject(
test, new ValidationContext(test), _errors, true);
}
}
public class Test
{
[Required]
public string Name; // Returns true even when unassigned / null / empty string
[Required]
public string Address { get; } // Only returns true when assigned a non-empty string
}
The reason is Validator.TryValidateObject will only check properties and class-level annotations, but your Name is a field, not a property (see here that documentation of Validator class does not mention fields). If you apply any other attribute (like Range) to a field - it also won't work with Validator.TryValidateObject. The reason why RequiredAttribute can be applied to a field is because RequiredAttribute is an entity on it's own and is not related\tied to Validator class in any way. Any other validation mechanism can validate public fields, it's just specific Validator class which does not.
I've decorated a class with:
[Required(ErrorMessage = "Price is required.")]
public decimal Price { get; set; }
But when validating it with code:
for each (PropertyInfo prop in Me.GetType().GetProperties())
{
if (prop.GetIndexParameters().Length = 0)
{
for each (ValidationAttribute validatt in prop.GetCustomAttributes(GetType(ValidationAttribute), True))
{
if (!validatt.IsValid(prop.GetValue(Me, Nothing))
{
retval.Add(New PropertyValidationError(prop.Name, string.Format("There is a problem with the {0} property. It is flagged with the {1}", prop.Name, validatt.GetType.Name), validatt.ErrorMessage));
}
}
}
}
I'm finding that a value of 0 is being treated as fulfilling the "requiredness", which is not what I intended (in fact, I wanted to allow any value other than zero) - is it my validation code doing the wrong thing, or is there a way to use decorate with a ValidationAttribute that will fail for default values for value types?
When you're using a value type, such as decimal, it's impossible to not have a value. As such, that attribute is effectively meaningless.
It would be a better idea to use [Range], as this allows you to specify meaningful values. However, this will not allow you to handle "any non-zero value" (though you could easily handle any positive non-zero value).
Your criteria, as stated, would require creating a custom validation attribute to validate, as "any value other than default(T)" is not a built-in validation.
What about make you value type variable nullable?
public decimal? Price { get; set; }
Use the range validation attribute.
[Range(min, max, ErrorMessage )]
I have a numeric property on my model and i am using editorfor on my razor view with it. The field is not mandatory but the default validation makes the user enter a value because it wont accept an empty string for a number. I have ended up changing the model property to a string and then putting my own custom validation attribute on the property. This cant possibly be the correct way of getting what i want....can it??
[NonMandatoryDoubleValidation("Latitude")]
public string Latitude { get; set; }
What you need is a nullable double: double?. That way your variable will accept empty string or null value as well as double values. However, you'll need to check if it's empty each time you use it with Latitude.HasValue and use Latitude.Value to get its value.
How about a nullable double:
[Required]
public double? Latitude { get; set; }