Custom Dataannotations, Client side Validation, ViewModels, & Model State - c#

This is an extension of an older question from this post:
Enforcing a model's boolean value to be true using data annotations
This gave me a lot of insight on how to get the client side custom validation working with jquery-unubtrusive-ajax.js.
Here is the answer code:
namespace Checked.Entitites
{
public class BooleanRequiredAttribute : ValidationAttribute, IClientValidatable
{
public override bool IsValid(object value)
{
return value != null && (bool)value == true;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
//return new ModelClientValidationRule[] { new ModelClientValidationRule() { ValidationType = "booleanrequired", ErrorMessage = this.ErrorMessage } };
yield return new ModelClientValidationRule()
{
ValidationType = "booleanrequired",
ErrorMessage = this.ErrorMessageString
};
}
}
}
When i use this custom attribute in my models it works fine.
ex:
[BooleanRequired(ErrorMessage = "Please accept the terms")]
public bool Terms { get; set; }
When i use textboxfor in my views the error messages populate in the html validation summary.
#Html.CheckBoxFor(model => model.Terms, new { #class = "legalbox", #placeholder = "Terms" })
I also had to add this line to my jquery.unubtrusive-ajax.js
$.validator.unobtrusive.adapters.addBool("BooleanRequired", "required");
The issue i am having with this fix arises when i reference this model in the controller and check the modelstate. The modelstate is always false no matter what i have as the value for this custom validated field. It looks like the override isvalid is working in the client side validation but not on the server side. I need it to work on both and have been scouring trying to find a solution that isn't "ditch dataannotations and use something else".
The specific line i am using in my controller that will never pass with this field is as follows:
if (ModelState.IsValid) {
//Logic here
}
The rest of the model using the standard data-annotations validate correctly, i just cannot get this custom one to work the same way as the built in attributes. Its possible that the right solution would prevent me from having to add that line to the unobtrusive ajax file.
Any help would be appreciated.
Thanks

Related

Shared validation error message for more than one field

In an ASP.NET MVC project I'm trying to validate several fields against a regular expression. However I would like to have only one validation message displayed if any of them fails (and highlight the ones failing).
I can make a custom validation for that, and annotating one of them with the id's of the rest kind of work although only highlights the one decorated with the attribute. But it looks to me as an overkill as I just want to reduce the message to one.
In the same form I will try to do the same for two checkboxes, both must be checked.
So as far as I understand if I use the summary to put a generic message won't be able to tell if it's failing for the checkboxes or for the fields.
Is there a simple way of achieving this?
You could write a custom validation that targets your entire view model class. I wrote the following when I wanted to check that at least one property was set. You can see that this targets the class itself and should therefore give you one message.
/// <summary>
/// A configurable class wide attribute that is used to determine if at least one property of a class has received a value.
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class SingleValueConfigurableAttribute : ValidationAttribute, IClientValidatable
{
public SingleValueConfigurableAttribute(string errorKey)
{ ErrorMessage = Properties.Settings.Default[errorKey].ToString(); }
public override bool IsValid(object value)
{
var typeInfo = value.GetType();
var propertyInfo = typeInfo.GetProperties();
return propertyInfo.Any(property => null != property.GetValue(value, null));
}
public override string FormatErrorMessage(string name)
{
return ErrorMessage;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
yield return new ModelClientValidationRule
{
ErrorMessage = String.IsNullOrEmpty(ErrorMessage) ? FormatErrorMessage(metadata.DisplayName) : ErrorMessage,
ValidationType = "enforcetrue"
};
}
}

How to manually validate a model with attributes?

I have a class called User and a property Name
public class User
{
[Required]
public string Name { get; set; }
}
And I want to validate it, and if there are any errors add to the controller's ModelState or instantiate another modelstate...
[HttpPost]
public ActionResult NewUser(UserViewModel userVM)
{
User u = new User();
u.Name = null;
/* something */
// assume userVM is valid
// I want the following to be false because `user.Name` is null
if (ModelState.IsValid)
{
TempData["NewUserCreated"] = "New user created sucessfully";
return RedirectToAction("Index");
}
return View();
}
The attributes works for UserViewModel, but I want to know how to validate a class without posting it to an action.
How can I accomplish that?
You can use Validator to accomplish this.
var context = new ValidationContext(u, serviceProvider: null, items: null);
var validationResults = new List<ValidationResult>();
bool isValid = Validator.TryValidateObject(u, context, validationResults, true);
I made an entry in the Stack Overflow Documentation explaining how to do this:
Validation Context
Any validation needs a context to give some information about what is being validated. This can include various information such as the object to be validated, some properties, the name to display in the error message, etc.
ValidationContext vc = new ValidationContext(objectToValidate); // The simplest form of validation context. It contains only a reference to the object being validated.
Once the context is created, there are multiple ways of doing validation.
Validate an Object and All of its Properties
ICollection<ValidationResult> results = new List<ValidationResult>(); // Will contain the results of the validation
bool isValid = Validator.TryValidateObject(objectToValidate, vc, results, true); // Validates the object and its properties using the previously created context.
// The variable isValid will be true if everything is valid
// The results variable contains the results of the validation
Validate a Property of an Object
ICollection<ValidationResult> results = new List<ValidationResult>(); // Will contain the results of the validation
bool isValid = Validator.TryValidatePropery(objectToValidate.PropertyToValidate, vc, results, true); // Validates the property using the previously created context.
// The variable isValid will be true if everything is valid
// The results variable contains the results of the validation
And More
To learn more about manual validation see:
ValidationContext Class Documentation
Validator Class Documentation
I wrote a wrapper to make this a bit less clunky to work with.
Usage:
var response = SimpleValidator.Validate(model);
var isValid = response.IsValid;
var messages = response.Results;
Or if you only care about checking validity, it's even tighter:
var isValid = SimpleValidator.IsModelValid(model);
Complete source:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace Ether.Validation
{
public static class SimpleValidator
{
/// <summary>
/// Validate the model and return a response, which includes any validation messages and an IsValid bit.
/// </summary>
public static ValidationResponse Validate(object model)
{
var results = new List<ValidationResult>();
var context = new ValidationContext(model);
var isValid = Validator.TryValidateObject(model, context, results, true);
return new ValidationResponse()
{
IsValid = isValid,
Results = results
};
}
/// <summary>
/// Validate the model and return a bit indicating whether the model is valid or not.
/// </summary>
public static bool IsModelValid(object model)
{
var response = Validate(model);
return response.IsValid;
}
}
public class ValidationResponse
{
public List<ValidationResult> Results { get; set; }
public bool IsValid { get; set; }
public ValidationResponse()
{
Results = new List<ValidationResult>();
IsValid = false;
}
}
}
Or at this gist: https://gist.github.com/kinetiq/faed1e3b2da4cca922896d1f7cdcc79b
Since the question is asking specifically about ASP.NET MVC, you can use the TryValidateObject inside your Controller action.
Your desired method overload is TryValidateModel(Object)
Validates the specified model instance.
Returns true if the model validation is successful; otherwise false.
Your modified source code
[HttpPost]
public ActionResult NewUser(UserViewModel userVM)
{
User u = new User();
u.Name = null;
if (this.TryValidateObject(u))
{
TempData["NewUserCreated"] = "New user created sucessfully";
return RedirectToAction("Index");
}
return View();
}
There is another approach to validation, which is more easy reusable - FluentValidation
This library allows to play with inheritance, different rule sets for one model and has many other cool features. Read about advantages here.
With this library definition of validation rules is separated from model and code will look next way:
public class UserValidator:AbstractValidator<User>
{
public UserValidator()
{
RuleFor(x => x.Name).NotEmpty();
}
}
Usage will look next way:
var validator = new UserValidator();
var validationResult = await validator.ValidateAsync(model);

Extending the MVC RequiredAttribute

I have an extended class of RequiredAttribute that doesn't send error messages back. If I check it in the debugger the text is there alright.
public class VierRequired : RequiredAttribute
{
public VierRequired(string controlName)
{
//...
}
public string VierErrorMessage
{
get { return ErrorMessage; }
set { ErrorMessage = value; }
}
// validate true if there is any data at all in the object
public override bool IsValid(object value)
{
if (value != null && !string.IsNullOrEmpty(value.ToString()))
return true;
return false; // base.IsValid(value);
}
}
I call it like this
[VierRequired("FirstName", VierErrorMessage = "Please enter your first name")]
public string FirstName { get; set; }
And the mvc-view
<%: Html.TextBoxFor(model => model.FirstName, new { #class = "formField textBox" })%>
<%: Html.ValidationMessageFor(model => model.FirstName)%>
It works if I use the normal Required annotation
[Required(ErrorMessage = "Please enter your name")]
public string FirstName { get; set; }
But the custom does not send any error message back
I also had a problem with client side validation when I created my own derivative of the RequiredAttribute. To fix it you need to register your data annotation like so:
DataAnnotationsModelValidatorProvider.RegisterAdapter(
typeof(VierRequired),
typeof(RequiredAttributeAdapter));
Simply call this in your Application_Start() method and client side validation should work as normal.
If your attribute is not working when you are POST-ing your form then this would indicate to me that there is something wrong with the logic in your attribute (check you IsValid method).
I am also not sure what you are trying to achieve with your derived data annotation; your logic looks like it is trying to do pretty much what the default attribute does anyway:
Taken from the MSDN documentation:
A validation exception is raised if the property is null, contains an empty string (""), or contains only white-space characters.

Get error message when using custom validation attribute

I'm using the CustomValidationAttribute like this
[CustomValidation(typeof(MyValidator),"Validate",ErrorMessage = "Foo")]
And my validator contains this code
public class MyValidator {
public static ValidationResult Validate(TestProperty testProperty, ValidationContext validationContext) {
if (string.IsNullOrEmpty(testProperty.Name)) {
return new ValidationResult(""); <-- how can I get the error message from the custom validation attribute?
}
return ValidationResult.Success;
}
}
So how can I get the error message from the custom validation attribute?
I know this is a little of an old post, but I will provide an better answer to the question.
The asker wants to use the CustomValidationAttribute and pass in an error message using the ErrorMessage property.
If you would like your static method to use the error message that you provided when decorating your property, then you return either:
new ValidationResult(string.Empty) or ValidationResult("") or ValidationResult(null).
The CustomValidationAttribute overrides the FormatErrorMessage of its base class and does a conditional check for string.IsNullOrEmpty.
There's no reliable way to get the error message from the attribute. Alternatively you could write a custom validation attribute:
[MyValidator(ErrorMessage = "Foo")]
public TestProperty SomeProperty { get; set; }
like this:
public class MyValidatorAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var testProperty = (TestProperty)value;
if (testProperty == null || string.IsNullOrEmpty(testProperty.Name))
{
return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
}
return null;
}
}
In this case the error message will be inferred from the custom validation attribute.
You can look into the following posting to get some ideas on how to do what you want to do (they use JS):
Custom validator error text through javascript?
Hope this helps.
The only way I have found that works is to validate the model from the post back method using TryValidateObject and if it fails, show the model again - then the error will show up.
[HttpPost]
public ActionResult Standard(Standard model)
{
var valContext = new ValidationContext(model, null, null);
var valResults = new List<ValidationResult>();;
bool b = Validator.TryValidateObject(model, valContext, valResults, true);
if(!b)
return View(model);
...

How does DataAnnotations really work in MVC?

This is more of a theoretical question.
I'm currently examining the MVC 3 validation by using ComponentModel.DataAnnotations, and everything works automagically, especially on client side.
Somehow something checks for those attributes, and generates javascript for the validation (or html5 attributes, if using unobtrusive mode), and it works.
My question is that what generates the client side javascript and how can I access and modify it? For example I want to handle the given dataannotation attributes a little differently, or handle custom attributes (I have found that I can derive them from ValidationAttribute, but maybe for some reason I don't want).
Can someone explain it to me what really happens?
(Or links to good explanations would also be good, as I have only found tutorials for actually using dataannotations)
EDIT: Also with deriving from ValidationAttribute, the client-side validation is not working automatically. Why?
MVC3 has a new jQuery Validation mechanism that link jQuery Validation and Validation Attributes Metadata, this is the jquery.validate.unobtrusive file that takes all data- attributes and work with them, just like before when you set the
<add key="UnobtrusiveJavaScriptEnabled" value="false" />
All you need to do is come up with your own Custom Validation Attributes, for that you have 2 options:
Create a Custom Validation Attribute that inherits the ValidationAttribute interface and
override the IsValid
or
Create a Self Validate Model use the model IValidatebleObject that all you need is to return the Validate method
in MVC3 you now have a method that you can override that has a ValidationContext object, where you can simply get all references, properties and values of any other object in the form
Create your own, and that unobtrusive file will handle the mapping of what your custom validator needs and will work out together with the jQuery Validation plugin.
YOU DO NOT Change the javascript... that's sooo 90's and not MVC way!
for example if you want to validate, let's say 2 dates that the last can not be less than the first (period of time for example)
public class TimeCard
{
public DateTime StartDate { get; set; }
[GreaterThanDateAttribute("StartDate")]
public DateTime EndDate { get; set; }
}
creating a Custom Validation
public class GreaterThanDateAttribute : ValidationAttribute
{
public string GreaterThanDateAttribute(string otherPropertyName)
:base("{0} must be greater than {1}")
{
OtherPropertyName = otherPropertyName;
}
public override string FormatErrorMessage(string name)
{
return String.Format(ErrorMessageString, name, OtherPropertyName);
}
public override ValidateionResult IsValid(object value, ValidationContext validationContext)
{
var otherPropertyInfo = validationContext.ObjectTYpe.GetProperty(OtherPropertyName);
var otherDate = (DateTime)otherPropertyInfo.GetValue(validationContext.ObjectInstance, null);
var thisDate = (DateTime)value;
if( thisDate <= otherDate )
{
var message = FormatErrorMessage(validationContext.DisplayName);
return new ValidationResult(message);
}
return null;
}
}
if using the Self Validating model then the code would be just
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if( EndDate <= StartDate )
yield return new ValidationResult("EndDate must be grater than StartDate");
}
Keep in mind that the Custom Validation is Generic, that's why much code, and Self Validating Model only works on the model applied.
Hope it helps
added
I didn't explain the Custom Client Validation part, fell free to ask if you need examples, but basically:
It's easier in MVC3 (if of course, you understand jQuery.Validate) all you need to do is:
Implement IClientValidateble
Implement a jQuery validation method
Implement an unobtrusive adapter
To create this 3 things, let's take this GreaterThanDateAttribute into account and create the Custom Client Side Validation. For that we need to code this:
append to the GreaterThanDateAttribute
public IEnumerable<ModelCLientValidation> GetCLientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelCLientValidationRule();
rule.ErrorMessage = FormatErrorMessage(metadata.GetDisplayName());
rule.ValidationType = "greater"; // This is what the jQuery.Validation expects
rule.ValidationParameters.Add("other", OtherPropertyName); // This is the 2nd parameter
yield return rule;
}
Then you need to write the new jQuery Validator and the metadata adapter that will link the jQuery.Validation with your code providing the correct data- attributes for that field (if of course, UnobtrusiveJavaScriptEnabled is true)
create a new js file and attach to your <head> for example as
<script src="#Url.Content("~/Scripts/customValidation.js")" type="text/javascript"></script>
and append the new validation
jQuery.validator.addMethod("greater", function(value, element, param) {
// we need to take value and compare with the value in 2nd parameter that is hold in param
return Date.parse(value) > Date.parse($(param).val());
});
and then we write the adapter
jQuery.validator.unobtrusive.adapters.add("greater", ["other"], function(options) {
// pass the 'other' property value to the jQuery Validator
options.rules["greater"] = "#" + options.param.other;
// when this rule fails, show message that comes from ErrorMessage
options.messages["greater"] = options.message;
});

Categories

Resources