I'm trying to create a MVC model validation attribute that fills in the following situation:
I have created several models (Birthday and PhoneNumber being excellant examples) that are submitted over multiple input fields on the view (month, day, year; areacode, exchange, suffix).
public class PhoneNumber
{
[RegularExpression("^([2-9][0-8][0-9])$", ErrorMessage = "Invalid Area Code")]
public virtual string AreaCode { get; set; }
[RegularExpression("^([2-9][0-9][0-9])$", ErrorMessage = "Invalid Phone Exchange")]
public virtual string Exchange { get; set; }
[RegularExpression("^([0-9][0-9][0-9][0-9])$", ErrorMessage = "Invalid Phone Suffix")]
public virtual string Suffix { get; set; }
}
I often nest these models inside other models (Person has a PhoneNumber and a Birthday, for example). Sometimes, in my views, a PhoneNumber is required, and sometimes it isn't.
I can handle these situations on the server side by using implementing the class as an IValidatableObject, but I run into trouble if I want to do client side validation, or even just do server side validation via attributes.
I imagine I will have to build my own custom validation attribute but I'm not even sure where to start in accessing object and attribute information on multiple levels. Has anyone encountered anything like this? Any good ideas for pointing me in the right direction?
Thanks in advance!
--------Update------
By using IClientValidatable, and GetClientValidationRules, I have access to the ModelMetadata can get the type of the container. The problem is that the container is birthday or phone number, not the type of the top level model, which is what is most important here. The ModelMetadata hasn't populated the Model property yet because one may not exist yet. What I'm really trying to do is get the type of the top level model. Anyone have any insight?
Depending on how you are writing your view, I think you may want to look at an extension called BeginCollectionItem http://nuget.org/packages/BeginCollectionItem
Validation gets a little messed up if you are doing
#Html.TextBoxFor( m => number.AreaCode )
instead of
#Html.TextBoxFor( m => m.AreaCode )
Related
After researching various methods to implement custom form validation rules in MVC I have found, what I originally considered to be a straightforward bit of validation, to be rather more complex that I anticipated.
I have 2 text inputs, one or the other (or both) need to be populated. If both are NullOrEmpty we need to throw an error.
I have found DataAnnotations to have it's limitations when attempting to validate on multiple fields, giving me highlighting on both inputs and throwing a single error. Is this some beginners naivity?
I also played around with FluentValidation and was unable to get the results I was after.
Currently I am throwing the error in the Controller using:
ModelState.AddModelError("PropertyName", "You need to enter a Property Number or a Property Name")
ModelState.AddModelError("PropertyNumber", String.Empty)
This gives me highlighting on both fields and server-side validation. I am now finding it difficult binding custom client-side validation without using DataAnnotations.
Does anyone have any advice on how to do this properly? Any help or suggestions would be greatly appreciated. I need validation on the server/client, on both fields, with highlighting and a single error message.
Thanks in advance.
[Fool proof][1] validation library covers almost all kind of validation scenarios.
[RequiredIf]
[RequiredIfNot]
[RequiredIfTrue]
[RequiredIfFalse]
[RequiredIfEmpty]
[RequiredIfNotEmpty]
[RequiredIfRegExMatch]
[RequiredIfNotRegExMatch]
Applied to a Model:
public class CreateUserViewModel
{
[Required]
public string Username { get; set; }
[Required]
public string Department { get; set; }
[RequiredIfEmpty(ErrorMessage="error message"]
public string Role { get; set; }
}
I'm using ASP.NET MVC 3 with Fluent Validation. I'd like all my error messages to be worded and formatted the same, whether they are validation error messages or model binding error messages.
Let's say I have the following view-model:
[Validator(typeof(PersonValidator))]
public class Person
{
[ScaffoldColumn(false)] public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
}
To validate this using Fluent Validation, I might use something like this:
public class EditorValidator : AbstractValidator<EditorModel>
{
public EditorValidator()
{
RuleFor(model => model.Month.Value).InclusiveBetween(0, 120)
}
}
If the user enters "abc" for Age, this causes a model binding error, not a validation error. This is because "abc" is not an int. The system never even gets to the point of asking whether "abc" is between 0 and 120, because "abc" cannot be stored in Age.
This is fine and makes sense. The problem is that the resulting error message is:
The field Age must be a number.
I'd like the message to be formatted and worded like other error message produced by Fluent Validation. In this case, I would like:
'Age' must be a number.
I realize this is only a subtle difference, but I'd like to have control of model binding error messages.
How can I customize the model binding error messages to match the error messages used by Fluent Validation?
I'm not sure if there's another way to do this, but I use Data Annotations Extensions, also available via NuGet (Install-Package DataAnnotationsExtensions.MVC3) for this exact type of thing. This package will give you an IntegerAttribute, and from there you can specify an error message like so:
[Integer(ErrorMessage = "'Age' must be a number.")]
public int Age { get; set; }
Take a look at my answer here:
How to change 'data-val-number' message validation in MVC while it is generated by #Html helper
Actually, it's a very common question that you asked, so you should've googled the stackoverflow prior to posting.
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;
I would like to use DataAnnotations for basic client and server-side validation of my MVC ViewModels. My ViewModel looks like this:
public class MyViewModel
{
public Client Client1 { get; set; }
public Client Client2 { get; set; }
public Product Product { get; set; }
}
So I would like to check that both client objects have a name and telephone number, the product object has a valid numeric price, etc.
The problem I have is that both Client and Product are proxy types generated by Visual Studio from a web service, so I can't directly add the annotation attributes to their required properties.
I've read about using the MetadataType attribute to specify the meta data in an alternative class (with duplicate properties), but in this case I can't even add that attribute to the Client and Product classes.
Or can I? In the Web References folder where my VS solution is saved, there is a folder for the web service namespace containing a file called Reference.cs, which contains the VS generated code for the proxy types.
If I add the metadata to the classes in here, will this work—or is messing about with the generated code a really bad idea? Or is there just a simpler, cleaner way to do this?
After a bit of hunting I found that this is actually remarkably simple—it was just a case of my not knowing exactly what to search for!
You don't actually need to add the MetadataType attribute to the original class definition: you can add it to an empty partial class of the same type (make sure your partial class is in the same namespace as the original type).
Then you just create a "buddy" class containing your validation rules as you would normally:
using System.ComponentModel.DataAnnotations;
namespace WebServiceNamespace
{
[MetadataType(typeof(ClientMetaData))]
public partial class Client
{
}
public class ClientMetaData
{
[Required(ErrorMessage = "Please enter a name")]
public string Name { get; set; }
[Required(ErrorMessage="Please enter a telephone Number")]
public string Telephone { get; set; }
}
}
This works perfectly with the standard Model Binding and requires no access to the original code for the type, so you can easily set up validation rules with DataAnnotations, even for types which aren't part of your code base.
Modifying the generated code would work, so long as you don't regenerate it and write over your modifications. Other than the chance of losing your work if someone generates the reference, there isn't a reason you can't add the metadata references to the proxy classes.
The other alternative is using custom validation, or create a model that you then map the fields to the proxy objects. Creating a model that isn't based on the Client object would be your safest method.
I think it would be cleaner to create a model and then map the fields using AutoMapper and/or Model Generator Helper ( http://modelhelper.codeplex.com/ ).
I have certain panels on my page that are hidden under certain circumstances.
For instance I might have a 'billing address' and 'shipping address' and I dont want to validate 'shipping address' if a 'ShippingSameAsBilling' checkbox is checked.
I am trying to use the new DataAnnotations capabilities of ASP.NET MVC 2 (preview 1) to achieve this.
I need to prevent validation of the 'shipping address' when it is not displayed and need to find the way way to achieve this. I am talking mainly server side as opposed to by using jquery.
How can I achieve this? I have had several ideas, related to custom model binding but my current best solution is below. Any feedback on this method?
For the CheckoutModel I am using this approach (most fields hidden):
[ModelBinder(typeof(CheckoutModelBinder))]
public class CheckoutModel : ShoppingCartModel
{
public Address BillingAddress { get; set; }
public Address ShippingAddress { get; set; }
public bool ShipToBillingAddress { get; set; }
}
public class Address
{
[Required(ErrorMessage = "Email is required")]
public string Email { get; set; }
[Required(ErrorMessage = "First name is required")]
public string FirstName { get; set; }
[Required()]
public string LastName { get; set; }
[Required()]
public string Address1 { get; set; }
}
The custom model binder removes all ModelState errors for fields beginning with 'ShippingAddress' if it finds any. Then 'TryUpdateModel()' will return true.
public class CheckoutModelBinder : DefaultModelBinder
{
protected override void OnModelUpdated(ControllerContext controllerContext,
ModelBindingContext bindingContext) {
base.OnModelUpdated(controllerContext, bindingContext);
var model = (CheckoutModel)bindingContext.Model;
// if user specified Shipping and Billing are the same then
// remove all ModelState errors for ShippingAddress
if (model.ShipToBillingAddress)
{
var keys = bindingContext.ModelState.Where(x => x.Key.StartsWith("ShippingAddress")).Select(x => x.Key).ToList();
foreach (var key in keys)
{
bindingContext.ModelState.Remove(key);
}
}
}
}
Any better solutions?
http://bradwilson.typepad.com/blog/2009/04/dataannotations-and-aspnet-mvc.html
I can see your predicament. I'm looking for other validation solutions also with regard to complex validation rules that might apply to more than one property on a given model object or even many properties from different model objects in a object graph (if your unlucky enough to be validating linked objects like this).
The limitation of the IDataErrorInfo interface is that a model object satisfies the valid state simply when none of the properties have errors. This is to say that a valid object is one where all of it's properties are also valid. However, i may have a situation where if property A, B and C are valid - then the whole object is valid.. but also if property A is not valid but B and C are, then the object satisfies validity. I simply have no way of describing this condition/rule with the IDataErrorInfo interface / DataAnnotations attributes.
So i found this delegate approach. Now many of the helpful advancements in MVC didn't exist at the time of writing this article but the core concept should help you. Rather than using attributes to define the validation conditions of an object we create delegate functions that validate more complex requirements and because they're delegated we can re-use them. Sure it's more work, but the use of delegates means that we should be able to write validation rule code once and store all the validation rules in the one place (maybe service layer) and (the kool bit) even use the MVC 2 DefaultModelBinder to invoke the validation automatically (without heaps of checking in our controller actions - like Scott's blog says we can do with DataAnnotations. Refer to the last paragraph before the 'Strongly Typed UI Helpers' heading)!
I'm sure you can beef the approach suggested in the above article up a little with anonymous delegates like Func<T> or Predicate<T> and writing custom code blocks for the validation rules will enable cross-property conditions (for example the condition you referred to where if your ShippingSameAsBilling property is true then you can ignore more rules for the shipping address, etc).
DataAnnotations serves to make simple validation rules on objects really easy with very little code. But as your requirements develop you will need to validate on more complex rules. The new virtual methods in the MVC2 model binder should continue to provide us with ways of integrating our future validation inventions into the MVC framework.
Make sure the fields you don't want validated are not posted to the action. We only validate the fields that were actually posted.
Edit: (by questioner)
This behavior has changed in MVC2 RC2 :
Default validation system validates
entire model The default validation
system in ASP.NET MVC 1.0 and in
previews of ASP.NET MVC 2 prior to RC
2 validated only model properties that
were posted to the server. In ASP.NET
MVC 2, the new behavior is that all
model properties are validated when
the model is validated, regardless of
whether a new value was posted.
Applications that depend on the
ASP.NET MVC 1.0 behavior may require
changes. For more information about
this change, see the entry Input
Validation vs. Model Validation in
ASP.NET MVC on Brad Wilson’s blog.
For the more complex cases I moved away from simple DataAnnotations to the following: Validation with visitors and extension methods.
If you want to make use of your DataAnnotations you would replace something like the following:
public IEnumerable<ErrorInfo> BrokenRules (Payment payment)
{
// snip...
if (string.IsNullOrEmpty (payment.CCName))
{
yield return new ErrorInfo ("CCName", "Credit card name is required");
}
}
with a method to validate a property by name via DataAnnotations (which I don't have atm).
I created a partial model binder that only validates the keys that were submitted. For security reasons (if I was going to take this a step farther) I'd create a data annotation attribute that marks which fields are allowed to be excluded from a model. Then, OnModelUpdated check field attributes to ensure there is no undesired underposting going on.
public class PartialModelBinder : DefaultModelBinder
{
protected override void OnModelUpdated(ControllerContext controllerContext,
ModelBindingContext bindingContext)
{
// default model binding to get errors
base.OnModelUpdated(controllerContext, bindingContext);
// remove errors from filds not posted
// TODO: include request files
var postedKeys = controllerContext.HttpContext.Request.Form.AllKeys;
var unpostedKeysWithErrors = bindingContext.ModelState
.Where(i => !postedKeys.Contains(i.Key))
.Select(i=> i.Key).ToList();
foreach (var key in unpostedKeysWithErrors)
{
bindingContext.ModelState.Remove(key);
}
}
}
This isn't related to DataAnnotations but have you looked at the Fluent Validation project? It gives you fine grain control over your validation and if you have object-to-object validation an aggregate object of the two objects will get you going.
Also it seems to have been build with MVC in mind but it also has its own "runtime" so that you can use it in other .NET applications as well which is another bonus in my book.