I have a data validation class that checks whether the start date of a meeting is before the end date.
The model automatically passes in the date that requires validation, but i'm having a bit of difficulty passing the data that it needs to be validated against.
Here's my validation class
sealed public class StartLessThanEndAttribute : ValidationAttribute
{
public DateTime DateEnd { get; set; }
public override bool IsValid(object value)
{
DateTime end = DateEnd;
DateTime date = (DateTime)value;
return (date < end);
}
public override string FormatErrorMessage(string name)
{
return String.Format(CultureInfo.CurrentCulture, ErrorMessageString, name);
}
}
Here's the class that contains the data annotations
[StartLessThanEnd(ErrorMessage="Start Date must be before the end Date")]
public DateTime DateStart { get; set; }
And here's my controller
[HttpPost, Authorize]
public ActionResult Create(Pol_Event pol_Event)
{
ViewData["EventTypes"] = et.GetAllEventTypes().ToList();
StartLessThanEndAttribute startDateLessThanEnd = new StartLessThanEndAttribute();
startDateLessThanEnd.DateEnd = pol_Event.DateEnd;
if (TryUpdateModel(pol_Event))
{
pol_Event.Created_On = DateTime.Now;
pol_Event.Created_By = User.Identity.Name;
eventRepo.Add(pol_Event);
eventRepo.Save();
return RedirectToAction("Details", "Events", new { id = pol_Event.EventID });
}
return View(pol_Event);
}
Validation attributes that work with multiple properties should be applied to the model and not on individual properties:
[AttributeUsage(AttributeTargets.Class)]
public class StartLessThanEndAttribute : ValidationAttribute
{
public override bool IsValid(object value)
{
var model = (MyModel)value;
return model.StartDate < model.EndDate;
}
}
[StartLessThanEnd(ErrorMessage = "Start Date must be before the end Date")]
public class MyModel
{
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
}
Related
THE CONTEXT
I have an MVC form that includes two date pickers, for which I have written a custom validation attribute to ensure that both selected dates are not in the past (>= today).
THE PROBLEM
The validation attribute works only for the first date but not for the second, because the value passed to the "value" object in the validation attribute is always reset to the value set in the form class (HomeForm.cs) constructor (HomeForm( )).
Example (VS + browser screenshot):
The picked date is the 10/08/2021 but the Object value for dateB is 16/08/2021.
issue example
THE CODE
This is my form (HomeForm.cs):
public class HomeForm
{
public HomeForm()
{
dateA = DateTime.Now.Date;
TimeSpan time = new TimeSpan(1, 0, 0, 0);
dateB = DateTime.Now.Date + time;
}
[Required(ErrorMessage = "You must select a departure date.")]
[DataType(DataType.Date)]
[DisplayName("Departure date:")]
[DateTimeRange]
public DateTime dateA { get; set; }
[Required(ErrorMessage = "You must select a return date.")]
[DataType(DataType.Date)]
[DisplayName("Return date:")]
[DateTimeRange]
public DateTime dateB { get; set; }
};
A custom Validation Attribute "DateTimeRange" is applied to dateA and dateB:
[AttributeUsage(AttributeTargets.Property)]
public class DateTimeRangeAttribute : ValidationAttribute
{
private readonly DateTime _today;
public DateTimeRangeAttribute()
{
_today = DateTime.Now.Date;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value is not DateTime)
return new ValidationResult("Date format is not valid.");
if (!IsValid((DateTime)value))
return new ValidationResult("Chosen date is in the past.");
if(value.ToString().Count() != 19)
return new ValidationResult("Not a valid date. Choose a different one.");
return ValidationResult.Success;
}
private bool IsValid(DateTime value)
{
value = value.Date;
return value >= _today;
}
}
I have a class with a remote validation data annotation as follows:
public partial class LuInspectionWindow
{
[Required]
public int InspectionWindowId { get; set; }
[Required]
public string Description { get; set; }
[Required]
[DataType(DataType.Date)]
public DateTime StartDate { get; set; }
[Required]
[Remote("ValidateWindowEndDate", "InspectionWindow", AdditionalFields = "StartDate")]
public DateTime EndDate { get; set; }
}
That calls this annotation:
[AcceptVerbs("Get", "Post")]
public IActionResult ValidateWindowEndDate(DateTime? endDate, DateTime? startDate)
{
int minWeeks = 8;
if (startDate.HasValue && endDate.HasValue
&& (endDate < startDate.Value.AddDays(minWeeks * 7)))
{
return Json(data: $"Inspection window end date must be at least {minWeeks} weeks after start date.");
}
return Json(data: true);
}
If I invalidate the object and then validate it as follows I only get one error, for the null Description (which is marked Required), the remote validation is not checked at all.
luInspectionWindow.EndDate = luInspectionWindow.StartDate.AddDays(1);
luInspectionWindow.Description = null;
var context = new ValidationContext(
luInspectionWindow, serviceProvider: null, items: null);
var results = new List<ValidationResult>();
var isValid = Validator.TryValidateObject(luInspectionWindow, context, results);
await _context.SaveChangesAsync();
The remote validation works fine from the view, so it is wired up correctly. Is Validator.TryValidateObject expected to check remote validations?
In my web api project, I have model class RegisterModel, There is nullable date time, I want to validate input enter by user only when if user has enter the dob.
I'm using Json.Net Serializer
my model class
[Validator(typeof(RegisterModelValidator))]
public class RegisterModel
{
[JsonProperty("dob")]
public Nullable<DateTime> DOB { get; set; }
}
my validator
public class RegisterModelValidator : AbstractValidator<RegisterModel>
{
public RegisterModelValidator()
{
RuleFor(x => x.DOB).Must(BeAValidDate).WithMessage("Please enter valid date.");
}
private bool BeAValidDate(DateTime date)
{
if (date == default(DateTime))
return false;
return true;
}
private bool BeAValidDate(DateTime? date)
{
if (date == default(DateTime))
return false;
return true;
}
}
But when I pass value e.g: "dob":"123 APR 2015"
It ModelState.IsValid return false, But does not return validation message.
Validating DateTimes using fluent validation only does not seem to possible, as mentioned here. This answer seems to provide the most useful ways of actually validating invalid dates.
public ActionResult UpdateDateOfBirth(ModelClass model)
{
if (ModelState.IsValid)
{
// your logic
}
AddErrors(ModelState);
return View();
}
// in my case i have to be add in viewbag
private void AddErrors(ModelStateDictionary result)
{
ViewBag.ErrorMessages = result;
//foreach (KeyValuePair<string,System.Web.Mvc.ModelState> error in result)
//{
// //ModelState.AddModelError(error.Key,error.Value.Errors[0].ErrorMessage);
//}
}
// and you can add the validation message on
public class ModelClass
{
[StringLength(100, ErrorMessage = "Not a valid date of birth"]
[DataType(DataType.DateTime)]
[Display(Name = "Date of Birth")]
public string DateOfBirth{ get; set; }
}
Say I have 2 classes with the same set of properties:
public class MyDto
{
public int Id { get; set; }
public DateTime CreatedOn { get; set; }
}
public class MyViewModel
{
public int Id { get; set; }
public DateTime CreatedOn { get; set; }
}
I want to map with AutoMapper, adjusting the UTC date of the input class to local time of the output class, e.g., granted I am in UK where UTC offset currently is 1h:
var input = new MyDto {Id = 1, CreatedOn = DateTime.Parse("01-01-2015 14:30")};
var output = Mapper.Map<MyViewModel>(input); // output.CreatedOn = "01-01-2015 15:30"
Can I cofigure AutoMapper to this automatically for all DateTime properties?
N.B. to adjust the time I use DateTime.SpecifyKind(value, DateTimeKind.Utc)
You can create a custom type converter:
public class CustomDateTimeConverter : ITypeConverter<DateTime, DateTime> {
public DateTime Convert(ResolutionContext context) {
var inputDate = (DateTime) context.SourceValue;
var timeInUtc = DateTime.SpecifyKind(inputDate, DateTimeKind.Utc);
return TimeZoneInfo.ConvertTime(timeInUtc, TimeZoneInfo.Local);
}
}
This will make AutoMapper perform the conversion from UTC to local time for every mapping between two DateTime properties.
I'm facing an issue with date formatting. Upon calling up the UpdateItem action, the date format for CreatedAt gets messed up. I'm using JSON by the way, so must be something to do with date serialization.
Model:
public class Item
{
public int ItemId { get; set; }
public string ItemName { get; set; }
public string CreatedBy { get; set; }
public DateTime? CreatedAt { get; set; }
public string UpdatedBy { get; set; }
public DateTime? UpdatedAt { get; set; }
}
Create action:
public int CreateItem(Item item)
{
var item = new Item();
viewModel.CopyToItem(item);
item.CreatedBy = WebSecurity.CurrentUserName;
item.CreatedAt = DateTime.Now;
db.Items.Add(item);
db.SaveChanges();
return item.ItemId;
}
Update action:
public void UpdateItem(Item item)
{
item.UpdatedBy = WebSecurity.CurrentUserName;
item.UpdatedAt = DateTime.Now;
db.SaveChanges();
}
The incorrect date format:
/Date(1395366469723)/
It should be:
2014-03-21T09:50:01.747
I tried this in the controller but get a String was not recognized as a valid DateTime' error.
string isoJson = JsonConvert.SerializeObject(DateTime.Now, new IsoDateTimeConverter());
item.CreatedAt = DateTime.ParseExact(isoJson, "yyyy-MM-dd hh:mm:ss.ttt", CultureInfo.InvariantCulture);
Using non-nullable DateTime in the model didn't fix it either.
Javascript uses Unix Time. If you are wanting to get a DateTime object with the given javascript date value, create a new DateTime object from 1/1/1970 and then add the milliseconds.
Observe:
var dt = new DateTime(1970, 1, 1).AddMilliseconds(1395366469723);
// "21/03/2014 1:47:49 AM"