Custom ValidationAttribute test against whole model - asp.net-mvc

I know this is probably not possible but let's say I have a model with two properties.
I write a ValidationAttribute for one of the properties. Can that VA look at the other property and make a decision?
So;
public class QuickQuote
{
public String state { get; set; }
[MyRequiredValidator(ErrorMessage = "Error msg")]
public String familyType { get; set; }
So in the above example, can the validator test to see what's in the "state" property and take that into consideration when validating "familyType"?
I know I can probably save the object to the session but would like to avoid any saving of state if possible.

Your custom validation could be applied to the class directly, take a look at PropertiesMustMatch attribute in the AccountModels class that is created by default as a part of the MVC project template in VS2008.

Another way to achieve this kind of validation is to have your model implement IDataErrorInfo. That way you can do whole viewmodel validation.
This page has some information about iplementing the IDataErrorInfo Interface, about 2/3 of the way down under the heading "mplementing the IDataErrorInfo Interface"

Use ValidationContext to get your model:
public class MyRequiredValidator: RequiredAttribute
{
public override bool RequiresValidationContext
{
get {return true;} //it needs another propertie in model
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
QuickQuote model = (QuickQuote)validationContext.ObjectInstance;
if (model.state == "single")
return null;
else
return base.IsValid(value, validationContext);//familyType is require for married
}
}

Related

ASP.NET MVC: Implement client side validation with attribute without IClientValidatable

How can I create a custom validation attribute with client side validation without implementing IClientValidatable?
How does System.ComponentModel.DataAnnotations.RequiredAttribute client side validate?
The reason to do this is because I'm using objects from classes in another project as models in my views and I don't want to add the System.Web.MVC reference to that project.
EDIT to add more information:
I know that IClientValidatable is used to add custom attributes to
the HTML to be used later by the unobtrusive validation.
I know I'll need to add the javascript code to made the validation in
the client.
What I don't know is how to use the information from the custom validation attribute to add the necessary attributes to the HTML for unobtrusive validation to work.
This is my custom validation attribute:
public class RequiredGuidAttribute : ValidationAttribute
{
public override bool IsValid(object value)
{
Guid? guidValue = value as Guid?;
if (guidValue == null)
return false;
return guidValue != Guid.Empty;
}
}
This is my property with the attribute applied:
[RequiredGuid(ErrorMessageResourceType = typeof(ClientOrderResources), ErrorMessageResourceName = "RequiredShippingMethod")]
public Guid ShippingMethodId
{
get { return GetProperty(ShippingMethodIdProperty); }
set { SetProperty(ShippingMethodIdProperty, value); }
}
And finally I'm rendering a hidden input for that property in the view using Html.HiddenFor.
Now, how can I get the error message from the attribute to apply it to the HTML? Should I do it my self using Reflection or there is a better way?
And then how can I tell Html.HiddenFor to use that information to add the necessary attributes to the HTML?
We had a similar problem. We have a model we use for our account creation that uses IClientValidatable on its custom attributes. However, we created a batch account creation process that sits outside of the website that we weren't able to reference System.Web.Mvc in. Because of this, when we called Validator.TryValidateObject, any custom validator that inherited from IClientValidatable was simply skipped. Here's what we were working with that was failing to validate outside of our website:
public class AgeValidatorAttribute : ValidationAttribute, IClientValidatable
{
public int AgeMin { get; set; }
public int AgeMax { get; set; }
public override bool IsValid(object value)
{
//run validation
}
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule
{
ErrorMessage = ErrorMessageString,
ValidationType = "agevalidator"
};
rule.ValidationParameters["agemin"] = AgeMin;
rule.ValidationParameters["agemax"] = AgeMax;
yield return rule;
}
Removing System.Web.Mvc required us to also remove GetClientValidationRules and the IClientValidatable reference. In order to do this and still have client side validation, we had to create a new class:
public class AgeValidatorClientValidator : DataAnnotationsModelValidator<AgeValidatorAttribute>
{
private readonly string _errorMessage;
private readonly string _validationType;
public AgeValidatorClientValidator(ModelMetadata metadata, ControllerContext context, AgeValidatorAttribute attribute)
: base(metadata, context, attribute)
{
this._errorMessage = attribute.FormatErrorMessage(metadata.DisplayName);
this._validationType = "agevalidator";
}
public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
{
var rule = new ModelClientValidationRule
{
ErrorMessage = this._errorMessage,
ValidationType = this._validationType
};
rule.ValidationParameters["agemin"] = base.Attribute.AgeMin;
rule.ValidationParameters["agemax"] = base.Attribute.AgeMax;
yield return rule;
}
}
As you can see, it does essentially the same thing as it used to, it's just done using the DataAnnatotationsModelValidator rather than IClientValidatable. There's one more step we need to do to actually attach the DataAnnotationsModelValidator to the atttribute, and that's done in the Global.asax.cs Application_Start method
DataAnnotationsModelValidatorProvider.RegisterAdapter(
typeof(AgeValidatorAttribute), typeof(AgeValidatorClientValidator));
Now you can use this just as you would use a normal attribute:
[AgeValidator(AgeMax = 110, AgeMin = 18, ErrorMessage = "The member must be between 18 and 110 years old")]
public string DateOfBirth { get; set; }
I know this question is a year old, but I spent all day yesterday and half of today trying to figure this issue out. So I hope this helps somebody who runs into the same problem if OP hasn't figured the answer out yet.
Please note, I did not include any javascript in this writeup as it required no changes from the standard implementation of custom validation rules using jQuery.validate.
You can't have custom validation on the client unless you implement IClientValidatable. And for that you also need to add client script as well.
http://msdn.microsoft.com/en-us/vs2010trainingcourse_aspnetmvccustomvalidation_topic3.aspx
It is possible, i found this article on how to do it:
http://xhalent.wordpress.com/2011/05/12/custom-unobstrusive-jquery-validation-in-asp-net-mvc-3-using-dataannotationsmodelvalidatorprovider/
basically you have to create a DataAnnotationsModelValidator on your client an register it in Application_Start().
And don't forget that you still have to write the Javascript for client side validation.

Best way of adding "different than" zero validation attribute

What is the best way of adding "Different than zero" validation attribute in ASP.NET MVC 3/4?
I would assume you would want to add a custom validation attribute with the error message "Different than zero" (not sure of the correct English usage here). Say you have a model with a property that requires the validation:
public class YourModel
{
[Required]
[CheckZero] //custom attribute
public int PropertyName { get; set; }
...
}
Create a class CheckZeroAttribute that derives from ValidationAttribute and override one of the IsValid methods provided by the base class. Overriding the IsValid version taking a ValidationContext parameter provides more information to use inside the IsValid method (the ValidationContext parameter will give you access to the model type, model object instance, and friendly display name of the property you are validating, among other pieces of information).
public class CheckZeroAttribute : ValidationAttribute
{
protected override ValidationResult IsValid (object value, ValidationContext validationContext)
{
//the error message
string sErrorMessage = "Different from zero";
//implement appropriate validation logic here
...
if (...) { return new ValidationResult(sErrorMessage); }
return ValidationResult.Success;
}
}

ASP.NET MVC (4) - Prevent calling IValidatableObject.Validate for properties

How do I prevent calling IValidatableObject.Validate for properties and call it for the top level model only?
public abstract class Foo, IValidatableObject
{
public virtual Foo Related { get; set; }
public virtual IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
// This is first called for the 'Related' property
// and then for the model itself
// I want this to be called for the top level model only
}
}
I am not sure if there is a more elegant way, but what about creating a new class overriding the Validate method of Foo?
Something like:
public class FooNoValidation : Foo
{
public override IEnumerable Validate(ValidationContext validationContext)
{
yield break;
}
}
Of course this would require the Validate method on Foo to be virtual and you to refer to FooNoValidation on the Foo parent class, but might work.
I know its a bit of a hack, but if you just need to get this to work this should make that happen.

ASP.NET MVC ModelBinding - set custom value

I have a view model
public class ViewModel
{
public string Text { get; set; }
public string Name { get; set; }
}
The submited form provides only the Text value. I'd like to set the the Name property in my custom model binder.
So I derived my custom model binder from the DefaultModelBinder class and overrided the BindModel method.
The problem is that the BindModel method is called only for the incomning properties.
My question is how can I set the Name value in my cystom model binder ?
If you do not have an incoming value for Name, then you are not doing (custom) model binding. Instead, you want to supply some data in your model object before the action executes, right ? If so, use ActionFilter for it, override OnActionExecuting() and supply the data you need into action parameters.
public class SupplyNameAttribute : FilterAttribute, IActionFilter
{
public void OnActionExecuting(ActionExecutingContext filterContext)
{
if (filterContext.ActionParameters != null)
{
foreach (KeyValuePair<string, object> parameter in filterContext.ActionParameters)
{
if (parameter.Key == "Name") parameter.Value == "Hey";
}
}
}
}
EDIT :
You can also use custom ValueProvider for default model binding, see
http://mgolchin.net/posts/19/dive-deep-into-mvc-ivalueprovider

Model Validation with dictionary

Say I have a model like so:
public class MyViewModel {
//some properties
public string MyString {get;set;}
public Dictionary<string,string> CustomProperties {get;set;}
}
And I am presenting the dictionary property like this:
<%= Html.EditorFor(m => m.CustomProperties["someproperty"]) %>
All is working well, however I have implemented a custom validator to validate the properties of this dictionary, but when returning a ModelValidationResult I can not get the member name referenced correctly (which chould be CustomProperties[someproperty] I believe). All the items in the list which are properties are bound correctly to their errors (I want the error class in the text box so I can highlight it).
Here is my code for the custom validator so far
public class CustomValidator : ModelValidator
{
public Custom(ModelMetadata metadata, ControllerContext controllerContext) : base(metadata, controllerContext)
{
}
public override IEnumerable<ModelValidationResult> Validate(object container)
{
if (Metadata.PropertyName.Equals("mystring", StringComparison.OrdinalIgnoreCase))
{
yield return new ModelValidationResult() {Message = "normal property validator works!!"};
}
else if (Metadata.PropertyName.Equals("customproperties", StringComparison.OrdinalIgnoreCase))
{
yield return new ModelValidationResult() { MemberName = "CustomProperties[someproperty]", Message = "nope!" };
}
}
}
It appears like something is filling in the MemberName property further up, and ignoring what I put in there
Cheers,
Amar
It appears to me that you are making validation more difficult than it needs to be. Have you taken a look at DataAnnotations which are built into the framework? Scott Gu's blog talks about this. It's a really nice (and easy) way to do validation of models.

Resources