I have a custom model that holds some DateTime values, and a custom DataAnnotation that's built to compare these values.
Here's the properties with their annotations:
[Required]
[DataType(System.ComponentModel.DataAnnotations.DataType.Date)]
[Display(Name = "Start Date")]
public DateTime StartTime { get; set; }
[DataType(System.ComponentModel.DataAnnotations.DataType.Date)]
[Display(Name = "End Date")]
[CompareTo(this.StartTime, CompareToAttribute.CompareOperator.GreaterThanEqual)]
public DateTime? EndTime { get; set; }
The CompareTo attribute is the one in question. I get an error:
Keyword 'this' is not available in the current context
I've tried placing only StartTime in the annotation with no luck. How can I pass in a property value from the same model class?
If anyone is still wondering how to compare two dates and use that in a validation DataAnnotation, you can simply add an extension method that will compare the start date and the end date like the following.
Assuming that this is your class:
using System;
using System.ComponentModel.DataAnnotations;
namespace Entities.Models
{
public class Periode
{
[Key]
public int PeriodeID { get; set; }
public string Name { get; set; }
[DataType(DataType.Date)]
[Display(Name ="Start Date")]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime StartDate { get; set; }
[DataType(DataType.Date)]
[Display(Name = "End Date")]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime EndDate { get; set; }
}
}
You simply add the following class as a validator:
namespace Entities.Models
{
public class StartEndDateValidator : ValidationAttribute
{
protected override ValidationResult
IsValid(object value, ValidationContext validationContext)
{
var model = (Models.Periode)validationContext.ObjectInstance;
DateTime EndDate = Convert.ToDateTime(model.EndDate);
DateTime StartDate = Convert.ToDateTime(value);
if (StartDate > EndDate)
{
return new ValidationResult
("The start date must be anterior to the end date");
}
else
{
return ValidationResult.Success;
}
}
}
}
And then you need to add that DataAnnotation on the StartDate as following
namespace Entities.Models
{
public class Periode
{
[Key]
public int PeriodeID { get; set; }
public string Name { get; set; }
[DataType(DataType.Date)]
[Display(Name ="Start Date")]
// You need to add the following line
[StartEndDateValidator]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime StartDate { get; set; }
[DataType(DataType.Date)]
[Display(Name = "End Date")]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime EndDate { get; set; }
}
}
I've tried placing only StartTime in the annotation with no luck. How
can I pass in a property value from the same model class?
That's impossible because attributes are metadata that is baked into the assembly at compile-time. This means that you can pass only CONSTANT parameters to an attribute. Yeah, that's a hell of a limitation because in order to perform such an obvious validation thing as comparing 2 values in your model you will have to write gazzilion of plumbing code such as what I have illustrated here for example: https://stackoverflow.com/a/16100455/29407 I mean, you will have to use reflection! Come on Microsoft! Are you serious?
Or just cut the crap of data annotations and start doing validation the right way: using FluentValidation.NET. It allows you to express your validation rules in a very elegant way, it greatly integrates with ASP.NET MVC and allows you to unit test your validation logic in isolation. It also doesn't rely on reflection so it is super fast. I have benchmarked it and using it in very heavy traffic production applications.
Data annotations just don't cut the mustard compared to imperative validation rules when you start writing applications that are a little more complicated than a Hello World and which require a little more complex validation logic than you would have in a Hello World application.
I like Hassen's answer.
Same as Hassen's example, but suggest:
1) aborting if no end date if end date is optional.
2) putting a validator on end date in case user only changes end date
Data Annotation:
[Required]
[Display(Name = "Start Effective Date", Description = "Start Date")]
[DisplayFormat(DataFormatString = "{0:MM/dd/yyyy}")]
[DataType(DataType.Date)]
[StartDateValidator]
public DateTime StartEffectiveDate { get; set; }
[Display(Name = "End Effective Date", Description = "End Date")]
[DisplayFormat(DataFormatString = "{0:MM/dd/yyyy}")]
[DataType(DataType.Date)]
[EndDateValidator]
public DateTime? EndEffectiveDate { get; set; }
Code:
public class StartDateValidator : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var model = (CostCenterAllocationHeader)validationContext.ObjectInstance;
if (model.EndEffectiveDate == null) // Abort if no End Date
return ValidationResult.Success;
DateTime EndDate = model.EndEffectiveDate.GetValueOrDefault();
DateTime StartDate = Convert.ToDateTime(value); // value = StartDate
if (StartDate > EndDate)
return new ValidationResult("The start date must be before the end date");
else
return ValidationResult.Success;
}
}
public class EndDateValidator : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var model = (CostCenterAllocationHeader)validationContext.ObjectInstance;
if (model.EndEffectiveDate == null) // Abort if no End Date
return ValidationResult.Success;
DateTime EndDate = Convert.ToDateTime(value); // value = EndDate
DateTime StartDate = model.StartEffectiveDate;
if (StartDate > EndDate)
return new ValidationResult("The start date must be before the end date");
else
return ValidationResult.Success;
}
}
Would have commented on Hassen's answer, but don't have enough reputation.
public class StartDateValidator : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var model = (CostCenterAllocationHeader)validationContext.ObjectInstance;
if (model.EndEffectiveDate == null) // Abort if no End Date
return ValidationResult.Success;
DateTime EndDate = model.EndEffectiveDate.GetValueOrDefault();
DateTime StartDate = Convert.ToDateTime(value); // value = StartDate
if (StartDate > EndDate)
return new ValidationResult("The start date must be before the end date");
else
return ValidationResult.Success;
}
}
In the above example there is one issue , This solution can't be used as a common solution for validating the dates . Because the type casting of below line is not generic
var model = (CostCenterAllocationHeader)validationContext.ObjectInstance;
It means that the validation can only be applied to the specific model "costCenterAllocationHeader" . What needs to be done by passing the member name to the constructor of the validator and get the value from the ValidationContext using reflection. By this method we can use this attribute as a generic solution and can be applied in any ViewModels.
Related
My Blazor application has two forms in different components. Both forms use he same view model. Though the model is the same, different fields are displayed in the components. E.g. the first component's form does not have the UnitPrice field, but the second does. I use a simple validation:
[Required(ErrorMessage = "Unit Price is required.")]
[Range(0.01, double.MaxValue, ErrorMessage = "Unit Price must be greater than 0")]
public double UnitPrice { get; set; }
Unfortunately, when the first form is displayed and submitted, the missing field is validated, and the validation fails. Is there any way to do it without splitting the model or using custom validation?
Example as requested:
public interface IForm
{
int FormStatus { get; set; }
// Your other fields that are always shared on this form...
}
public class Form1 : IForm
{
public int FormStatus { get; set; }
[Required(ErrorMessage = "Unit Price is required.")]
[Range(0.01, double.MaxValue, ErrorMessage = "Unit Price must be greater than 0")]
public decimal UnitPrice { get; set; }
}
public class Form2 : IForm
{
public int FormStatus { get; set; }
[Required]
public string Name { get; set; }
// I made this up but it demonstrates the idea of encapsulating what differs.
}
Your shared Blazor Component would be something like.
// SharedFormFields.razor
<input type="text" #bind-Value="_form.FormStatus">
#code {
[Parameter] private IForm Form { get; set; }
}
And then your consuming components/pages
#page "/Form1"
<EditContext Model=_form1>
<SharedFormFields Form=_form1>
<input type="number" #bind-Value="_form1.UnitPrice">
</EditContext
#code {
private Form1 _form1 = new()
}
I used conditional validation by deriving my view model from IValidatableObject and implementing it:
public class MyViewModel : IValidatableObject
{
...
public double UnitPrice { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
var result = new List<ValidationResult>();
if (StatusId >= (int)Models.Status.ReadyForReview) // my condition
{
if (UnitPrice == 0)
{
result.Add(new ValidationResult("Unit Price is required."));
}
else if (UnitPrice < 0)
{
result.Add(new ValidationResult("Unit Price must be greater than 0."));
}
}
return result;
}
}
In my ASP.Net mvc3 Razor project i have to implement date validation.My format for the same was dd-mm-yyyy.i tried in different way but none works fine .I need a simple one.My question is is there any regular expression for the same.
My Model Code
{
[Table("tbl_Employee")]
public class Employee
{
[Key]public int EmpId { get; set; }
[Required(ErrorMessage="Employee First Name is Required")]
public string FirstName { get; set; }
public string MiddleName { get; set; }
[Required(ErrorMessage="Employee Last Name is Required")]
public string LastName { get; set; }
public int Age{get;set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:d}", ApplyFormatInEditMode = true)]
public DateTime DateOfBirth { get; set; }
public string Address { get; set; }
public string Position { get; set; }
public string Department { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:d}", ApplyFormatInEditMode = true)]
public DateTime DateOfJoining { get; set; }
public string EducationalQuali { get; set; }
public string Experience { get; set; }
public string Others { get; set; }
}
View Code
<div class="col-lg-10" >#Html.TextBoxFor(model => model.DateOfBirth, new { #class = "form-control", placeholder = "Date/Month/Year" })</div>
I have used one placeholder to show the user that ""this was the format".But it is also creating problem from the user.how to solve this?
You can decorate the model property with RegularExpression Attribute Class with the following pattern,
^(0[1-9]|[12][0-9]|3[01])[- /](0[1-9]|1[012])[- /](19|20)[0-9][0-9]$
It will validate following date formats,
dd/MM/yyyy
dd-MM-yyyy
The property value will be set valid if it satisfies the pattern. You can set the custom error message in ErrorMessage property.
[RegularExpression("^(0[1-9]|[12][0-9]|3[01])[- /](0[1-9]|1[012])[- /](19|20)[0-9][0-9]$", ErrorMessage="")]
public DateTime DateOfBirth { get; set; }
This works well on both server and client side. For client side which is better for good user experience, do not forget to turn on the unobtrusive validation and include jquery.validation.js and jquery.validation.unobtrusive.js in your web page. :)
I am using Range Validator to validate the date. My requirement is validate the input date should be between current date and current date -60 years.
This is what I tried so far,
[Range(typeof(DateTime),DateTime.UtcNow.ToString(),DateTime.UtcNow.AddYears(-60).Date.ToString())]
public DateTime? DOJ { get; set; }
But this throws error : An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type.
So I modified my code:
#region variables
public const string MaxDate = DateTime.UtcNow.ToString();
public const string MinDate = DateTime.UtcNow.AddYears(-60).Date.ToString();
#endregion
And Set property :
[Range(typeof(DateTime),maximum:MaxDate,minimum:MinDate)]
public DateTime? DOJ { get; set; }
Now the error is :The expression being assigned to 'MaxDate' must be constant.
Same for MinDate.
What's the solution?
You can't use variables in Attributes. All items in attributes must be constant. If you want to filter value based on dynamic values, then you can make new ValidationAttribute like this:
public class ValidateYearsAttribute : ValidationAttribute
{
private readonly DateTime _minValue = DateTime.UtcNow.AddYears(-60);
private readonly DateTime _maxValue = DateTime.UtcNow;
public override bool IsValid(object value)
{
DateTime val = (DateTime)value;
return val >= _minValue && val <= _maxValue;
}
public override string FormatErrorMessage(string name)
{
return string.Format(ErrorMessage, _minValue, _maxValue);
}
}
And then you just need to place it on your property:
[ValidateYears]
public DateTime? DOJ { get; set; }
You can update FormatErrorMessage based on what you need.
I am trying to update user in my model object
public ActionResult AddJob(JobQueue job,HttpPostedFileBase file)
{
job.User = "itdev";
TryUpdateModel(job)
if (ModelState.IsValid)//Always returns false
{
}
}
MODEL
public class JobQueue {
[Required]
[Display(Name="JobId")]
public string JobId { get; set; }
[Required] [Display(Name = "FileName")]
public string FileName { get; set; }
[Required]
[Display(Name = "Job Run Date")]
public DateTime JobRunDate { get; set; }
[Required]
[Display(Name = "Email")]
public string Mail { get; set; }
[Required]
[Display(Name = "User")]
public string User { get; set; }
I tried using TryUpdateModel(job) and UpdateModel(job) after assigning the values.Both of these does not seem to update the model because ModelState.IsValid return false.Can someone point me in the right directions?I am using MVC3
Thanks,
Sab
I may be wrong here, but I think job.User = "itdev"; should be sufficent to update the model without using the TryUpdateModel(job) thats how we do it in our site anyway. I have never need to use any method to actually update the model itself. Just assigned values manually.
It depends on how your model is setup I guess.
You should probably post the code for your model just in case my answer isnt helpful.
I need a linq statement that returns all entries from a certain date. A the moment I have a class in my controller which handles Events. My Index class contains a linq statement which groups the vents by date and returns how many there are in each date. I want a browse class which returns a list of Events connected with a certain date. Here is my mode:
namespace NewAtAClick.Models
{
public class WhatsOn
{
public int ID { get; set; }
public DateTime? start { get; set; }
public DateTime? end { get; set; }
public string Name { get; set; }
public string Desc { get; set; }
public string link { get; set; }
public bool CalenderDisplay { get; set; }
public DateTime? day { get; set; }
public int whtscount { get; set; }
}
}
And here's my classes in the WhatsOn controller;
public ViewResult Index(WhatsOn model)
{
DateTime myDate = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day);
var datequery =
db.WhatsOns.Where(c => c.start > myDate)
.OrderByDescending(c => c.start)
.GroupBy(c => c.start).AsEnumerable().Select(
sGroup => new WhatsOn
{
day = sGroup.Key,
whtscount = sGroup.Count()
});
return View(datequery);
}
public ViewResult Browse(DateTime? day , int? id)
{
var eventsquery = from c in db.WhatsOns
where c.start == day
select c;
return View(eventsquery);
}
A the moment the linq query in the browse class returns nothing, just an empty table. Any help is greatly appreciated! Thanks.
UPDATE:
Hey! Got it working
Here;s my new controller;
public ViewResult Browse(int? id, DateTime? day, DateTime? start)
{
var eventsquery = from c in db.WhatsOns where c.start.Value.Day == day.Value.Day select c;
return View(eventsquery);
}
And what did the trick, in my actionlink in my view....
#Html.ActionLink("Browse", "Browse", new { start=item.start, day=item.day })
Thanks for you help!!
does
var eventsquery = from c in db.WhatsOns
where c.start.Value.Date == day.Value.Date
select c;
work?
When comparing DateTime objects, keep in mind that the '==' sign also looks at seconds, miliseconds, etc.
Do you have any results you DO expect? Is the database table filled with information?
Edit: DateTime.Now you already used ;)