On my Domain Model for my ASP.net MVC3 application I have built a custom validator to ensure the date of birth is inserted in a particular format.
I am saving the date of birth as a string, because my application needs to save the date of birth of long dead people, e.g. Plato, Socrates, etc., just in case you were wondering why not use DateTime to save date of birth.
Here is my custom validator code:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public sealed class ValidateDODDOB : ValidationAttribute
{
// Error Message
private const string DefaultErrorMessage = "Please type the date in the format specified.";
// Gets or sets the Regular expression.
private Regex Regex { get; set; }
// The pattern used for Date of Birth and Date of Death validation.
public string Pattern { get { return #"^(?:\d+\s)?(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)?(?:\s?\d+)(?:\sBCE)?$"; } }
// Initializes a new instance of the VerifyDODDOB class.
public ValidateDODDOB() : base(DefaultErrorMessage)
{
this.Regex = new Regex(this.Pattern);
}
// Determines whether the specified value of the object is valid.
// true if the specified value is valid; otherwise, false.
public override bool IsValid(object value)
{
// convert the value to a string
var stringValue = Convert.ToString(value);
var m = Regex.Match(stringValue);
return m.Success;
}
}
The above works in terms of validating, and stopping the Create/Edit Actions from proceeding through to the database. But no error message is being displayed when the form is returned to the View!
UPDATE IN RESPONSE TO COMMENT 01:
Sorry Olive, I should have posted the view code too. Here it is:
<div class="inputField">
#Html.LabelFor(x => x.DOB, "Date of Birth")
#Html.TextBoxFor(model => model.DOB)
#Html.ValidationMessageFor(model => model.DOB)
</div>
So Yes, I have told it to to show the Validation message as well. And as far as AJAX, it is not via AJAX. Like you said, it is after a full POST Request.
Do you mean you want the message to show in a ValidationSummary control?
If so, try setting the "excludePropertyErrors" of the ValidationSummary HtmlHelper to false:
#Html.ValidationSummary(false)
This will tell the summary control to summary display all errors (having it set to 'true', the default value, will tell the control to display model-level errors only).
I think what you are probably wanting to do is use this method
protected override ValidationResult IsValid(object value, ValidationContext context)
{
// convert the value to a string
var stringValue = Convert.ToString(value);
var m = Regex.Match(stringValue);
if(!m.Success)
{
return new ValidationResult(DefaultErrorMessage);
}
return null;
}
Then in your view make sure you have the ValidationMessageFor, and in the Controller make sure you check ModelState.IsValid and return the original View if it is not valid. That ought to do it.
Related
I have developed a custom validator Attribute class for checking Integer values in my model classes. But the problem is this class is not working. I have debugged my code but the breakpoint is not hit during debugging the code. Here is my code:
public class ValidateIntegerValueAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value != null)
{
int output;
var isInteger = int.TryParse(value.ToString(), out output);
if (!isInteger)
{
return new ValidationResult("Must be a Integer number");
}
}
return ValidationResult.Success;
}
}
I have also an Filter class for model validation globally in application request pipeline. Here is my code:
public class MyModelValidatorFilter: IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
if (context.ModelState.IsValid)
return;
var errors = new Dictionary<string, string[]>();
foreach (var err in actionContext.ModelState)
{
var itemErrors = new List<string>();
foreach (var error in err.Value.Errors){
itemErrors.Add(error.Exception.Message);
}
errors.Add(err.Key, itemErrors.ToArray());
}
actionContext.Result = new OkObjectResult(new MyResponse
{
Errors = errors
});
}
}
The model class with validation is below:
public class MyModelClass
{
[ValidateIntegerValue(ErrorMessage = "{0} must be a Integer Value")]
[Required(ErrorMessage = "{0} is required")]
public int Level { get; set; }
}
Can anyone please let me know why the attribute integer validation class is not working.
Model validation comes into play after the model is deserialized from the request. If the model contains integer field Level and you send value that could not be deserialized as integer (e.g. "abc"), then model will not be even deserialized. As result, validation attribute will also not be called - there is just no model for validation.
Taking this, there is no much sense in implementing such ValidateIntegerValueAttribute. Such validation is already performed by deserializer, JSON.Net in this case. You could verify this by checking model state in controller action. ModelState.IsValid will be set to false and ModelState errors bag will contain following error:
Newtonsoft.Json.JsonReaderException: Could not convert string to
integer: abc. Path 'Level', ...
One more thing to add: for correct work of Required validation attribute, you should make the underlying property nullable. Without this, the property will be left at its default value (0) after model deserializer. Model validation has no ability to distinguish between missed value and value equal to default one. So for correct work of Required attribute make the property nullable:
public class MyModelClass
{
[Required(ErrorMessage = "{0} is required")]
public int? Level { get; set; }
}
The model below used in two ways:
public class SimpleModel
{
public DateTime? Date { get; set; }
// Some other properties
public SimpleModel()
{
Date = DateTime.Now;
}
}
When model is used in the form, generated URL have empty parameter Date (/Controller/Action?Date=&SomeOtherParams=123) and Date property in model is null (after submitting form with empty date).
...
#Html.TextBoxFor(model => model.Date)
...
Also this model used as third parameter in UrlHelper.Action():
#Url.Action("Action", "Controller", Model) // Model is SimpleModel
In this case if Date is null, generated URL does not contains parameter Date (/Controller/Action?SomeOtherParams=123). And if I following this URL, property Date is DateTime.Now, not null as expected.
How to force passing empty properties to URL?
UPD. Action code
public ActionResult MyAction( SimpleModel model = null )
{
if ( model.Date == null )
{
// show all
}
else
{
// show by date
}
}
Actually, instead of TextBoxFor used DropDownListFor.
#Html.DropDownListFor( model => model.Date, Model.Dates)
User can choose Date from DropDown or live it empty if he want to see all entities.
If user submiting form with empty Date, he following URL /Controller/Action?Date= and getting all entities (Date property was initialized with default value in constructor and then overriten with null).
If user following by generated url via #Url.Action from other page (not submitting form), he getting only todays entities, because URL not contains Date parameter (/Controller/Action). In this case Date property initializing in constructor and this is all.
Problem is model in MyAction never equals null and I cant recognize when user selects empty Date and when he just visit page without parameters.
If you want the Date to be null, then remove the line that assigns DateTime.Now in the empty constructor.
public SimpleModel()
{
//This line is assigning the value to Date whenever a new SimpleModel() is created. Comment it out.
// Date = DateTime.Now;
}
And if I following this URL, property Date is DateTime.Now, not null as expected.
The problem is that the MVC engine is trying to construct a Model object based on the constructor you defined, which sets the Date property to DateTime.Now. It's advised that you do not define a constructor with empty parameters as it will be used by the MVC engine to create the model on POST. As it stands, this is expected behavior.
If you need the functionality, then define another constructor:
public SimpleModel() // Left for MVC
{
}
public SimpleModel(DateTime date) // Use as: new SimpleModel(DateTime.Now) in your action
{
Date = date;
}
I have two fields in my model
CreateDateTo
CreateDateFrom
which renders like this
<b>Start Date</b> #Html.EditorFor(model => model.AdvanceSearch.CreateDatefrom, new { #class = "picker startDate" })
<b>End Date</b> #Html.EditorFor(model => model.AdvanceSearch.CreateDateto, new { #class = "picker endDate" })
I have a validation scenario that enddate should not be greater then start date, currently I am validating it by jquery
$.validator.addMethod("endDate", function (value, element) {
var startDate = $('.startDate').val();
return Date.parse(startDate) <= Date.parse(value);
}, "* End date must be Equal/After start date");
I want to know that is there any way in MVC3 model validation to do this?
I would say that you should not rely solely on Javascript unless you are in control of your client's browser in some sort of intranet application. If the app is public facing - make sure you have both client and server side validation.
Also, a cleaner way of implementing the server side validation inside your model object can be done with a custom validation attribute shown below. Your validation then becomes centralised and you do not have have to explicitly compare the dates in your controller.
public class MustBeGreaterThanAttribute : ValidationAttribute
{
private readonly string _otherProperty;
public MustBeGreaterThanAttribute(string otherProperty, string errorMessage) : base(errorMessage)
{
_otherProperty = otherProperty;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var otherProperty = validationContext.ObjectInstance.GetType().GetProperty(_otherProperty);
var otherValue = otherProperty.GetValue(validationContext.ObjectInstance, null);
var thisDateValue = Convert.ToDateTime(value);
var otherDateValue = Convert.ToDateTime(otherValue);
if (thisDateValue > otherDateValue)
{
return ValidationResult.Success;
}
return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
}
}
This can then be applied to your model like so:
public class MyViewModel
{
[MustBeGreaterThan("End", "Start date must be greater than End date")]
public DateTime Start { get; set; }
public DateTime End { get; set; }
// more properties...
}
You need to create a custom validation against the model. You could put this in the controller after if(Model.IsValid)
if(Model.End<Model.StartDate)
....
But I would stick to javascript.
It works on the clientside and does not hit the server.
Unless you just need the added assurance.
This is my first question here on stack overflow.
i need help on a problem i encountered during an ASP.NET MVC2 project i am currently working on.
I should note that I'm relatively new to MVC design, so pls bear my ignorance.
Here goes :
I have a regular form on which various details about a person are shown. One of them is "Date of Birth". My view is like this
<div class="form-items">
<%: Html.Label("DateOfBirth", "Date of Birth:") %>
<%: Html.EditorFor(m => m.DateOfBirth) %>
<%: Html.ValidationMessageFor(m => m.DateOfBirth) %>
</div>
I'm using an editor template i found, to show only the date correctly :
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<System.DateTime?>"%>
<%= Html.TextBox("", (Model.HasValue ? Model.Value.ToShortDateString() : string.Empty))%>
I used LinqToSql designer to create my model from an sql database. In order to do some validation i made a partial class Person to extend the one created by the designer (under the same namespace) :
[MetadataType(typeof(IPerson))]
public partial class Person : IPerson
{ //To create buddy class }
public interface IPerson
{
[Required(ErrorMessage="Please enter a name")]
string Name { get; set; }
[Required(ErrorMessage="Please enter a surname")]
string Surname { get; set; }
[Birthday]
DateTime? DateOfBirth { get; set; }
[Email(ErrorMessage="Please enter a valid email")]
string Email { get; set; }
}
I want to make sure that a correct date is entered. So i created a custom DataAnnotation attribute in order to validate the date :
public class BirthdayAttribute : ValidationAttribute
{
private const string _errorMessage = "Please enter a valid date";
public BirthdayAttribute() : base(_errorMessage) { }
public override bool IsValid(object value)
{
if (value == null)
{
return true;
}
DateTime temp;
bool result = DateTime.TryParse(value.ToString(), out temp);
return result;
}
}
Well, my problem is this. Once i enter an incorrect date in the DateOfBirth field then no custom message is displayed even if use the attribute like [Birthday(ErrorMessage=".....")]. The message displayed is the one returned from the db ie "The value '32/4/1967' is not valid for DateOfBirth.". I tried to enter some break points around the code, and found out that the "value" in attribute is always null when the date is incorrect, but always gets a value if the date is in correct format. The same ( value == null) is passed also in the code generated by the designer.
This thing is driving me nuts. Please can anyone help me deal with this?
Also if someone can tell me where exactly is the point of entry from the view to the database. Is it related to the model binder? because i wanted to check exactly what value is passed once i press the "submit" button.
Thank you.
Generally speaking all validation stuff is work after binder binded values. As you can understand it's not possible to bind dateTime value from string like "asdvfvk". So, when binder encounters with such an error it adds it to the ModelState (take a look at ModelState["DateOfBirth"].Errors[0].ErrorMessage), and binds default value. Default value for DateTime? is null, so that's why you always get it in IsValid method. It's normal.
So as you can see validation for date has sence if you whant to check for example if it's bigger then some other date. If input string is incorrect no further verification have sence.
What can you do?
First straightforward way - you can correct your action like this
[HttpPost]
public ActionResult About(Person person, string dateOfBirth) {
var birthdayAttribute = new BirthdayAttribute();
if( !birthdayAttribute.IsValid(dateOfBirth)) {
ModelState["DateOfBirth"].Errors.Clear();
ModelState.AddModelError("DateOfBirth", birthdayAttribute.ErrorMessage);
}
.......
}
As you can see there is string dateOfBirth, so binder have no problems with binding string value. But this will not make your users happy.
The better way - ensure that string will be in correct format with client Javascript. Actualy people use date picker controls for dates and feel themselves good.
In addition take a look here http://forums.asp.net/t/1512140.aspx
Especialy Brad Wilson's answer.
I am using server side validation like
public IEnumerable<RuleViolation> GetRuleViolations()
{
if (String.IsNullOrEmpty(Name))
yield return new RuleViolation("Name is Required", "Name");
if (Price == 0)
yield return new RuleViolation("Price is Required", "Price");
yield break;
}
When I left Price as blank, Then It takes 0 as a value.
So I check it with 0.
In my Database Price cannot be null; and I am using LINQ-to-SQL class.
Now my problem is when I left Price blank it gives me two messages.e.g.
A value is required.
Price is Required.
So How do I put custom validation without showing first error message?
Relpy to comment
I am reffering book code of Professional Asp.net MVC 1.0 here.
HTML pages of Book are Here.
usefull page.
public class RuleViolation
{
public string ErrorMessage { get; private set; }
public string PropertyName { get; private set; }
public RuleViolation(string errorMessage)
{
ErrorMessage = errorMessage;
}
public RuleViolation(string errorMessage, string propertyName)
{
ErrorMessage= errorMessage;
PropertyName = propertyName;
}
}
I think you get the first message "A value is required" automatically from the framework because your Price property is a value type, which can never be null.
So when you post a blank field, the framework will usually try to assign null to this property, which is not possible in this case.
If you change the type to nullable:
public double? Price { get; set; }
That particular message should disappear. Then you could change your validation to:
if (Price == null)
yield return new RuleViolation("Price is required", "Price");
The fact that the database field does not allow nulls should not interfere with your viewmodels.
To make what Thomas Eyde wrote above work (without messing with the code), you can...
Open the corresponding .dbml file
Click the "Price" property
In the Visual Studio Properties window, change the Nullable value from False to True
Save the file!
You can now go into your class and add the if statement and VS should not complain.
That's because the Default Model Binder adds that error. You can write your own model binder for that particular object and work directly with the form collection to get more control over validation.