The question here is similar, but I don't have any domain object inheritance. My field and validation tags are in the following order, but the MustBe18 error and the Required error are the only ones that print. I have several other fields in this model with much more validation, but the order of ValidationAttribute's in the code doesn't seem to matter. jfar's answer in the linked post seems to suggest a helper could be built, but how? How can the order be controlled?
[Required(ErrorMessage = "This field is required")]
[DisplayName("Date of Birth")]
[MustBeValidDate(ErrorMessage = "Must be a valid date")]
[MustBe18(ErrorMessage = "You must be 18 years old")]
[MustNotBeOver100(ErrorMessage = "This caller is too old")]
public string dob { get; set; }
MustBe18 : ValidationAttribute (the overloaded IsValid method)
try
{
DateTime dob = new DateTime(DateTime.Now.AddYears(-18).Year, DateTime.Now.Month, DateTime.Now.Day);
return DateTime.Compare(DateTime.Parse(value.ToString()), dob) <= 0;
}
catch
{
return false;
}
The only way to specify the order is to create your own ModelValidatorProvider which can then order the attributes. This will probably be tricky because you'd also need to create overloads for each attribute that takes an Order parameter ( don't know if they already do ).
If all you mind is the order in which validation summaries appear all you'd need to do is loop through the ModelState entries and spit out the errors from there.
Related
In my Mvc5 test project I have a model with a property like the following:
[Required]
[DisplayName("Codigo Cliente")]
public int ClientCode{ get; set; }
the default error message when the user enteres a letter of special character in the editor is:
The field Codigo Cliente must be a number.
How can I modify this? in this case I need to change the language, but in case that I wanted to show a more specific error what can I do?
I have tried with the DataType attribute but the Enum does not have a value that applys for this case (numbers)
Use Range:
http://msdn.microsoft.com/en-us/library/system.componentmodel.dataannotations.rangeattribute.aspx
Or use IntegerOnly from Data Annotations Extensions
http://dataannotationsextensions.org/Integer/Create
The simplest way I found to solve this issue is use String with Range attribute in Data Annotation Model like specify below.
[Required]
[Range(0, int.MaxValue, ErrorMessage = "Codigo Cliente must be a positive or negative non-decimal number.")]
[DisplayName("Codigo Cliente")]
public string ClientCode { get; set; }
In Range attribute you can specify your custom Error Message.
For Interger use int.MaxValue , For double use double.MaxValue like so on.
I hope this will help you a lot.
If you want to specify a message you must use this
[Required(ErrorMessage = "your message")]
If you want to use a lang. based message is not that easy. You can use multiple resource file (for every language you need) and try a custom error binder that extends the DefaultModelBinder and make an override of the method BindModel(), there you can make your custom validation ad use your custom language message.
I have properties declared in my view model like:
[Required(ErrorMessage = "The Date field is required for Start.")]
[Display(Name = "Start")]
public DateTime DateStart { get; set; }
However, I am still getting a default The Start field is required error message. I assume this is because a non-nullable DateTime is implicitly required, and the Required attribute is ignored. Is there a way to customise my error message for these specific properties, besides making them nullable?
You right, your problem is that your property is not nullable. For not nullable properties attribute Required is meaningless. When there is no StartDate value, validation is not go to your Required attribute and fails on previous step. If you want to get your ErrorMessage you should
use:
[Required(ErrorMessage = "The Date field is required for Start.")]
[Display(Name = "Start")]
public DateTime? DateStart { get; set; }
You cannot customize ErrorMessage for nonullable types that get null on modelbinding, cause it is hardcoded deep in MVC framework.
I've started with refresh new test project in MVC 4 and create a test model
public class TestModel {
[Required(ErrorMessage = "The Date field is required for Start.")]
[Display(Name = "Start")]
public DateTime DateStart { get; set; }
}
Then in my model I just have this:
#using(Html.BeginForm()){
#Html.ValidationMessageFor(a => a.DateStart);
#Html.TextBoxFor(a => a.DateStart)
<input type="submit" value="add"/>
}
When I remove clean the text box and hit submit, I am getting the customized error message instead of the default.
The Date field is required for Start.
This make sense to me, imagine if this is a multilingual application, you will definitely need to customize the error message for that country. It doesn't take a rocket scientist to realise the need for customized message. And I would expect MVC team have that covered.
In our ASP.NET MVC 4 application, one of the models has a field of the DateTime type. When editing such model objects via a form, the value for the DateTime field has to be non-empty and on the format yyyy-MM-dd H:mm:ss (e.g., 2012-10-17 10:49:00). How do I ensure this field is correctly validated in the application? I've tried the following annotations:
[System.ComponentModel.DataAnnotations.Required]
[System.ComponentModel.DataAnnotations.DisplayFormat(DataFormatString="yyyy-MM-dd H:mm:ss",
ApplyFormatInEditMode=true)]
However, validation of form data doesn't require all components of the format to be present. For instance, the value '2012-10-17' is accepted (leaving out the 'H:mm:ss' part). It's just verified that the field contains a valid DateTime string.
How should I ensure that this DateTime field is indeed on my specified format (yyyy-MM-dd H:mm:ss)?
Alternative solution - view-only model class
Darin's solution is of course valid, but it's not the only one you can use. And it would require you to write more complex code than with this solution that I'm going to show you here.
So this is an alternative. I'd suggest that instead of creating a custom model binder you rather create a separate view model class that instead of taking DateTime takes a string where you can set as complex validation regular expression as you like. And then have a method on it that would translate it to your application/domain model class instance (and back).
// suppose this app model
public class User
{
[Required]
public string Name { get; set; }
[Required]
public DateTime DateOfBirth { get; set; }
}
public class ViewUser
{
[Required]
public string Name { get; set; }
[Required]
[RegularExpression("\d{4}-\d{2}-\d{2}(?:\s\d{1,2}:\d{2}:\d{2})?")]
public string DateOfBirth { get; set; }
public ViewUser(User user)
{
this.Name = user.Name;
this.DateOfBirth = user.DateOfBirth.ToString("yyyy-MM-dd H:mm:ss");
}
public User ToPoco()
{
return new User {
Name = this.Name,
DateOfBirth = DateTime.Parse(this.DateOfBirth, "yyyy-MM-dd H:mm:ss")
};
}
}
With a bit of tweaking you could inherit ViewUser from User class and use new keyword on DateOfBirth and use base property to store correct typed value. In that case you wouldn't need the ToPoco method.
Note: you will have to use DateTime.TryParseExact method to parse your dates because they may include time or they may not. I didn't include that in my code because it depends on the exact requirements of your date input.
You could write a custom model binder which will use the exact format you have specified in the DisplayFormat attribute. I have shown an example of how this could be achieved in this post.
Also don't be confused into thinking that the DisplayFormat attribute overrides the Required attribute. The DisplayFormat is only used for displaying the field in the input field. It is not a validation attribute. It has strictly nothing to do with validation and when the form is POSTed to the server it is never used.
I'm developing in mvc 3 and have a little question.
I want to change the default error message for invalid data type.
let say I've a model with the prop Price, and I want his error message for input "aaa" will be "The only value you can enter here is a number".
what is the easiest way of doing that?
(I want to do it for all of my models)
You could use a Regular Expression data annotation on your model property, e.g.:
[RegularExpression(#"^[0-9\.]*$", ErrorMessage="The only value you can enter here is a number")]
public double Price { get; set; }
You should approach validation from a white list point of view - i.e. what should be allowed through, as opposed to a black list, which would be what is invalid.
More information here:
http://www.asp.net/mvc/tutorials/mvc-music-store-part-6
Hope this helps!
Sam
http://haacked.com/archive/2011/03/19/fixing-binding-to-decimals.aspx
Assuming that you are working with entity framework or Linq to SQL and your class name is Product. here is the example for that. create a partial class like below;
[MetadataType(typeof(Product.MetaData))]
public partial class Product {
private class MetaData {
[Required(ErrorMessage = "The only value you can enter here is a number")]
public decimal Price { get; set; }
}
}
you should add the following using statement in order to use dataanotations for validation;
using System.ComponentModel.DataAnnotations;
Serious n00b warning here; please take mercy!
So I finished the Nerd Dinner MVC Tutorial and I'm now in the process of converting a VB.NET application to ASP.NET MVC using the Nerd Dinner program as a sort of rough template.
I am using the "IsValid / GetRuleViolations()" pattern to identify invalid user input or values that violate business rules. I am using LINQ to SQL and am taking advantage of the "OnValidate()" hook that allows me to run the validation and throw an application exception upon trying to save changes to the database via the CustomerRepository class.
Anyway, everything works well, except that by the time the form values reach my validation method invalid types have already been converted to a default or existing value. (I have a "StreetNumber" property that is an integer, though I imagine this would be a problem for DateTime or any other non-strings as well.)
Now, I am guessing that the UpdateModel() method throws an exception and then alters the value because the Html.ValidationMessage is displayed next to the StreetNumber field but my validation method never sees the original input. There are two problems with this:
While the Html.ValidationMessage does signal that something is wrong, there is no corresponding entry in the Html.ValidationSummary. If I could even get the exception message to show up there indicating an invalid cast or something that would be better than nothing.
My validation method which resides in my Customer partial class never sees the original user input so I do not know if the problem is a missing entry or an invalid type. I can't figure out how I can keep my validation logic nice and neat in one place and still get access to the form values.
I could of course write some logic in the View that processes the user input, however that seems like the exact opposite of what I should be doing with MVC.
Do I need a new validation pattern or is there some way to pass the original form values to my model class for processing?
CustomerController Code
// POST: /Customers/Edit/[id]
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(int id, FormCollection formValues)
{
Customer customer = customerRepository.GetCustomer(id);
try
{
UpdateModel(customer);
customerRepository.Save();
return RedirectToAction("Details", new { id = customer.AccountID });
}
catch
{
foreach (var issue in customer.GetRuleViolations())
ModelState.AddModelError(issue.PropertyName, issue.ErrorMessage);
}
return View(customer);
}
This would be a good starting point: Scott Gu - ASP.NET MVC 2: Model Validation
But I would suggest that it's bad practice to databind stuff to your domain objects.
My personal approach would be to use POCO presentation models sprinkled with DataAnnotations validation attributes to do validation (this makes it easy to hook up client side validation later on). You can also create you own ValidationAttributes to hook into the databinding validation.
If it's valid after databinding to your POCO object, pass it a service that does your more complex business validation. After that validation passes, pass it to another service (or the same service) that transfers the values to your domain object and then saves it.
I'm not familiar with the Linq to SQL GetRuleViolations pattern, so feel free to replace one of my steps with that pattern where it fits.
I'll try my best to explain it here.
POCO presentation model:
public class EditCustomerForm
{
[DisplayName("First name")]
[Required(ErrorMessage = "First name is required")]
[StringLength(60, ErrorMessage = "First name cannot exceed 60 characters.")]
public string FirstName { get; set; }
[DisplayName("Last name")]
[Required(ErrorMessage = "Last name is required")]
[StringLength(60, ErrorMessage = "Last name cannot exceed 60 characters.")]
public string LastName { get; set; }
[Required(ErrorMessage = "Email is required")]
[RegularExpression(#"^([a-zA-Z0-9_\-\.]+)#((\[[0-9]{1,3}" +
#"\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\" +
#".)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$",
ErrorMessage = "Email appears to be invalid.")]
public string Email { get; set; }
}
Controller logic
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(int id, EditCustomerForm editCustomerForm)
{
var editCustomerForm = CustomerService.GetEditCustomerForm(id);
return View(editCustomerForm);
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(int id, EditCustomerForm editCustomerForm)
{
try
{
if (Page.IsValid)
{
//Complex business validation that validation attributes can't handle
//If there is an error, I get this method to throw an exception that has
//the errors in it in the form of a IDictionary<string, string>
CustomerService.ValidateEditCustomerForm(editCustomerForm, id);
//If the above method hasn't thrown an exception, we can save it
//In this method you should map the editCustomerForm back to your Cusomter domain model
CustomerService.SaveCustomer(editCustomerForm, id)
//Now we can redirect
return RedirectToAction("Details", new { id = customer.AccountID });
}
//ServiceLayerException is a custom exception thrown by
//CustomerService.ValidateEditCusotmerForm or possibly .SaveCustomer
catch (ServiceLayerException ex)
{
foreach (var kvp in ex.Errors)
ModelState.AddModelError(kvp.Key, kvp.Value);
}
catch (Exception ex) //General catch
{
ModelState.AddModelError("*", "There was an error trying to save the customer, please try again.");
}
return View(editCustomerForm);
}
Clear as mud? If you need more clarification, just let me know :-)
Again, this is just my approach. You could possibly just follow the article by Scott Gu I linked to first and then go from there.
HTHs,
Charles