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; }
}
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 am learning Dependency Injection.
How can I make my "UserCreate" model to use the custom constructor I set when it is being used as a parameter on controller action? I want to pass the UserContext to my UserCreate model.
My action:
[HttpPost]
public JsonResult Post(UserCreate model)
{
var user = _repository.GetByUserName(model.Email);
if (user != null)
{
this.ModelState.AddModelError(nameof(model.Email), "Email already registered!");
}
else
{
if (ModelState.IsValid)
{
var userModel = _mapper.Map<User>(model);
_repository.Add(userModel);
_repository.SaveChanges();
return Json(new { success = "true" });
}
}
return Json(new { success = "false", errors = this.ModelErrors(this.ModelState) });
}
My Model
public class UserCreate : BaseModel
{
private readonly IUserRepo repo;
public UserCreate(UserContext context) : base(context){
repo = new UserRepository(context);
}
public UserCreate():base() { }
[Required]
[MaxLength(100)]
public string Email { get; set; }
[Required]
[MaxLength(30)]
public string Password { get; set; }
[Required]
[MaxLength(30)]
public string FirstName { get; set; }
[Required]
[MaxLength(30)]
public string MiddleName { get; set; }
[Required]
[MaxLength(30)]
public string LastName { get; set; }
[Required]
public int Age { get; set; }
[Required]
public DateTime Birthday { get; set; }
[Required]
[MaxLength(250)]
public string Adddress { get; set; }
public DateTime Created { get { return DateTime.Now; } }
}
I've set it on startup:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<UserContext>(opt => opt.UseSqlServer
(Configuration.GetConnectionString("Dev")));
services.AddControllers();
services.AddScoped<IUserRepo, UserRepository>();
services.AddScoped<ICardRepo, CardRepository>();
services.AddScoped<IUserContext, UserContext>();
services.AddScoped<TransactCreate, TransactCreate>();
services.AddSingleton<UserCreate>(x =>
new UserCreate(x.GetRequiredService<UserContext>()));
I have set the Addsingleton on startup however when i test my API, public UserCreate():base() { } constructor is called instead of the constructor with UserContext parameter. I am using netcore 3.1
the reason why I want do this is I to move my validations to model and i need to use UserContext from there.
Thanks!
I understand what you are asking, but please understand that your approach to this problem is very flawed. Your view model should absolutely know nothing about your repository.
In MVC, the Controller is responsible for handling HTTP requests (as well as model validation), and delegating actions to the rest of the application. The Model (UserCreate), should be a simple poco that only exists to transfer data from the client back to your controller. The controller should then delegate responsibility to the repository for handling the data.
Your controller should, instead, accept the repository via DI, and then send the UserCreate model through, after validating it. And your model, UserCreate, should 100% have a parameterless constructor, as the ModelBinder is going to build it up from the request.
however what I want to achieve is if I have multiple properties that i
need to validate from the database, i dont want to write them all in
my controller action. Can you recommend the right way to handle custom
validations?
According to your code and the previous discuss, I suppose you want to valid whether the entered value is exist in the database, if the value exist, display the error message, such as "Email already registered". If that is the case, it is better to use the [Remote] attribute:
Code as below:
[Remote(action: "VerifyEmail", controller: "Users")]
public string Email { get; set; }
and
[AcceptVerbs("GET", "POST")]
public IActionResult VerifyEmail(string email)
{
if (!_userService.VerifyEmail(email))
{
return Json($"Email {email} is already in use.");
}
return Json(true);
}
Besides, if you want to create custom validation, you can check this thread, then, in the Custom validation IsValid method, you could get the current dbcontext and check whether the entered data is valid or not. Code as below:
code in the model:
[Required(ErrorMessage ="Country is Required")]
public string Country { get; set; }
[RequiredIfHasState("Country", ErrorMessage ="State is Required")]
public string State { get; set; }
code in the custom valiation:
public class RequiredIfHasStateAttribute : ValidationAttribute
{
private readonly string _comparisonProperty;
public RequiredIfHasStateAttribute(string comparisonProperty)
{
_comparisonProperty = comparisonProperty;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
ErrorMessage = ErrorMessageString;
//get entered state value
var stateValue = (string)value;
var property = validationContext.ObjectType.GetProperty(_comparisonProperty);
if (property == null)
throw new ArgumentException("Property with this name not found");
//get the country value
var countryValue = (string)property.GetValue(validationContext.ObjectInstance);
//get the current dbcontext
var _context = (MvcMovieContext)validationContext.GetService(typeof(MvcMovieContext));
//query the database and check whether the country has state.
if (_context.Countries.Where(c => c.CountryCode == countryValue).Select(c => c).FirstOrDefault().HasState)
{
if(stateValue == null)
{
//if country has state and the state is null. return error message
return new ValidationResult(ErrorMessage);
}
else
{
//if country has state and the state is not found.
if(!_context.Countries.Where(c => c.CountryCode == countryValue).Any(c => c.States.Any(e => e.StateName == stateValue)))
{
return new ValidationResult("State not found");
}
}
}
return ValidationResult.Success;
}
}
I have a second thing:
<td>
#Html.LabelFor(m => m.Number, titleHtmlAttrs)
</td>
<td>
<span class="element-value2">
#Html.EditorFor(m => m.Number)
#Html.ValidationTooltipFor(m => m.Number)
</span>
</td>
And this is how this field looks like in model:
[Display(Name = "Special Number")]
[StringLength(20)]
public string Number { get; set; }
Which means that if I wanted to change this field, i can have any value from empty to 20.
It's ok, but now I need an additional validation.
In model I have some fields:
public DateTime? TimeOf { get; set; }
public bool HasType { get; set; }
New validation should work ONLY if TimeOf is not null and HasType is true. New validation should prevent empty values in Number. Basically, change (from empty to 20) to (from 1 to 20).
How could I correctly accomplish this?
P.S Sorry about my bad English.
For complex validation logic, look at implementing IValidatableObject in your ViewModel and then you can place your conditional validation logic inside the Validate method. (Caveat, this is obviously server side)
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (this.HasType)
{
// Do other conditional validation
if (validationFails)
{
yield return new ValidationResult("descriptive error goes here");
}
}
// Other validation here.
UPDATE It seems I have misunderstood the question. As the other answer has already pointed out, you could implement the IValidatableObject for achieving this. Something like:
public class YourModelName : IValidatableObject
{
[StringLength(20)]
public string Number{ get; set; }
[Required]
public DateTime? TimeOf { get; set; }
public bool HasType { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
var results = new List<ValidationResult>();
if (TimeOf != null && HasType)
Validator.TryValidateProperty(this.Number,
new ValidationContext(this, null, null) { MemberName = "Number" },
results);
if (TimeOf == null)
results.Add(new ValidationResult("Date Time must have a value"));
if (!HasType)
results.Add(new ValidationResult("Must be true"));
return results;
}
}
OLD ANSWER:
You could write your custom validator for more complex validation conditions. Something like:
public class SomeCustomValidator : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
string number = value as string;
if (value == null) throw new InvalidOperationException("Can only be used on string properties");
if (!value.IsEmpty && value.Length <= 20)
{
return ValidationResult.Success;
}
return new ValidationResult("Name must be a non-empty string smaller than 20 chars"));
}
}
And for HasType, another custom one:
public class IsTrueAttribute : ValidationAttribute
{
public override bool IsValid(object value)
{
if (value == null) return false;
if (value.GetType() != typeof(bool)) throw new InvalidOperationException("can only be used on boolean properties.");
return (bool) value == true;
}
}
And on TimeOf you could use the required attribute to make sure it has a value:
[Required(ErrorMessage="Must have value")]
public DateTime? TimeOf {get;set;}
And use the custom attributes on the other two:
[SomeCustomValidator(ErrorMessage="Error msg...")]
public string Number {get;set;}
[IsTrueAttribute(ErrorMessage="Must be true")]
public bool HasType {get;set;}
I have a Model with 4 properties which are of type string. I know you can validate the length of a single property by using the StringLength annotation. However I want to validate the length of the 4 properties combined.
What is the MVC way to do this with data annotation?
I'm asking this because I'm new to MVC and want to do it the correct way before making my own solution.
You could write a custom validation attribute:
public class CombinedMinLengthAttribute: ValidationAttribute
{
public CombinedMinLengthAttribute(int minLength, params string[] propertyNames)
{
this.PropertyNames = propertyNames;
this.MinLength = minLength;
}
public string[] PropertyNames { get; private set; }
public int MinLength { get; private set; }
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var properties = this.PropertyNames.Select(validationContext.ObjectType.GetProperty);
var values = properties.Select(p => p.GetValue(validationContext.ObjectInstance, null)).OfType<string>();
var totalLength = values.Sum(x => x.Length) + Convert.ToString(value).Length;
if (totalLength < this.MinLength)
{
return new ValidationResult(this.FormatErrorMessage(validationContext.DisplayName));
}
return null;
}
}
and then you might have a view model and decorate one of its properties with it:
public class MyViewModel
{
[CombinedMinLength(20, "Bar", "Baz", ErrorMessage = "The combined minimum length of the Foo, Bar and Baz properties should be longer than 20")]
public string Foo { get; set; }
public string Bar { get; set; }
public string Baz { get; set; }
}
Self validated model
Your model should implement an interface IValidatableObject. Put your validation code in Validate method:
public class MyModel : IValidatableObject
{
public string Title { get; set; }
public string Description { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (Title == null)
yield return new ValidationResult("*", new [] { nameof(Title) });
if (Description == null)
yield return new ValidationResult("*", new [] { nameof(Description) });
}
}
Please notice: this is a server-side validation. It doesn't work on client-side. You validation will be performed only after form submission.
ExpressiveAnnotations gives you such a possibility:
[Required]
[AssertThat("Length(FieldA) + Length(FieldB) + Length(FieldC) + Length(FieldD) > 50")]
public string FieldA { get; set; }
To improve Darin's answer, it can be bit shorter:
public class UniqueFileName : ValidationAttribute
{
private readonly NewsService _newsService = new NewsService();
public override bool IsValid(object value)
{
if (value == null) { return false; }
var file = (HttpPostedFile) value;
return _newsService.IsFileNameUnique(file.FileName);
}
}
Model:
[UniqueFileName(ErrorMessage = "This file name is not unique.")]
Do note that an error message is required, otherwise the error will be empty.
Background:
Model validations are required for ensuring that the received data we receive is valid and correct so that we can do the further processing with this data. We can validate a model in an action method. The built-in validation attributes are Compare, Range, RegularExpression, Required, StringLength. However we may have scenarios wherein we required validation attributes other than the built-in ones.
Custom Validation Attributes
public class EmployeeModel
{
[Required]
[UniqueEmailAddress]
public string EmailAddress {get;set;}
public string FirstName {get;set;}
public string LastName {get;set;}
public int OrganizationId {get;set;}
}
To create a custom validation attribute, you will have to derive this class from ValidationAttribute.
public class UniqueEmailAddress : ValidationAttribute
{
private IEmployeeRepository _employeeRepository;
[Inject]
public IEmployeeRepository EmployeeRepository
{
get { return _employeeRepository; }
set
{
_employeeRepository = value;
}
}
protected override ValidationResult IsValid(object value,
ValidationContext validationContext)
{
var model = (EmployeeModel)validationContext.ObjectInstance;
if(model.Field1 == null){
return new ValidationResult("Field1 is null");
}
if(model.Field2 == null){
return new ValidationResult("Field2 is null");
}
if(model.Field3 == null){
return new ValidationResult("Field3 is null");
}
return ValidationResult.Success;
}
}
Hope this helps. Cheers !
References
Code Project - Custom Validation Attribute in ASP.NET MVC3
Haacked - ASP.NET MVC 2 Custom Validation
A bit late to answer, but for who is searching.
You can easily do this by using an extra property with the data annotation:
public string foo { get; set; }
public string bar { get; set; }
[MinLength(20, ErrorMessage = "too short")]
public string foobar
{
get
{
return foo + bar;
}
}
That's all that is too it really. If you really want to display in a specific place the validation error as well, you can add this in your view:
#Html.ValidationMessage("foobar", "your combined text is too short")
doing this in the view can come in handy if you want to do localization.
Hope this helps!
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; }
}