7

I was just wondering why the Range validation attribute can take a Type and two strings as parameters? Is this for validating strings against an Enum or something like this?

Also what I am trying to do is find an easy way to validate a 3 character string which must be present in an enum, any sugestions?

Thanks, Alex.

AGuyCalledGerald
  • 7,882
  • 17
  • 73
  • 120
Alex Hope O'Connor
  • 9,354
  • 22
  • 69
  • 112
  • how/why does the 3 char string need to be in an enum? – gideon Mar 20 '11 at 07:12
  • I was trying to store the a address state as a 3 character string, but i changed it to a byte now and am just editing the data type to match that of the enum. – Alex Hope O'Connor Mar 20 '11 at 07:17
  • see my answer. Not sure how the enum would work but the 3 char string could work if you know how `string.Compare()` compares them. Also, this validation would only work on the sever side. You could add your own custom class that implements `IComparable` for your strings. – gideon Mar 20 '11 at 07:32
  • 1
    Also note: RangeAttribute is from `System.ComponentModel.DataAnnotations.dll` NOT part of asp.net mvc. – gideon Mar 20 '11 at 07:33

6 Answers6

18

I did find the Range ctor you mentioned fishy. Couldn't help but investigate. (So I wrote this answer like a log while investigating.)

From MSDN

public RangeAttribute(
    Type type,
    string minimum,
    string maximum
)

Note : MSDN says the Type should be IComparable. And, their example description says its for a date comparison when its not!.

So since I had my production asp.net mvc3 app opened I tried this on a with a date time like this:

[Range(typeof(DateTime),"1-Jan-1910","1-Jan-2060")]

When I run it this happens:

enter image description here

Note how although I specified the minimum and maximum with dashed and no time, it gives a different format, so its probably TryParsing the strings right? But I'm sure it can't possibly ICompare the two on the client side!? Now no matter what date I enter still shows the error. (The Date is entered as 11-Mar-20 (20 as in 2020).)

I tried char (below), since thats an IComparable too. Same thing. It can't actually manage to do a range comparison on the client side.

[Range(typeof(char), "a", "g")]

But wait...

Just remove Client Validation! I Removed References to JQuery validation and Unobtrusive validation and viola! It works perfect. It posts, then shows the errors correctly when the values (Both Char and Date) are NOT within the specified range.

Note: Maybe someone can expand this solution to disabling ONLY certain fields for client validation.

Hope this was helpful.

gideon
  • 19,329
  • 11
  • 72
  • 113
  • @alex Your welcome. I actually though it wasn't working till the _but wait_ Which is when I tried disabling the client validation and it worked! =P – gideon Mar 20 '11 at 07:41
  • @gideon: I know this was posted long time ago but still: many many thanks for the CLEAR explanation! I encountered the problem today and couldn't find a proper explanation.. :) – Abbas Mar 15 '12 at 21:30
  • 1
    You don't have to disable the client-side validation completely. You can just remove the client-side range validation for your specific textbox instead. Like this: `$('yourTextBox').rules('remove','range');` – Bhushan Shah Apr 30 '14 at 07:15
  • It doesn't accept "20" because it reads it as "0020", not "2020". Two-digit years are not compatible with many things in code, anymore because of the Y2K bug. – vapcguy Mar 25 '15 at 21:56
5

I've also noticed that jQuery Validation does not play well with the ASP MVC Range-validator (it seems like jQuery Validation plugin requires the range values to be numbers).

A simple solution is to turn off validation for the specific fields. The server side validaton will work anyway.

The following example will remove the "range" rule from all input fields with a "date" class:

$('input.date').each(function() {
    $(this).rules('remove', 'range');
});
jhdrn
  • 187
  • 1
  • 9
3

I too have picked up on this fairly late :)

Surely the CustomValidation attribute was built for these circumstances?

This way we don't have to go about altering any client side validation. Plus it is advantageous in that it allows us the opportunity to apply a configurable range.

For example:

public class Person
{
    [CustomValidation(typeof(Person), "validateDOB")]
    public DateTime DateOfBirth { get; set; }

    //field (or property) limits that we could look-up
    private static DateTime _MinDate = new DateTime(1, 1, 1900);
    private static DateTime _MaxDate = new DateTime(31, 12, 2999);

    //this method must be public and static and take a single
    //parameter: the field to validate
    public static ValidationResult validateDOB(DateTime dateOfBirth)
    {
        string errorMsg = "";
        if (dateOfBirth < _MinDate)
        {
            errorMsg = "Date too early";
        }
        else if (dateOfBirth > _MaxDate)
        {
            errorMsg = "Date too late";
        }
        return errorMsg == "" ? null : new ValidationResult(errorMsg);
    }
}
ne1410s
  • 6,864
  • 6
  • 55
  • 61
3

I ended up creating a custom DateRangeAttribute as described here. You don't get client side validation, but you can customize this to fit your needs and no messing with javascript required. Here's the code and how to use it:

public class Person
{
    [Required]
    public string FirstName { get; set; }

    [DataType(DataType.Date)]
    [DateRange("2010/12/01", "2010/12/16")]
    public DateTime DateOfBirth { get; set; }
}

DateRange is simply:

public class DateRangeAttribute : ValidationAttribute
{
    private const string DateFormat = "yyyy/MM/dd";
    private const string DefaultErrorMessage = "'{0}' must be a date between {1:d} and {2:d}.";

    public DateTime MinDate { get; set; }
    public DateTime MaxDate { get; set; }

    public DateRangeAttribute(string minDate, string maxDate)
        : base(DefaultErrorMessage)
    {
        MinDate = ParseDate(minDate);
        MaxDate = ParseDate(maxDate);
    }

    public override bool IsValid(object value)
    {
        if (value == null || !(value is DateTime))
        {
            return true;
        }
        DateTime dateValue = (DateTime)value;
        return MinDate <= dateValue && dateValue <= MaxDate;
    }

    public override string FormatErrorMessage(string name)
    {
        return String.Format(CultureInfo.CurrentCulture, ErrorMessageString,
            name, MinDate, MaxDate);
    }

    private static DateTime ParseDate(string dateValue)
    {
        return DateTime.ParseExact(dateValue, DateFormat, CultureInfo.InvariantCulture);
    }
}
Alex
  • 9,250
  • 11
  • 70
  • 81
1

You can use the below custom date range validation which compares the date value to provided min and max date or mentioned dependent properties. It also demonstrates the client side validation support and integration.

CustomDateRange

public enum CustomDateRangeType
{
/// <summary>
/// The direct value of property.
/// </summary>
Value,

/// <summary>
/// The dependent property.
/// </summary>
DependentProperty
}

/// <summary>
/// The CustomDateComparAttribute Validator
/// </summary>
[AttributeUsage(AttributeTargets.All | AttributeTargets.Property,        AllowMultiple = false, Inherited = true)]
 public sealed class CustomDateRangeAttribute : ValidationAttribute,  IClientValidatable
 {

private const string UniversalDatePattern = "yyyy-M-d";

/// <summary>
/// The min date.
/// </summary>
private string minDate;

/// <summary>
/// The max date.
/// </summary>
private string maxDate;

/// <summary>
/// The date range type
/// </summary>
private CustomDateRangeType dateRangeType;

/// <summary>
/// Initializes a new instance of the <see cref="CustomDateRangeAttribute"/> class.
/// </summary>
/// <param name="minDate">
/// The min date in <example>yyyy-M-d</example> format. Throws FormatException exception if not provided in specified format.
/// </param>
/// <param name="maxDate">
/// max date in <example>yyyy-M-d</example> format. Throws FormatException exception if not provided in specified format.
/// </param>
public CustomDateRangeAttribute(string minDate, string maxDate)
    : this(CustomDateRangeType.Value, minDate, maxDate)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="CustomDateRangeAttribute" /> class.
/// </summary>
/// <param name="dateRangeType">Type of the date range.</param>
/// <param name="minDate">The minimum date dependent property or value. If value then it should be <example>yyyy-M-d</example> format.</param>
/// <param name="maxDate">The maximum date property or value. If value then it should be <example>yyyy-M-d</example> format.</param>
public CustomDateRangeAttribute(CustomDateRangeType dateRangeType, string minDate, string maxDate)
{
    if (dateRangeType == CustomDateRangeType.Value)
    {
        if (!IsValidDate(minDate))
        {
            throw new FormatException(string.Format(CultureInfo.InvariantCulture, "Max date should be in {0} format.", UniversalDatePattern));
        }

        if (!IsValidDate(maxDate))
        {
            throw new FormatException(string.Format(CultureInfo.InvariantCulture, "Min date should be in {0} format.", UniversalDatePattern));
        }
    }

    this.dateRangeType = dateRangeType;
    this.minDate = minDate;
    this.maxDate = maxDate;
}

/// <summary>
/// Gets the min date.
/// </summary>
public string MinDate
{
    get
    {
        return this.minDate;
    }
}

/// <summary>
/// Gets the max date.
/// </summary>
public string MaxDate
{
    get
    {
        return this.maxDate;
    }
}

/// <summary>
/// Gets the type of the date range.
/// </summary>
/// <value>
/// The type of the date range.
/// </value>
public CustomDateRangeType DateRangeType
{
    get
    {
        return this.dateRangeType;
    }
}

/// <summary>
/// gets client validation rules
/// </summary>
/// <param name="metadata">
/// meta data parameter
/// </param>
/// <param name="context">
/// controller context
/// </param>
/// <returns>
/// client validation rule
/// </returns>
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(
    ModelMetadata metadata,
    ControllerContext context)
{
    if (metadata != null)
    {
        return new[]
                   {
                       new ModelClientValidationCustomDateRangeRule(
                           this.ErrorMessageString,
                           this.DateRangeType,
                           this.MinDate,
                           metadata.PropertyName,
                           this.MaxDate)
                   };
    }

    return null;
}

/// <summary>
/// overridden method
/// </summary>
/// <param name="value">
/// value to be compared
/// </param>
/// <param name="validationContext">
/// validation context
/// </param>
/// <returns>
/// validation result
/// </returns>
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
    var result = ValidationResult.Success;
    var errorResult = new ValidationResult(this.ErrorMessageString);
    if (value == null)
    {
        return result;
    }

    DateTime dateValue = (DateTime)value;

    if (this.DateRangeType == CustomDateRangeType.Value)
    {
        if (ParseDate(this.MinDate) <= dateValue && dateValue <= ParseDate(this.MaxDate))
        {
            return result;
        }
    }
    else
    {
        if (validationContext == null || string.IsNullOrEmpty(this.MinDate) || string.IsNullOrEmpty(this.MaxDate))
        {
            return errorResult;
        }

        var minDatePropertyInfo = validationContext.ObjectType.GetProperty(this.MinDate);
        var maxDatePropertyInfo = validationContext.ObjectType.GetProperty(this.MaxDate);
        if (minDatePropertyInfo == null || maxDatePropertyInfo == null)
        {
            return errorResult;
        }

        var minDateValue = Convert.ToDateTime(
            minDatePropertyInfo.GetValue(validationContext.ObjectInstance, null), 
            CultureInfo.CurrentCulture);
        var maxDateValue = Convert.ToDateTime(maxDatePropertyInfo.GetValue(validationContext.ObjectInstance, null), 
            CultureInfo.CurrentCulture);

        if (minDateValue <= dateValue && dateValue <= maxDateValue)
        {
            return result;
        }
    }

    return errorResult;
}

/// <summary>
/// The parse date.
/// </summary>
/// <param name="dateValue">
/// The date value.
/// </param>
/// <returns>
/// The <see cref="DateTime"/>.
/// </returns>
private static DateTime ParseDate(string dateValue)
{
    return DateTime.ParseExact(
        dateValue, UniversalDatePattern, 
        CultureInfo.InvariantCulture);
}

/// <summary>
/// The is valid date.
/// </summary>
/// <param name="dateValue">
/// The date value.
/// </param>
/// <returns>
/// A value indicating whether the provided dateValue is a valid date.
/// </returns>
private static bool IsValidDate(string dateValue)
{
    DateTime? date = null;
    var regex = new Regex(@"\d{4}-\d{1,2}-\d{1,2}");
    if (regex.IsMatch(dateValue))
    {
        var dateParts = dateValue.Split('-');
        if (dateParts.Length == 3)
        {
            date = new DateTime(
                Convert.ToInt32(dateParts[0], CultureInfo.InvariantCulture),
                Convert.ToInt32(dateParts[1], CultureInfo.InvariantCulture),
                Convert.ToInt32(dateParts[2], CultureInfo.InvariantCulture));
        }
    }

    return date != null;
}

/// <summary>
///     ModelClientValidationCustomCompareRule class
/// </summary>
private class ModelClientValidationCustomDateRangeRule : ModelClientValidationRule
{
    /// <summary>
    /// Initializes a new instance of the <see cref="ModelClientValidationCustomDateRangeRule"/> class.
    /// </summary>
    /// <param name="errorMessage">error message</param>
    /// <param name="dateRangeType">Type of the date range.</param>
    /// <param name="minDateProperty">The minimum date property.</param>
    /// <param name="currentProperty">The current property.</param>
    /// <param name="maxDateProperty">The maximum date property.</param>
    public ModelClientValidationCustomDateRangeRule(
        string errorMessage,
        CustomDateRangeType dateRangeType,
        string minDateProperty,
        string currentProperty,
        string maxDateProperty)
    {
        this.ErrorMessage = errorMessage;
        this.ValidationType = "customdaterange";
        this.ValidationParameters.Add("daterangetypeproperty", dateRangeType.ToString());
        this.ValidationParameters.Add("mindateproperty", minDateProperty);
        this.ValidationParameters.Add("currentproperty", currentProperty);
        this.ValidationParameters.Add("maxdateproperty", maxDateProperty);
    }
}
}

ClientSide Integration

(function ($) {
jQuery.validator.addMethod('customdaterange', function (value, element, param) {
    if (value == '' || value == undefined) {
        return true;
    }

    var minValue;
    var maxValue;

    if (param.daterangetypeproperty == "DependentProperty") {
        var minDateValue = $('#' + param.mindateproperty).val();
        var maxDateValue = $('#' + param.maxdateproperty).val();
        minValue = new Date(minDateValue);
        maxValue = new Date(maxDateValue);
    } else {
        minValue = new Date(param.mindateproperty);
        maxValue = new Date(param.maxdateproperty);
    }

    var currentValue = new Date(value);
    if (minValue <= currentValue && currentValue <= maxValue) {
        return true;
    }

    return false;
});

jQuery.validator.unobtrusive.adapters.add('customdaterange', ['daterangetypeproperty', 'mindateproperty', 'currentproperty', 'maxdateproperty'], function (options) {
    var params = {
        daterangetypeproperty: options.params.daterangetypeproperty,
        mindateproperty: options.params.mindateproperty,
        currentproperty: options.params.currentproperty,
        maxdateproperty: options.params.maxdateproperty
    };

    options.rules['customdaterange'] = params;
    if (options.message) {
        options.messages['customdaterange'] = options.message;
    }
});
}(jQuery));

Demo

Model

public class DateRangeModel
{
public DateRangeModel()
{
    this.MinDateDependentProperty = new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1);
    this.MaxDateDependentProperty = DateTime.Today.AddDays(1 - DateTime.Today.Day).AddMonths(1);
}

[Required]
[CustomDateRange("2015-10-01", "2015-10-15", ErrorMessage = "Date value is not in range.")]
[DataType(DataType.Date)]
public DateTime DateCompareWithMinMaxValue { get; set; }

[Required]
[CustomDateRange(CustomDateRangeType.DependentProperty, "MinDateDependentProperty", "MaxDateDependentProperty", 
    ErrorMessage = "Date to select value is not in range.")]
[DataType(DataType.Date)]
public DateTime DateCompareWithMinMaxDependentProperty { get; set; }

[Required]
[DataType(DataType.Date)]
public DateTime MinDateDependentProperty { get; set; }

[Required]
[DataType(DataType.Date)]
public DateTime MaxDateDependentProperty { get; set; }
}

Date controls with validations

Download the complete implementation from here.

Amit Gupta
  • 23
  • 8
-3

Guys! Here is one more solition. I found it here: MVC Validation Lower/Higher than other value

public class FinanceModel{
   public int MinimumCost {get;set;}

   [GreaterThan("MinimumCost")]
   public int MaximumCost {get;set;}
}

http://foolproof.codeplex.com/

Community
  • 1
  • 1
Eugene
  • 1
  • What you have is for setting two different parameters and seeing how one compares to the other, not for checking a single parameter within a set range. – vapcguy Mar 25 '15 at 21:59