MVC5: Custom validations are not triggered when Submit button is clicked - data-annotations

I am creating a selection screen, let say there are two fields "Start Date" and "End Date" and a "Search" button.
I created custom attribute for validating the date fields such that it must fall within a dynamic date range:
public class YearTimeFrameAttribute : ValidationAttribute
{
private DateTime _minDate;
public YearTimeFrameAttribute(int timeFrameInYear)
{
int _timeFrame = Math.Abs(timeFrameInYear) * -1;
_minDate = DateTime.Now.AddYears(_timeFrame);
}
public override bool IsValid(object value)
{
DateTime dateTime = Convert.ToDateTime(value);
if (dateTime < _minDate)
{
return false;
}
else
{
return true;
}
}
public override string FormatErrorMessage(string name)
{
return String.Format("The field {0} must be >= {1}", name, _minDate.ToString("dd/MM/yyyy"));
}
}
Below is the code in the selection screen model:
public class SelectionParametersModel
{
[YearTimeFrame(7)]
[DataType(DataType.Date)]
[Display(Name = "Extraction Start Date")]
public DateTime StartDate { get; set; }
[YearTimeFrame(7)]
[DataType(DataType.Date)]
[Display(Name = "Extraction End Date")]
public DateTime EndDate { get; set; }
}
Finally my controller do this (I am trying to return a file):
// GET: /SelectionScreen/
public ActionResult SelectionScreen()
{
ViewBag.Title = "Selection Screen";
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult SelectionScreen(SelectionParametersModel selectionParameter)
{
... Code to build report ...
string mimeType;
Byte[] renderedBytes;
Functions.GenerateReport(out renderedBytes, out mimeType);
return File(renderedBytes, mimeType);
}
So I input a wrong date in the start/end date fields and click "Search", the program just ignore the validations and generate the file.
(Note: I haven't paste all my code here to keep thing simpler, but I have proved that the date validation logic is correct.)

Sorry that I find my solution shortly after posting the question. (I am really new in MVC)
I find that I should include "if (ModelState.Isvalid)" in the HttpPost method.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult SelectionScreen(SelectionParametersModel selectionParameter)
{
if (ModelState.IsValid)
{
... Code ....
}
return View();
}

Related

Why the server side validation is not working?

This is my Movies controller.....
public class MoviesController : Controller
{
MoviesEntities db = new MoviesEntities();
public ActionResult Index()
{
var movies = from m in db.Films
where m.ReleaseDate > new DateTime(1989, 12, 20)
select m;
return View(movies.ToList());
}
public ActionResult Create()
{
return View();
}
[HttpPost]
public ActionResult Create(Film newFilm)
{
..some code for adding new movie in the database
}
}
and created Movie class in the model
namespace Movies.Models
{
[MetadataType(typeof(MovieMetadata))]
public partial class Movie
{
class MovieMetadata
{
[Required(ErrorMessage = "*")]
public string Title { get; set; }
[Required(ErrorMessage = "*")]
[Range(5, 100, ErrorMessage = "Movies cost between $5 and $100.")]
public decimal Price { get; set; }
}
}
}
This should give me proper validations.. but the range is not working..
also... they are getting added into database
[HttpPost]
public ActionResult Create(Film newFilm)
{
if (ModelState.IsValid)
{
..some code for adding new movie in the database
}
}
Do this. And with regard to the comment of Aman who is saying or JQuery validation. Clientside validation cannot be a replacement for serverside validation. So always use the ModelState validation next to clientside.

Model Validation not working with all properties

I have the following ViewModel:
public class MyViewModel:IValidatableObject
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime? Birthday { get; set; }
public int Status { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (string.IsNullOrWhiteSpace(Name))
yield return new ValidationResult("Please fill the name", new string[] { "Name" });
if (Birthday.HasValue == false)
yield return new ValidationResult("Please fill the birthday", new string[] { "Birthday" });
if(Status <= 0)
yield return new ValidationResult("Please fill the status", new string[] { "Status" });
}
}
Controller:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "Id,Name,Birthday,Status")] MyViewModel myViewModel)
{
if (ModelState.IsValid)
{
db.MyViewModels.Add(myViewModel);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(myViewModel);
}
I would like to display all the validation messages at the same time, however it shows first status and then the other two properties.
This is due to the order in which validation happens. First the ModelBinder does it's job, and if that passes, since you've created a self validating viewmodel by implementing IValidatableObject, the Validate method is called. In the first screenshot the modelbinding process is failing so Validate is never called. In the second screenshot, modelbinding succeeds, but Validate() fails.
You can solve this by using DataAnnotations instead of implementing IValidatableObject like so:
public class MyViewModel:IValidatableObject
{
public int Id { get; set; }
[Required]
public string Name { get; set; }
[Required]
public DateTime Birthday { get; set; }
[Required, Range(0, Int32.MaxValue)]
public int Status { get; set; }
}

Create item using a ViewModel setting unwanted defaults

I am using a ViewModel to Create a new item in my DB
the ViewModel has only the properties that I want the user to be able to set, and when it is posted back I make a new 'real' object and save it away to the DB.
I am doing this as detailed below
[HttpGet]
public ActionResult Create(int id = 0)
{
var opt = unitOfWork.OptionRepository.GetByID(id);
CreateAvailabilityViewModel model = new CreateAvailabilityViewModel();
model.OptionDescription = opt.Description;
model.CentreCode = opt.CentreCode;
model.OptionID = id;
return View(model);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(CreateAvailabilityViewModel cavm)
{
if (ModelState.IsValid)
{
OptionAvailability newOA = new OptionAvailability();
DateTime now = DateTime.Now;
newOA.ChangedDate = newOA.CreatedDate = now;
newOA.ChangedBy = newOA.CreatedBy = User.Identity.Name;
newOA.DateFrom = cavm.DateFrom;
newOA.DateTo = cavm.DateTo;
newOA.MinNumbers = cavm.MinNumbers;
newOA.MaxNumbers = cavm.MaxNumbers;
newOA.OptionID = cavm.OptionID;
unitOfWork.OptionAvailabilityRepository.Insert(newOA);
unitOfWork.Save();
return RedirectToAction("Detail", "Option", new { id = newOA.OptionID });
}
return View(cavm);
}
and this is the ViewModel
public class CreateAvailabilityViewModel
{
[HiddenInput(DisplayValue = false)]
public int OptionAvailabilityID { get; set; }
[HiddenInput(DisplayValue = false)]
public int OptionID { get; set; }
[Required]
public DateTime DateFrom { get; set; }
[Required]
public DateTime DateTo { get; set; }
[Required]
public int MinNumbers { get; set; }
[Required]
public int MaxNumbers { get; set; }
public string CentreCode { get; set; }
public string OptionDescription { get; set; }
}
the problem I am facing is that when the form is rendered the form fields for the dates and ints are defaulting to 01/01/0001 and 0 instead of being blank. I am using the Html.EditorFor helpers to render the inputs I assume it is because in the HttpGet Create method, when I instantiate the ViewModel it uses the type defaults and then passes them through in the object to the form, but this is not wha tI want to be happening.. do I need to set these properties to DateTime? and/or int? ?
I am pretty sure this is good practice to use but am a bit stumped as to why
can anyone explain what I am doing wrong please
thanks muchly
You can instantiate the dates with whatever values you want.
You could use backing fields in your viewmodel (instead of auto properties) and initialize them:
public class MyViewModel
{
private DateTime _firstDate = new DateTime(12/12/2012);
private DateTime _secondDate = DateTime.Now();
public DateTime FirstDate { get { return _firstDate; } set { _firstDate = value; } }
...
}
Or you could initialize them in the viewmodel's constructor:
public class MyViewModel
{
public MyViewModel(DateTime firstDate)
{
FirstDate = firstDate;
SecondDate = DateTime.Now();
}
public DateTime FirstDate { get; set; }
....
}
Or you could initialize them in your controller; you probably get the idea.
Also consider decorating these members with metadata:
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime FirstDate { get; set; }

Date Range Validation making control turn into a datetime input type, just want text type

I have a Razor web page where I have a model
public class UploadModel
{
[Required]
[StringLength(25)]
public string PatientID { get; set; }
[DataType(DataType.Date)]
[DateRange("1000/12/01", "4010/12/16")]
public DateTime DrawDate { get; set; }
}
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);
}
}
That does validation for the datetime
However in the view,
when I run through all the elements in the model
#Html.EditorFor(m => m)
It is creating a datetime type which creates problems because I am using jquery to do the calendar date picking since it is cross broswer. Any way to force the datetime to become a text even with the validation class? Thanks!
I believe you just need to remove the DataType attribute from the DrawDate property.
Since you are writing your custom validation, why don't you change
public DateTime DrawDate { get; set; }
to a string type and adjust your validation accordingly?

ASP.NET MVC. Validation fails on dropdown no matter the value

I've got a form with a dropdownlist in my MVC app. Now that I'm trying to add validation to the mix it seems that a dropdownlist fails validation no matter what it's value is.
Without the validation it will allow the controller to work and redirect as planned. With the validation it does seem to allow the database changes to occur but ModelState.IsValid is false.
I'm stuck. Is this a known issue?
View:
<label for="parent">Child of:</label>
<%= Html.DropDownList("parent", (SelectList)ViewData["pageList"])%>
<%= Html.ValidationMessage("parent") %>
Controller action:
[AcceptVerbs(HttpVerbs.Post)]
[ValidateInput(false)]
[ValidateAntiForgeryToken()]
public ActionResult Create(Page page)
{
try
{
pageRepository.Insert(page);
}
catch (RuleException ex)
{
ex.CopyToModelState(ModelState);
}
if (!ModelState.IsValid)
{
var pageSelectList = pageRepository.GetTop().ToList();
pageSelectList.Add(new Page
{
menuTitle = "None"
});
ViewData["pageList"] = new SelectList(pageSelectList.OrderBy(x => x.listOrder), "ID", "menuTitle");
return View();
}
return RedirectToAction("List");
}
The error returned is: The value 'x' is invalid.
Where 'x' is the numeric value of the current selection. The failure occurs no matter what the chosen value is.
public class Page
{
private EntityRef<Page> _parent = default(EntityRef<Page>);
private EntitySet<Page> _children = new EntitySet<Page>();
public int ID { get; set; }
public string pageTitle { get; set; }
public string menuTitle { get; set; }
public string content { get; set; }
public int listOrder { get; set; }
public bool visible { get; set; }
public int parent { get; set; }
public DateTime? created { get; set; }
public DateTime? edited { get; set; }
public string createdBy { get; set; }
public string lastEditBy { get; set; }
public string linkInfo { get; set; }
public bool IsSelected { get; set; }
public Page Parent
{
// return the current entity
get { return this._parent.Entity; }
set { this._parent.Entity = value; }
}
public EntitySet<Page> Children
{
get { return this._children; }
set { this._children.Assign(value); }
}
public static Page Error404()
{
return (new Page
{
content = "<p>Page not found</p>",
pageTitle = "404. Page not found"
});
}
}
Here's what I tried for a workaround:
public ActionResult Create([Bind(Exclude="parent")] Page page)
{
page.parent = Convert.ToInt32(Request.Form["parent"]);
...
I just excluded the dropdownlist from the ModelBinding and reloaded it back in via the Request.Form. Is it good practice?
What's throwing the RuleException? I'm assuming you're using some sort of validation engine to determine whether the "parent" property is valid or not. I'd step through to see why this exception is being thrown. Maybe the value isn't passing into your controller action correctly or maybe your validation rules are different than what you think they are.
I ended up testing against ModelState["parent"].Value.AttemptedValue instead of the entity property which was nulling out at the attempt to put a string into an int?.

Resources