Consider this code:
[Required]
[RegularExpression(#"\d{2,2}/\d{2,2}/\d{4,4} \d{2,2}:\d{2,2}:\d{2,2}",
ErrorMessage = "Wrong Syntax Entered, Needed:day/Month/Year Hour:Minutes:Seconds")]
public DateTime Posted { get; set; }
When I enter this value, my application crashes: 00/00/0000 00:00:00
Is there a way to stop this and make it more realistic? I'm wanting to allow the date so it only allows max 31 days or less up 1 and allows max months up 12 and min 1?
A regex is the wrong way to validate dates and times. Use DateTime.TryParse instead.
EDIT:
Here's an example:
using System;
using System.Globalization;
...
bool valid;
DateTime dt;
valid = DateTime.TryParseExact(inputString, "dd/MM/yyyy HH:mm:ss", CultureInfo.InvariantCulture, DateTimeStyles.None, out dt);
I agree with #MRAB that TryParse is probably easier to manage and maintain.
This SO question also tries to do what you're doing and they created a custom attribute (that derives from RegularExpressionAttribute) that seems to have solved his problem. Maybe it will help you.
Hope this helps.
Yet another, change FormatException message in ModelBinder(but going to far...).
DefaultModelBinder.GetValueInvalidResource is static method.I can not override this method. Because I create CustomModelBinder class and override SetProperty method.
[Required]
[AdditionalMetadata(
"PropertyValueInvalid",
"Wrong Syntax Entered, Needed:day/Month/Year Hour:Minutes:Seconds")]
public DateTime? Posted { get; set; }
And create custom ModelBinder
public class CustomModelBinder : DefaultModelBinder
{
protected override void SetProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor, object value)
{
base.SetProperty(controllerContext, bindingContext, propertyDescriptor, value);
var propertyMetadata = bindingContext.PropertyMetadata[propertyDescriptor.Name];
var invalidMessage = propertyMetadata.AdditionalValues.ContainsKey("PropertyValueInvalid")
? (string)propertyMetadata.AdditionalValues["PropertyValueInvalid"]
: string.Empty;
if (string.IsNullOrEmpty(invalidMessage))
{
return;
}
// code from DefaultModelBinder
string fullPropertyKey = CreateSubPropertyName(bindingContext.ModelName, propertyDescriptor.Name);
if (!bindingContext.ValueProvider.ContainsPrefix(fullPropertyKey))
{
return;
}
ModelState modelState = bindingContext.ModelState[fullPropertyKey];
foreach (ModelError error in modelState.Errors.Where(err => String.IsNullOrEmpty(err.ErrorMessage) && err.Exception != null).ToList())
{
for (Exception exception = error.Exception; exception != null; exception = exception.InnerException)
{
if (exception is FormatException)
{
string displayName = propertyMetadata.GetDisplayName();
string errorMessageTemplate = invalidMessage;
string errorMessage = String.Format(CultureInfo.CurrentCulture, errorMessageTemplate,
modelState.Value.AttemptedValue, displayName);
modelState.Errors.Remove(error);
modelState.Errors.Add(errorMessage);
break;
}
}
}
}
}
How about this?
Validating datatime with Regex will result in complex regex like
^([0][1-9]||[1-2][0-9]||[3][0-1])/([0][1-9]||[1][1-2])/([1][0-9]{3}) ([0][1-9]||[1][0-2]):([0][1-9]||[1-5][0-9]):([0][1-9]||[1-5][0-9])$
But Still i doubt it may miss some edge cases.
If you are using MVC3, then the best way to Use Self validating model like follows,
public class TestModel:IValidatableObject
{
string MyDateTime{get;set;}
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
List<ValidationResult> v = new List<ValidationResult>();
DateTime dt = default(DateTime);
DateTime.TryParseExact(MyDateTime, "dd/MM/yyyy HH:mm:ss", CultureInfo.InvariantCulture,DateTimeStyles.None,out dt);
if (dt.Equals(default(DateTime)))
v.Add(new ValidationResult("Invalid Date time"));
return v;
}
}
Related
I Have a problem with my regular expression. I have made it possible to valdiate correct swedish social security number to match these criteria.
YYMMDDNNNN
YYMMDD-NNNN
YYYYMMDDNNNN
YYYYMMDD-NNNN
But i would also like to reject a user to register if the user is under 18 years old. My reqular expression is looking like this at the moment: Do anyone encountered the same problem with the age range Swedish SSN?
private const string RegExForValidation =
#"^(\d{6}|\d{8})[-|(\s)]{0,1}\d{4}$";
UPDATE
private const string RegExForValidation = #"^(?<date>\d{6}|\d{8})[-\s]?\d{4}$";
string date = Regex.Match("19970918-1234", RegExForValidation).Groups["date"].Value;
DateTime dt;
[Required(ErrorMessage = "Du måste ange personnummer")]
[RegularExpression(RegExForValidation, ErrorMessage = "Personnummer anges med 10 siffror (yymmddnnnn)")]
public string PersonalIdentityNumber { get; set; }
Second Update
public class ValidateOfAge : ValidationAttribute
{
public bool IsOfAge(DateTime birthdate)
{
DateTime today = DateTime.Today;
int age = today.Year - 18;
if (birthdate.AddYears(birthdate.Year) < today.AddYears(-age))
return false;
else
return true;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
string RegExForValidation = #"^(?<date>\d{6}|\d{8})[-\s]?\d{4}$";
string date = Regex.Match((string)value, RegExForValidation).Groups["date"].Value;
DateTime dt;
if (DateTime.TryParseExact(date, new[] { "yyMMdd", "yyyyMMdd" }, CultureInfo.InvariantCulture, DateTimeStyles.None, out dt))
if (IsOfAge(dt))
return ValidationResult.Success;
return new ValidationResult("Personnummer anges med 10 siffror (yymmddnnnn)");
}
}
This is a case where I wouldn't use regular expressions, but instead rely on the base class library's built-in DateTime parsing functionality:
public DateTime GetBirthDate(string ssn)
{
var strippedOfSerial =
ssn.Substring(0, ssn.Length - 4).TrimEnd('-');
return DateTime.ParseExact(
strippedOfSerial,
new[] { "yyMMdd", "yyyyMMdd" },
new CultureInfo("se-SE"),
DateTimeStyles.None);
}
Now you can look at the returned DateTime value and compare it to DateTime.Now in order to figure out if you want to reject it or not.
In my opinion, this is much more readable than relying on regular expressions, and may be safer and more flexible as well. As you can tell, you can e.g. use other cultures etc. to tweak the parsing strategy.
You need to get the birthdate from the SSN, parse to DateTime, and then compare with today's date.
Here is the method checking if a person is of age:
public bool IsOfAge(DateTime birthdate)
{
DateTime today = DateTime.Today; // Calculating age...
int age = today.Year - birthdate.Year;
if (birthdate > today.AddYears(-age))
age--;
return age < 18 ? false : true; // If the age is 18+ > true, else false.
}
And here is how you can use this:
string RegExForValidation = #"^(?<date>\d{6}|\d{8})[-\s]?\d{4}$";
string date = Regex.Match("19970918-1234", RegExForValidation).Groups["date"].Value;
DateTime dt;
if (DateTime.TryParseExact(date, new[] { "yyMMdd", "yyyyMMdd" }, new CultureInfo("sv-SE"), DateTimeStyles.None, out dt))
Console.WriteLine(IsOfAge(dt));
Note that [-|(\s)] matches a -, |, (, whitespace or ). I am sure you only want to match a hyphen or whitespace.
I added a named capture to the regex and removed unnecessary symbols from the character class. Also, note that {0,1} is the same as ?.
UPDATE
To make it work in an MVC app, you need to implement a custom validator:
[Required(ErrorMessage = "Du måste ange personnummer")]
[ValidateOfAge] // <---------------------------- HERE
public string PersonalIdentityNumber { get; set; }
And implement this as follows:
public class ValidateOfAge: ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
string RegExForValidation = #"^(?<date>\d{6}|\d{8})[-\s]?\d{4}$";
string date = Regex.Match((string)value, RegExForValidation).Groups["date"].Value;
DateTime dt;
if (DateTime.TryParseExact(date, new[] { "yyMMdd", "yyyyMMdd" }, new CultureInfo("sv-SE"), DateTimeStyles.None, out dt))
if (IsOfAge(dt))
return ValidationResult.Success;
return new ValidationResult("Personnummer anges med 10 siffror (yymmddnnnn)");
}
}
Instead of doing custom RegEx, I'd suggest looking at a NuGet package available that is able to parse a valid Swedish Personal Identity Number and then extract information such as DateOfBirth, Age, Gender etc that could be used as input to your validation. The method .GetAge() would be the most relevant in your case. Disclaimer: I'm one of the co-authors of that package.
An implementation of ValidateOfAge using SwedishPersonalIdentityNumber could look like:
public class ValidateOfAge : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (SwedishPersonalIdentityNumber.TryParse((string)value, out var personalIdentityNumber))
{
if(personalIdentityNumber.GetAge() >= 18) {
return ValidationResult.Success;
} else {
return new ValidationResult("Du måste vara minst 18 år gammal.");
}
}
return new ValidationResult("Ogiltigt personnummer.");
}
}
SwedishPersonalIdentityNumber supports the patterns you describe, including a few more that is valid (did you know that a PIN can contain a + for example?).
You can find the source code here:
https://github.com/ActiveLogin/ActiveLogin.Identity
And the packages on NuGet here:
https://www.nuget.org/packages/ActiveLogin.Identity.Swedish/
https://www.nuget.org/packages/ActiveLogin.Identity.Swedish.AspNetCore/
The second one contains a validation attribute for validating a PIN in your model.
If the use case of validating age is common enough, we could implement it into that validation attribute. I created an issue to find out about interest and the design.
The API design proposed at the moment is this (for your scenario):
[SwedishPersonalIdentityNumber(MinimumAge = 18)]
public string PersonalIdentityNumber { get; set; }
Please share your thoughts in the issue if this is something you'd like to see implemented.
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; }
Is there a way to change how the DataContractJsonSerializer serializes dates?
Currently, it'll convert a date to something like:
{
"date": "/Date(1260597600000-0600)/"
}
I want to convert it into human readable date format.
I am building a RestApi using openrasta framework. Can i write OperationInterceptors which will at some stage before serialization/deserialization convert JSON datetime format to something which is human readable?Or is there any other way to do it?
Use DataContractJsonSerializer constructor to pass your serialization settings:
var s = new DataContractJsonSerializer(
typeof(YourTypeToSerialize),
new DataContractJsonSerializerSettings
{
DateTimeFormat = new DateTimeFormat("yyyy-MM-dd'T'HH:mm:ss")
}
);
Finally i have handled this issue as below(c#)
[DataMember]
public string Date { get; set; }
[IgnoreDataMember]
public DateTime? DateForInternalUse { get; set; }
[OnSerializing]
public void OnSerializing(StreamingContext context)
{
Date = (DateForInternalUse != null) ? ((DateTime)DateForInternalUse).ToString(DateTimeFormatForSerialization) : null;
}
[OnDeserialized]
public void OnDeserialized(StreamingContext context)
{
try
{
DateForInternalUse = !String.IsNullOrEmpty(Date) ? DateTime.ParseExact(Date, DateTimeFormats, null, DateTimeStyles.None) : (DateTime?)null;
}
catch (FormatException)
{
DateForInternalUse = null;
}
}
In this case we can specify the formats which we want to support which i have kept inside web.config
<add key="DateTimePattern" value="yyyy-MM-dd,yyyy-MM-dd hh:mm:ss zzz,yyyy-MM-dd hh:mm:ss" />
Let me know for further clarifications.
Can I have a function that checks if true or false and send my verbal to other classes?
I tried:
public class Func
{
public static bool CheckDate(string number)
{
string new_number = number.ToString();
if (new_number.Length==8)
{
string yyyy = new_number.Substring(0, 4);
string mm = new_number.Substring(4, 2);
string dd = new_number.Substring(6, 2);
return true;
}
else
{
return false;
}
}
}
I want to send the verbal yyyy, mm, dd to my Program.cs class.
What should I do?
Don't reinvent wheels, use the DateTime.TryParseExact method which is built specifically for this purpose. Forget about regexes and substrings when you are dealing with dates in the .NET framework:
public static bool CheckDate(string number, out DateTime date)
{
return DateTime.TryParseExact(number, "dd/MM/yyyy", CultureInfo.InvariantCulture, DateTimeStyles.None, out date);
}
And now as you can see defining the CheckDate becomes kinda meaningless because it already exists in the BCL. You would simply use it like this:
string number = "that's your number coming from somewhere which should be a date";
DateTime date;
if (DateTime.TryParseExact(
number,
"dd/MM/yyyy",
CultureInfo.InvariantCulture,
DateTimeStyles.None,
out date
))
{
// the number was in the correct format
// => you could use the days, months, from the date variable which is now a DateTime
string dd = date.Day.ToString();
string mm = date.Month.ToString();
string yyyy = date.Year.ToString();
// do whatever you intended to do with those 3 variables before
}
else
{
// tell the user to enter a correct date in the format dd/MM/yyyy
}
UPDATE:
Since I got a remark in the comments section that I am not actually answering the question, you could use a similar approach to the one I recommend. But please, promise me you will never write a code like this, it's just for illustration of the TryXXX pattern.
define a model:
public class Patterns
{
public string DD { get; set; }
public string MM { get; set; }
public string YYYY { get; set; }
}
and then modify your CheckDate method so that it sends an out parameter:
public static bool CheckDate(string number, out Patterns patterns)
{
patterns = null;
string new_number = number.ToString();
if (new_number.Length == 8)
{
Patterns = new Patterns
{
YYYY = new_number.Substring(0, 4),
MM = new_number.Substring(4, 2),
DD = new_number.Substring(6, 2)
}
return true;
}
else
{
return false;
}
}
which you could use like this:
string number = "that's your number coming from somewhere which should be a date";
Patterns patterns;
if (CheckDate(numbers, out patterns)
{
string dd = patterns.DD;
string mm = patterns.MM;
string yyyy = patterns.YYYY;
// do whatever you intended to do with those 3 variables before
}
else
{
// tell the user to enter a correct date in the format dd/MM/yyyy
}
The CheckDate function’s aim is to check if a date is valid. Don’t introduce crappy side effects: write another function that actually sends your stuff to the object you want.
If you want to check if a string is a date, do it in CheckDate.
When you know a string is a date, extract the date elements you want from it through such a ExtractDateElem function, but please, no side-effects.
you have to declare your variables as below...
public static string yyyy;
public static string mm ;
public static string dd ;
Or
protected static string yyyy;
protected static string mm ;
protected static string dd ;
as per your need and depends where your program.cs file is...
Is it possible to use [Range] annotation for dates?
something like
[Range(typeof(DateTime), DateTime.MinValue.ToString(), DateTime.Today.ToString())]
I did this to fix your problem
public class DateAttribute : RangeAttribute
{
public DateAttribute()
: base(typeof(DateTime), DateTime.Now.AddYears(-20).ToShortDateString(), DateTime.Now.AddYears(2).ToShortDateString()) { }
}
Docs on MSDN says you can use the RangeAttribute
[Range(typeof(DateTime), "1/2/2004", "3/4/2004",
ErrorMessage = "Value for {0} must be between {1} and {2}")]
public datetime Something { get; set;}
jQuery validation does not work with [Range(typeof(DateTime),"date1","date2"] --
My MSDN doc is incorrect
Here is another solution.
[Required(ErrorMessage = "Date Of Birth is Required")]
[DataType(DataType.Date, ErrorMessage ="Invalid Date Format")]
[Remote("IsValidDateOfBirth", "Validation", HttpMethod = "POST", ErrorMessage = "Please provide a valid date of birth.")]
[Display(Name ="Date of Birth")]
public DateTime DOB{ get; set; }
The simply create a new MVC controller called ValidationController and past this code in there. The nice thing about the "Remote" approach is you can leverage this framework to handle any kind of validations based on your custom logic.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net.Mail;
using System.Web;
using System.Web.Mvc;
namespace YOURNAMESPACEHERE
{
public class ValidationController : Controller
{
[HttpPost]
public JsonResult IsValidDateOfBirth(string dob)
{
var min = DateTime.Now.AddYears(-21);
var max = DateTime.Now.AddYears(-110);
var msg = string.Format("Please enter a value between {0:MM/dd/yyyy} and {1:MM/dd/yyyy}", max,min );
try
{
var date = DateTime.Parse(dob);
if(date > min || date < max)
return Json(msg);
else
return Json(true);
}
catch (Exception)
{
return Json(msg);
}
}
}
}
For those rare occurrences when you are forced to write a date as a string (when using attributes), I highly recommend using the ISO-8601 notation.
That eliminates any confusion as to whether 01/02/2004 is january 2nd or february 1st.
[Range(typeof(DateTime), "2004-12-01", "2004-12-31",
ErrorMessage = "Value for {0} must be between {1} and {2}")]
public datetime Something { get; set;}
I use this approach:
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)]
internal sealed class DateRangeAttribute : ValidationAttribute
{
public DateTime Minimum { get; }
public DateTime Maximum { get; }
public DateRangeAttribute(string minimum = null, string maximum = null, string format = null)
{
format = format ?? #"yyyy-MM-dd'T'HH:mm:ss.FFFK"; //iso8601
Minimum = minimum == null ? DateTime.MinValue : DateTime.ParseExact(minimum, new[] { format }, CultureInfo.InvariantCulture, DateTimeStyles.None); //0 invariantculture
Maximum = maximum == null ? DateTime.MaxValue : DateTime.ParseExact(maximum, new[] { format }, CultureInfo.InvariantCulture, DateTimeStyles.None); //0 invariantculture
if (Minimum > Maximum)
throw new InvalidOperationException($"Specified max-date '{maximum}' is less than the specified min-date '{minimum}'");
}
//0 the sole reason for employing this custom validator instead of the mere rangevalidator is that we wanted to apply invariantculture to the parsing instead of
// using currentculture like the range attribute does this is immensely important in order for us to be able to dodge nasty hiccups in production environments
public override bool IsValid(object value)
{
if (value == null) //0 null
return true;
var s = value as string;
if (s != null && string.IsNullOrEmpty(s)) //0 null
return true;
var min = (IComparable)Minimum;
var max = (IComparable)Maximum;
return min.CompareTo(value) <= 0 && max.CompareTo(value) >= 0;
}
//0 null values should be handled with the required attribute
public override string FormatErrorMessage(string name) => string.Format(CultureInfo.CurrentCulture, ErrorMessageString, name, Minimum, Maximum);
}
And use it like so:
[DateRange("2004-12-01", "2004-12-2", "yyyy-M-d")]
ErrorMessage = "Value for {0} must be between {1} and {2}")]
I found issues with the [Range(typeof(DateTime)] annotation and would describe it as "clunky at best" it leaves too much to chance IF it works.
Remote validation seems to be a good way of: avoiding javascript in views and maintaining server side code integrity, personally never like sending code to a client to execute if I can avoid it.
Using #StackThis answer as a base and reference to an article on remote validation in MVC3
Model
public class SomeDateModel
{
public int MinYears = 18;
public int MaxYears = 110;
[Display(Name = "Date of birth", Prompt = "e.g. 01/01/1900")]
[Remote(action: "ValidateDateBetweenYearsFromNow", controller: "Validation", areaReference: AreaReference.UseRoot, AdditionalFields = "MinYears,MaxYears", HttpMethod = "GET" ,ErrorMessage = "Subject must be over 18")]
public DateTime? DOB { get; set; }
}
Controller - Deployed at the root directory
namespace Controllers
{
public class ValidationController : Controller
{
[HttpGet]
[ActionName("ValidateDateBetweenYearsFromNow")]
public JsonResult ValidateDateBetweenYearsFromNow_Get()
{
//This method expects 3 parameters, they're anonymously declared through the Request Querystring,
//Ensure the order of params is:
//[0] DateTime
//[1] Int Minmum Years Ago e.g. for 18 years from today this would be 18
//[2] int Maximum Years Ago e.g. for 100 years from today this would be 100
var msg = string.Format("An error occured checking the Date field validity");
try
{
int MinYears = int.Parse(Request.QueryString[1]);
int MaxYears = int.Parse(Request.QueryString[2]);
//Use (0 - x) to invert the positive int to a negative.
var min = DateTime.Now.AddYears((0-MinYears));
var max = DateTime.Now.AddYears((0-MaxYears));
//reset the response error msg now all parsing and assignmenst succeeded.
msg = string.Format("Please enter a value between {0:dd/MM/yyyy} and {1:dd/MM/yyyy}", max, min);
var date = DateTime.Parse(Request.QueryString[0]);
if (date > min || date < max)
//switch the return value here from "msg" to "false" as a bool to use the MODEL error message
return Json(msg, JsonRequestBehavior.AllowGet);
else
return Json(true, JsonRequestBehavior.AllowGet);
}
catch (Exception)
{
return Json(msg, JsonRequestBehavior.AllowGet);
}
}
}
}
The msg variable is displayed as part of the Html helper ValidationSummary or the Html helper ValidationFor(x=>x.DATETIME)
View
It's important to note that the fields passed as parameter 2 and 3 must exist in the view in order for the remote validation to pass the values to the controller:
#Html.EditorFor(m => m.DOB)
#Html.HiddenFor(m => m.MinYears)
#Html.HiddenFor(m => m.MaxYears)
#Html.ValidationSummary()
The model and Html helpers will do all the jquery work for you.