ASP.NET MVC 3 client-side validation with parameters - asp.net-mvc

Following on from this post Perform client side validation for custom attribute
I am trying to get my head around how to do this, passing additional parameters to the client-side script
As I understand it so far to implement custom validation with MVC 3 the following is required
Create a custom validation attribute
Based on ValidationAttribute and implementing IClientValidatable. I have also see some examples deriving from ModelValidator, which seems to implement the functionality of both ValidationAttribute and IClientValidatable. So this is my first point of confusion as to what the diffirences are or whether ModelValidator was used in MVC 2 but is now deprecated or what ?
An instance of ModelClientValidationRule must be returned from GetClientValidationRules() to specify details such as the error message, ValidationType (which I understand to be the name of the Javascript function that will perform the client-side validation) and any additional custom parameters that the attribute may have, and that need to be passed to the Javascript validation.
I assume that the runtime (not sure which part of it) then use the ModelClientValidationRule to generate html attribute in the tag elements as follows:
data-val="true" (to indicate that the element requires validation)
data-val-[ValidationType]=[ErrorMessage]
data-val-[ValidationType].[ValidationParameters(n).Key]=[ValidationParameters(n).Value]
Implement the client-side validation logic
A Javascript function must be created and added to jQuery.validators with jQuery.validators.addmethod() so that JQuery is aware of it when it need to be executed. Something like:
jQuery.validator.addMethod(
'greaterThan',
function (value, element, params) {
/.../
return /* true or false */ ;
},
''
);
My question here is whether the signature 'function (value, element, params)' is standard for methods that will handle validation and I assume it will be called by some jQuery functionality at the appropriate time such as before a form is submitted or when an element looses fuces or on keyUp events. I just don't undertand how you can controll this i.e. choose which event is appropriete for yout custom validation.
Implement an unobtrusive adapter
This translates unobtrusive attributes to; something I am not very clear on, but assume it to be a jQuery Rule, but I am not clear on how those work. Something like
jQuery.validator.unobtrusive.adapters.add(
'futuredate',
{ },
function (options) {
options.rules['greaterThan'] = true;
options.messages['greaterThan'] = options.message;
}
);
My question here is about 'function (options)'. Is this the function that will be called before 'function (value, element, params)' and is responsible for extracting the unobtrusive tags into a data structure that can be understood by jQuery.Validation. From the code example it seems to me that options is an object that contains both, the attribute values from the tag (such as options.message) and the jQuery relevant properties it must map to (such as options.messages['ClientSideValidationFunctionName']. If so how are custom parameters retrieved and mapped.
I hope I have not added any additional confusion.

You could use the ValidationParameters property to add custom parameters to the rule:
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule
{
ErrorMessage = this.ErrorMessage,
ValidationType = "futuredate",
};
rule.ValidationParameters.Add("param1", "value1");
rule.ValidationParameters.Add("param2", "value2");
yield return rule;
}
which could be used in the adapter:
jQuery.validator.unobtrusive.adapters.add(
'futuredate',
[ 'param1', 'param2' ],
function (options) {
var param1 = options.params.param1; // shall equal 'value1'
var param2 = options.params.param2; // shall equal 'value2'
// TODO: use those custom parameters to define the client rules
}
);
UPDATE:
As requested in the comments section here's how you could pass those parameters to the custom validator rule function:
jQuery.validator.unobtrusive.adapters.add(
'futuredate',
[ 'param1', 'param2' ],
function (options) {
// simply pass the options.params here
options.rules['greaterThan'] = options.params;
options.messages['greaterThan'] = options.message;
}
);
jQuery.validator.addMethod('greaterThan', function (value, element, params) {
// params here will equal { param1: 'value1', param2: 'value2' }
return ...
}, '');

Related

MVC Remote Validation With Additional bool Fields

I am trying to use Remote Validation with an additional bool checkbox field
[Remote("IsStorageConnectionValid", "TenantManagement", AdditionalFields = "CreateStorage")]
public String StorageConnectionString { get; set; }
Validation code
public JsonResult IsStorageConnectionValid(string storageConnectionString, bool createStorage){
It works perfectly in terms of it hitting the validator. However createStorage is always true irrespective of the value of the checkbox. If I use additional fields that aren't check boxes they are supplied perfectly.
Checkbox created as standard:
#Html.CheckBoxFor(m => m.CreateStorage)
Is this a bug? Or am I doing it wrong?
Fiddle Is here (Is MVC4 I think but does the same thing)
It does appear that this is a bug when used with #Html.CheckBoxFor. The problem is that CheckBoxFor renders 2 elements, a checkbox with value="true" and a hidden input with value="false" (Unchecked checkboxes do not post back so the second hidden input ensures a value is posted back for use by the DefaultModelBinder)
Looking at the relevant section of the jquery.validate.unobtrusive.js file
adapters.add("remote", ["url", "type", "additionalfields"], function (options) {
var value = {
url: options.params.url,
type: options.params.type || "GET",
data: {}
},
prefix = getModelPrefix(options.element.name);
$.each(splitAndTrim(options.params.additionalfields || options.element.name), function (i, fieldName) {
var paramName = appendModelPrefix(fieldName, prefix);
value.data[paramName] = function () {
return $(options.form).find(":input[name='" + escapeAttributeValue(paramName) + "']").val();
};
});
setValidationValues(options, "remote", value);
});
the return statement returns (in your case) .find(':input[name="CreateStorage"]').val(); which returns the value of the first input with the name="CreateStorage" which will always be true (the value of the checkbox)
As a test, if you render the value using HiddenFor rather that CheckBoxFor you will receive the correct value in your IsStorageConnectionValid method (but of course this does help since you cant change the value)
Not sure of the best solution, but the unobtrusive script should be first checking if .find(..) returns more than one element, then if the first is a checkbox which is unchecked, returning the value of the second element.
Edit
I have reported this as an issue at Codeplex
Edit 2
I have been advised the the issue has now been fixed here

$watch not invoked on input change when input doesn't match the pattern

I'm using ngVal to decorate my inputs with AngularJS directives based on the ASP.NET MVC Data Annotations.
My model has the following annotations:
[Display(Name="Test Number")]
[Required]
[RegularExpression(#"^[\d]{6}$", ErrorMessage = "The value must be six numbers.")]
public string TestNumber { get; set; }
ngVal is then used to display the error messages associated with the input field.
From what I can determine (with a debug breakpoint in the $watch function) is that the function that should be called on modification of the value in scope is not being called unless the pattern is matched. Here's how the ngVal directive sets up the scope.$watch:
var ngval = angular.module('ngval', []);
ngval.directive('ngval', ['$parse', function ($parse) {
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, iElm, iAttrs, ngModel) {
var messages = angular.fromJson(iAttrs.ngval);
var getErrors = function() {
var errors = [];
for (var prop in messages) {
if (ngModel.$error[prop])
errors.push({ validator: prop, message: messages[prop] });
}
return errors;
};
scope.$watch(function() {
return ngModel.$modelValue;
}, function () {
ngModel.ngval = {
hasError: ngModel.$dirty && ngModel.$invalid,
errors: getErrors()
};
});
}
};
}]);
The result is that initially I see that the field is required but typing something invalid doesn't change the error to the one specified for the pattern - it still says the field is required. This is because the function given to the $watch isn't being invoked. If I enter 6 numbers, the field is valid and modifying the value after that shows the pattern mismatch message. The only exception to this is if I select the contents of the input box and erase it with one key press (del/backspace).
Is this normal behaviour; that scope.$watch isn't invoked if the value doesn't match the pattern?
EDIT: I've added a ng-change directive on the input and it behaves the same - it's not invoked if the input doesn't match the pattern. A demonstration of the behaviour can be seen in this plunker: http://plnkr.co/edit/krmQVk0giwCYGxdY5YNS?p=preview
The problem is that the watch is set up against the model, and the value of the property on the model is not set for input changes that do not match the ngPattern. The property on the model is only changed to the input value when it matches the pattern; otherwise the property is emptied (null, undefined or blank - I'm not sure.
The workaround I have in place is to watch the $error properties of the DOM element, which are updated by AngularJS. I added a formItem property to allow the name of the form and input element to be passed to the HTML Helper method so that it can add it to the markup. The watch is now set up as follows:
for (prop in messages) {
scope.$watch(iAttrs.formitem + ".$error."+ prop,
function() {
ngModel.ngval = {
hasError: ngModel.$dirty && ngModel.$invalid,
errors: getErrors()
};
});
}
I hope that helps someone out there with the same issue.

Zend Framework 2, filter a value after validation in an InputFilter

In an InputFilter I have this code:
$password = new Input('password');
$password->setRequired(false)
->getValidatorChain()
->attach(new Validator\StringLength(6));
$password->getFilterChain()
->attach($this->passwordHash);
The problem is that the filter is applying to the value before the validation, so the validator always returns true.
I was wondering if there is any way to do the filtering after the validation.
Zend\InputFilter\FileInput class is one of the input types that does validation before filtering. You can see how they solved it in the source code and make your own PasswordInput class based on that solution.
As you can see in the source code on line 64 there is an if clause that prevents the filters to run as long as isValid is false (the initial value). So the validators run first and then when the parent InputFilter after validation calls getValue again to get the value from FileInput it runs the filters.
It seems odd to filter after validation as the input before validation could be edited by the filters. Like strip tags or trim the string. If you do use a filter like \Zend\I18n\Filter\Alnum() you could let it remove the whitespaces. So see it as bad practice to filter after validating your input.
E.g., take those filters:
// Default settings, deny whitespace
$filter = new \Zend\I18n\Filter\Alnum();
echo $filter->filter("This is (my) content: 123");
// Returns "Thisismycontent123"
// First param in constructor is $allowWhiteSpace
$filter = new \Zend\I18n\Filter\Alnum(true);
echo $filter->filter("This is (my) content: 123");
// Returns "This is my content 123"
Notice that the begin value differs from the result. But you can filter after validation, but I guess you have to it by yourself. So you could add a method to your inputfilter or form which you call after validation. For example:
public function filterPassword() {
$filter = new \Zend\I18n\Filter\Alnum(true);
$inputs = $this->getInputs();
$password = $filter->filter($inputs['password']);
return $password;
}
In your controller you can do this for example:
public function ExampleController extends AbstractActionController
{
public function sampleAction() {
$form = new LoginForm();
if($form->isValid()) {
$inputFilter = $form->getInputFilter();
$filterdPassword = $inputFilter->filterPassword();
}
return array('form' => $form);
}
}

Symonfy 1.4 dynamic validation possible?

I'm trying to create a form that change the validation of a field based on the select option from the html form field.
Ex: if user select a option 1 from drop down field "options", I want the field "metric" to validate as sfValidatorInteger. If user select option 2 from field "options", I want the field "metric" to validate as sfValidatorEmail, etc.
So inside the public function configure() { I have the switch statement to capture the value of "options", and create the validator based on that value returned from the "options".
1.) How do I capture the value of "options" ? I've tried:
$this->getObject()->options
$this->getTaintedValues()
The only thing that's currently working for me is but it's not really MVC:
$params = sfcontext::getInstance()->getRequest()->getParameter('options');
2.) Once I've captured that information, how can I assign the value of "metric" to a different field? ("metric" is not a real column in db). So I need to assign the value of "metric" to different field such as "email", "age" ... Currently I'm handling this at the post validator like this, just wondering if I can assign value within the configure():
$this->validatorSchema->setPostValidator(new sfValidatorCallback(array('callback' => array($this, 'checkMetric'))));
public function checkMetric($validator, $values) {
}
Thanks!
You want to use a post validator. Try doing something like this in your form:
public function configure()
{
$choices = array('email', 'integer');
$this->setWidget('option', new sfWidgetFormChoice(array('choices' => $choices))); //option determines how field "dynamic_validation" is validated
$this->setValidator('option', new sfValidatorChoice(array('choices' => array_keys($choices)));
$this->setValidator('dynamic_validation', new sfValidatorPass()); //we're doing validation in the post validator
$this->mergePostValidator(new sfValidatorCallback(array(
'callback' => array($this, 'postValidatorCallback')
)));
}
public function postValidatorCallback($validator, $values, $arguments)
{
if ($values['option'] == 'email')
{
$validator = new sfValidatorEmail();
}
else //we know it's one of email or integer at this point because it was already validated
{
$validator = new sfValidatorInteger();
}
$values['dynamic_validation'] = $validator->clean($values['dynamic_validation']); //clean will throw exception if not valid
return $values;
}
1) In a post validator, values can be accessed by using the $values parameter. Just use $values['options'] and it should be fine... or did you want to access this values from another part of you code? $this->getObject()->widgetSchema['options'] should work too I think, once your form is bound to an object.
2) The configure() method is called at the end of the form constructor, so values are not bound nor accessible yet, unless you are initializing your form with an object from the db (which does not require any validation). But if you want to initialize your form from $_POST, a post validator is definitely the way to go IMHO.
I got the validation error to appear alongside the field by throwing a sfValidatorErrorSchema instead of a sfValidatorError.
$values['dynamic_validation'] = $validator->clean($values['dynamic_validation']);
…becomes…
try
{
$values['dynamic_validation'] = $validator->clean($values['dynamic_validation']);
}
catch(sfValidatorError $e)
{
$this->getErrorSchema()->addError($e, 'dynamic_validation');
throw $this->getErrorSchema();
}
Not sure if this is the best way to get this result, but it seems to be working for me at the moment.

Post multiple parameters to MVC Controller using jQuery.post

I have a controller defined as:
[AcceptVerbs(HttpVerbs.Post)]
public JsonResult PostMoreData(DataContracts.Address address, DataContracts.GeoLocation geoLocation)
{
return Json("test");
}
where DataContracts.Address and DataContracts.GeoLocation are complex types.
From my View i'm trying to post using jQuery as such:
function PostMoreData() {
var JsonAddress = {
"Building": $('Building').val(),
"UnitNumber": $('UnitNumber').val(),
"StreetNumber": $('StreetNumber').val(),
"StreetName": $('StreetName').val(),
"StreetType": $('StreetType').val(),
"Suburb": $('Suburb').val(),
"State": $('State').val(),
"Postcode": $('Postcode').val(),
"MonthsAtAddress": $('MonthsAtAddress').val()
};
var JsonGeoLocation = {
"Latitude": $('Latitude').val(),
"Longitude": $('Longitude').val()
};
jQuery.post("/AddressValidation/PostMoreData", {address: JsonAddress, geoLocation: JsonGeoLocation}, function(data, textStatus) {
if (textStatus == "success") {
var result = eval(data);
if (result.length > 0) {
alert(result);
}
}
}, "json");
}
However, on the controller, I get nulls.
It works if my Controller takes just 1 argument and I post just one object.
[AcceptVerbs(HttpVerbs.Post)]
public JsonResult PostMoreData(DataContracts.Address address)
{
return Json("test");
}
function PostMoreData() {
var JsonAddress = {
"Building": $('Building').val(),
"UnitNumber": $('UnitNumber').val(),
"StreetNumber": $('StreetNumber').val(),
"StreetName": $('StreetName').val(),
"StreetType": $('StreetType').val(),
"Suburb": $('Suburb').val(),
"State": $('State').val(),
"Postcode": $('Postcode').val(),
"MonthsAtAddress": $('MonthsAtAddress').val()
};
jQuery.post("/AddressValidation/PostMoreData", JsonAddress, function(data, textStatus) {
if (textStatus == "success") {
var result = eval(data);
if (result.length > 0) {
alert(result);
}
}
}, "json");
}
Any ideas how i can post more than one object?
Note that the "default serialization" that jQuery is doing here isn't going to work no matter what your controller does. jQuery doesn't "traverse" the parameter map below the first level, so the example in the question is likely generating this post data:
address=[object]&geoLocation=[object]
The other, working example does not contain any sub-objects, so it is being translated directly, like this:
Building=value&UnitNumber=value&...&MonthsAtAddress=value
The easiest fix is making the parameter map flat, each key prefixed with either 'Address.' or 'GeoLocation.', depending.
Thank you everyone for your input on this issue.
At this stage, we have departed from using jquery to post complex types to controllers. Instead we use the ms ajax framework to do that. ms ajax post nicely binds the complex types automatically out of the box.
So our solution now uses a mix of jquery and ms ajax framework.
Ash
Your code requires that the way jquery serializes an object is compatible with the MVC default model binder, which I think is unlikely.
If you can build your javascript object so that it serializes as a flat object with dot notation (JsonAddress.Building) that would work, or you can let jquery do the default serialization and then create a custom model binder to deserialize to the action parameter types.
I had the same problem and couldn't get anything to work. Also someone raised it as a bug with jquery and they closed it as not a bug.
I have found a few solutions which answer part of the whole question.
And the answer includes the following.
1) Client side: we would need to stringyfy all the objects you need to send. This could be a normal object or an array. It works on both.
2) Client side: You send the data as you have in the first post. As you would object by object.
Tip: When you send parameterised objects, jquery encodes the data sent to the server.
Following all are server side implementations
1) Deserializer class: which will take the input string and put it back in to object/list<>/IList<> what ever you have defined as datatype of the parameter of the controller function.
You would need to implement ActionFilterAttribute for the above.
2) Finally add an attribute to controller function, so that it uses the deserialiser class to get the parameters.
As this is quite a lot of code let me know if you need details or have you solved the problem.
Deepak Chawla

Resources