Another thing I also found out is that for some reason it seems that once Im entering something in the Published field(the textbox of the site) and then blur it(unfocus it) the validation function gets called for this field and all the other fields that
has this validation on them aswell..
Kind a hard to explain..
I noticed that the blur causes validation on on all fields with the remote validator - but no other fields. Are you seeing validation run on fields with out the remote validator?
Another thing I also found out is that for some reason it seems that once Im entering something in the Published field(the textbox of the site) and then blur it(unfocus it) the validation function gets called for this field and all the other fields that
has this validation on them aswell..
Kind a hard to explain..
I noticed that the blur causes validation on on all fields with the remote validator - but no other fields. Are you seeing validation run on fields with out the remote validator?
No..just the fields with the remote validator..but the most logical (at least for me) whould what I can tell, be if the remote validation function only gets called once for the one field that you blur..and not for all of those that has the same remote validation
aswell...but guess Im not the only one that think so :)..
Thanks for the temporary sulotion for the other problem :)..even though it whould be nice in the future to be able to just get the values from all fields from the same parameter..or how I should put it..
After cleaning up my sample, I can no longer reproduce multiple remote validators running after an onBlur event. This is what I ended up with:
[OutputCache(Location = OutputCacheLocation.None, NoStore = true)]
public class ValidationController : Controller {
public JsonResult IsDateTimeFormat(string strDate) {
DateTime newdatetime;
bool success = DateTime.TryParse(strDate, out newdatetime);
if (success)
return Json(true, JsonRequestBehavior.AllowGet);
else
return Json("The Date format you enterd is invalid!", JsonRequestBehavior.AllowGet);
}
public JsonResult IsDateTimeFormatBornDate(string BornDate) {
return IsDateTimeFormat(BornDate);
}
public JsonResult IsDateTimeFormatJoinDate(string JoinDate) {
return IsDateTimeFormat(JoinDate);
}
public JsonResult IsDateTimeFormatLastPostDate(string LastPostDate) {
return IsDateTimeFormat(LastPostDate);
}
}
}
A wrapper around each property.
I checked with the developers and got this response:
MVC 3 Developers
He can write his own client-side validator and server-side attribute if he wants. There’s nothing intrinsically magical about what we’re doing here, if he doesn';t like the duplication.
This specific instance, by the way, seems like a good candidate for its own custom validator anyway, rather than remote validation. Checking if something is a validly formatted date doesn’t seem like it really needs server-side logic.
Since this is something I expect to need, I wrote a reusable remote validator, based upon the remote validation example for MVC 2. The underlying issue is that the jquery.validate.js remote validator has no way to pass the value to be validated using a
generic name. Because of this, we need a new remote validation attribute, which can pass the value using a generic name, which is the variable name expected by our reusable remote function.
Here are the elements needed.
First, we have our example remote validation function, which in this case checks to see that our address fields do not have leading spaces (gee, a really useful out-of-the-box example, huh?). Create a ValidationController class with the following:
[OutputCache(Location = OutputCacheLocation.None, NoStore = true)]
[ControllerSessionState(System.Web.SessionState.SessionStateBehavior.Disabled)]
public class ValidationController : Controller
{
public JsonResult AddressCheck(string addresspart)
{
if (!addresspart.StartsWith(" "))
return Json(true, JsonRequestBehavior.AllowGet);
return Json("Cannot start address with a space", JsonRequestBehavior.AllowGet);
}
}
This assumes you are using MVC 3. If not, remove [ControllerSessionState(...)].
Next, add the following to jquery.unobtrusive.validate.js (for MVC 3) or MicrosoftMvcJqueryValidation.js for MVC 2 (though some changes are probably needed for MVC 2:
// reusable remote validation
$jQval.addMethod("remoteval", function (value, element, params) {
var url = params.url;
var parameterName = params.parametername;
var inputName = params.inputname;
if (this.optional(element))
return "dependency-mismatch";
var previous = this.previousValue(element);
if (!this.settings.messages[element.name])
this.settings.messages[element.name] = {};
previous.originalMessage = this.settings.messages[element.name].remoteval;
this.settings.messages[element.name].remoteval = previous.message;
if (element == document.activeElement) {
return true;
}
if (previous.old !== value) {
previous.old = value;
var validator = this;
this.startRequest(element);
var data = {};
// use the generic input name the remote function expects
// to hold the value to be validated
data[inputName] = value;
$.ajax({
url: url,
data: data,
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function (response) {
var valid = response === true;
if (valid) {
//var submitted = validator.formSubmitted;
//validator.prepareElement(element);
//validator.formSubmitted = submitted;
//validator.successList.push(element);
//validator.showErrors();
$.validationEngine.closePrompt(element, false);
} else {
//var errors = {};
var message = (previous.message = response || validator.defaultMessage(element, "remote"));
//errors[element.name] = $.isFunction(message) ? message(value) : message;
//validator.showErrors(errors);
$.validationEngine.buildPrompt(element, message, "error");
}
previous.valid = valid;
validator.stopRequest(element, valid);
return previous.valid;
}
});
return "pending";
} else if (this.pending[element.name]) {
return "pending";
}
return previous.valid;
});
This example is set up for the jquery.validationEngine plug-in, but the regular validator stuff is still there.
The following also must be added to the same js file:
adapters.add("remoteval", ["url", "parametername", "inputname"], function (options) {
var value = {
url: options.params.url,
parametername: options.params.parametername,
inputname: options.params.inputname
};
setValidationValues(options, "remoteval", value);
});
Next, we create our custom remote reusable validator:
// reusable remote validator
public sealed class RemoteReusableAttribute : ValidationAttribute
{
public string Action { get; set; }
public string Controller { get; set; }
// InputName is the generic name the remote validation function expects
public string InputName { get; set; }
public RemoteReusableAttribute()
{
}
public RemoteReusableAttribute(string action, string controller)
{
this.Action = action;
this.Controller = controller;
}
public RemoteReusableAttribute(string action, string controller, string inputName)
{
this.Action = action;
this.Controller = controller;
this.InputName = inputName;
}
public override bool IsValid(object value)
{
return true;
}
}
public class RemoteReusableAttributeAdapter : DataAnnotationsModelValidator<RemoteReusableAttribute>
{
public RemoteReusableAttributeAdapter(ModelMetadata metadata, ControllerContext context,
RemoteReusableAttribute attribute) :
base(metadata, context, attribute)
{
}
public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
{
ModelClientValidationRule rule = new ModelClientValidationRule()
{
// Use the default DataAnnotationsModelValidator error message.
// This error message will be overridden by the string returned by
// the remote validation function.
ErrorMessage = ErrorMessage,
ValidationType = "remoteval"
};
rule.ValidationParameters["url"] = GetUrl();
rule.ValidationParameters["parametername"] = this.Metadata.PropertyName;
// Add the input name the remote validation function expects. If no
// generic input name is specified, use the name of the parameter to be validated.
rule.ValidationParameters["inputname"] =
String.IsNullOrEmpty(Attribute.InputName) ? this.Metadata.PropertyName : Attribute.InputName;
return new ModelClientValidationRule[] { rule };
}
private string GetUrl()
{
RouteValueDictionary rvd = new RouteValueDictionary() {
{ "controller", Attribute.Controller },
{ "action", Attribute.Action }
};
var virtualPath = RouteTable.Routes.GetVirtualPath(ControllerContext.RequestContext,
null, rvd);
if (virtualPath == null)
{
throw new InvalidOperationException("No route matched!");
}
return virtualPath.VirtualPath;
}
}
Then, we add the following to Application_Start() in global.asax.cs:
Then, finally, we add the following in our metadata class:
[DisplayName("Address")]
[RemoteReusable("AddressCheck", "Validation", "addresspart")]
[Required(ErrorMessage = "Address Required")]
[StringLength(63, MinimumLength = 2, ErrorMessage = "Must be between 2 and 63 characters long")]
public object Address1 { get; set; }
[DisplayName("Address - Line Two")]
[RemoteReusable("AddressCheck", "Validation", "addresspart")]
[StringLength(63, ErrorMessage = "Must be < 63 characters long")]
public object Address2 { get; set; }
Now, when we have a form with the Address1 and Address2 fields, both will make a request to the server to validate that the input does not start with a space, and both will pass the generic variable "addresspart" to our function "AddressCheck".
I would really love to use this with an Integer range lookup from a database. I have many (50) integer fields on my form that I'd love to use this for but all of the actual allowable ranges are a lookup in a database. Anyway to be able to pass the actual
name of the element as an additional parameter to the JSon Function? Something like this in the Json...
public class ValidationController : Controller
{
public JsonResult CheckIntegerRange(int integervalue, string AttributeName)
{
var result = false;
int MinInteger = 0;
int MaxInteger = 100;
//declare recipe entities
var context = new MadicoRecipeEntities();
//set sql statements and get description, etc from attributes view
var esqlIntegerAttributeDetails = "SELECT VALUE c FROM MadicoRecipeEntities.v_AttributeIntegerRangeDetails AS c " +
"WHERE c.Attribute = '" + AttributeName + "'";
var queryAttributeDetails = context.CreateQuery<v_AttributeIntegerRangeDetails>(esqlIntegerAttributeDetails);
var RecipeAttributes = queryAttributeDetails.ToList();
foreach (var AttributeDetails in RecipeAttributes)
{
MinInteger = AttributeDetails.Min;
MaxInteger = AttributeDetails.Max;
}
return Json(result, JsonRequestBehavior.AllowGet);
}
}
ricka6
All-Star
15088 Points
2277 Posts
Microsoft
Moderator
Re: CustomValidation?
Nov 27, 2010 06:09 AM|LINK
Thanks, I've reproduced the problem. I'll look into it next week.
Inx
Member
53 Points
217 Posts
Re: CustomValidation?
Nov 27, 2010 11:16 AM|LINK
Your welcome :) thanks yourself for trying to help me
ricka6
All-Star
15088 Points
2277 Posts
Microsoft
Moderator
Re: CustomValidation?
Nov 27, 2010 04:54 PM|LINK
This hack works until I can come up with something better.
In my case I have IsUID_Available2(string FirstName) as the wrapper method with the 2nd field name.
[OutputCache(Location = OutputCacheLocation.None, NoStore = true)] public class ValidationController : Controller { public JsonResult IsUID_Available(string Username) { if (UserNameHelper.IsAvailable(Username)) return Json(true, JsonRequestBehavior.AllowGet); string suggestedUID = String.Format(CultureInfo.InvariantCulture, "{0} is not available.", Username); for (int i = 1; i < 100; i++) { string altCandidate = Username + i.ToString(); if (UserNameHelper.IsAvailable(altCandidate)) { suggestedUID = String.Format(CultureInfo.InvariantCulture, "{0} is not available. Try {1}.", Username, altCandidate); break; } } return Json(suggestedUID, JsonRequestBehavior.AllowGet); } public JsonResult IsUID_Available2(string FirstName) { return IsUID_Available(FirstName); } }ricka6
All-Star
15088 Points
2277 Posts
Microsoft
Moderator
Re: CustomValidation?
Nov 27, 2010 05:22 PM|LINK
I noticed that the blur causes validation on on all fields with the remote validator - but no other fields. Are you seeing validation run on fields with out the remote validator?
Inx
Member
53 Points
217 Posts
Re: CustomValidation?
Nov 28, 2010 03:05 PM|LINK
No..just the fields with the remote validator..but the most logical (at least for me) whould what I can tell, be if the remote validation function only gets called once for the one field that you blur..and not for all of those that has the same remote validation aswell...but guess Im not the only one that think so :)..
Thanks for the temporary sulotion for the other problem :)..even though it whould be nice in the future to be able to just get the values from all fields from the same parameter..or how I should put it..
Thanks again :)
tugberk_ugur...
Participant
1944 Points
1344 Posts
MVP
Re: CustomValidation?
Nov 29, 2010 07:47 AM|LINK
Phli Haccked has a great talk at MIX 10 about MVC 2 and it gives a demon on custom validations;
http://live.visitmix.com/MIX10/Sessions/FT04
tweets as @tourismgeek
ricka6
All-Star
15088 Points
2277 Posts
Microsoft
Moderator
Re: CustomValidation?
Nov 29, 2010 11:08 PM|LINK
After cleaning up my sample, I can no longer reproduce multiple remote validators running after an onBlur event. This is what I ended up with:
[OutputCache(Location = OutputCacheLocation.None, NoStore = true)] public class ValidationController : Controller { public JsonResult IsDateTimeFormat(string strDate) { DateTime newdatetime; bool success = DateTime.TryParse(strDate, out newdatetime); if (success) return Json(true, JsonRequestBehavior.AllowGet); else return Json("The Date format you enterd is invalid!", JsonRequestBehavior.AllowGet); } public JsonResult IsDateTimeFormatBornDate(string BornDate) { return IsDateTimeFormat(BornDate); } public JsonResult IsDateTimeFormatJoinDate(string JoinDate) { return IsDateTimeFormat(JoinDate); } public JsonResult IsDateTimeFormatLastPostDate(string LastPostDate) { return IsDateTimeFormat(LastPostDate); } } }A wrapper around each property.
I checked with the developers and got this response:
counsellorbe...
Member
540 Points
112 Posts
Re: CustomValidation?
Nov 30, 2010 12:14 AM|LINK
Since this is something I expect to need, I wrote a reusable remote validator, based upon the remote validation example for MVC 2. The underlying issue is that the jquery.validate.js remote validator has no way to pass the value to be validated using a generic name. Because of this, we need a new remote validation attribute, which can pass the value using a generic name, which is the variable name expected by our reusable remote function.
Here are the elements needed.
First, we have our example remote validation function, which in this case checks to see that our address fields do not have leading spaces (gee, a really useful out-of-the-box example, huh?). Create a ValidationController class with the following:
[OutputCache(Location = OutputCacheLocation.None, NoStore = true)] [ControllerSessionState(System.Web.SessionState.SessionStateBehavior.Disabled)] public class ValidationController : Controller { public JsonResult AddressCheck(string addresspart) { if (!addresspart.StartsWith(" ")) return Json(true, JsonRequestBehavior.AllowGet); return Json("Cannot start address with a space", JsonRequestBehavior.AllowGet); } }This assumes you are using MVC 3. If not, remove [ControllerSessionState(...)].
Next, add the following to jquery.unobtrusive.validate.js (for MVC 3) or MicrosoftMvcJqueryValidation.js for MVC 2 (though some changes are probably needed for MVC 2:
// reusable remote validation $jQval.addMethod("remoteval", function (value, element, params) { var url = params.url; var parameterName = params.parametername; var inputName = params.inputname; if (this.optional(element)) return "dependency-mismatch"; var previous = this.previousValue(element); if (!this.settings.messages[element.name]) this.settings.messages[element.name] = {}; previous.originalMessage = this.settings.messages[element.name].remoteval; this.settings.messages[element.name].remoteval = previous.message; if (element == document.activeElement) { return true; } if (previous.old !== value) { previous.old = value; var validator = this; this.startRequest(element); var data = {}; // use the generic input name the remote function expects // to hold the value to be validated data[inputName] = value; $.ajax({ url: url, data: data, contentType: "application/json; charset=utf-8", dataType: "json", success: function (response) { var valid = response === true; if (valid) { //var submitted = validator.formSubmitted; //validator.prepareElement(element); //validator.formSubmitted = submitted; //validator.successList.push(element); //validator.showErrors(); $.validationEngine.closePrompt(element, false); } else { //var errors = {}; var message = (previous.message = response || validator.defaultMessage(element, "remote")); //errors[element.name] = $.isFunction(message) ? message(value) : message; //validator.showErrors(errors); $.validationEngine.buildPrompt(element, message, "error"); } previous.valid = valid; validator.stopRequest(element, valid); return previous.valid; } }); return "pending"; } else if (this.pending[element.name]) { return "pending"; } return previous.valid; });This example is set up for the jquery.validationEngine plug-in, but the regular validator stuff is still there.
The following also must be added to the same js file:
adapters.add("remoteval", ["url", "parametername", "inputname"], function (options) { var value = { url: options.params.url, parametername: options.params.parametername, inputname: options.params.inputname }; setValidationValues(options, "remoteval", value); });Next, we create our custom remote reusable validator:
// reusable remote validator public sealed class RemoteReusableAttribute : ValidationAttribute { public string Action { get; set; } public string Controller { get; set; } // InputName is the generic name the remote validation function expects public string InputName { get; set; } public RemoteReusableAttribute() { } public RemoteReusableAttribute(string action, string controller) { this.Action = action; this.Controller = controller; } public RemoteReusableAttribute(string action, string controller, string inputName) { this.Action = action; this.Controller = controller; this.InputName = inputName; } public override bool IsValid(object value) { return true; } } public class RemoteReusableAttributeAdapter : DataAnnotationsModelValidator<RemoteReusableAttribute> { public RemoteReusableAttributeAdapter(ModelMetadata metadata, ControllerContext context, RemoteReusableAttribute attribute) : base(metadata, context, attribute) { } public override IEnumerable<ModelClientValidationRule> GetClientValidationRules() { ModelClientValidationRule rule = new ModelClientValidationRule() { // Use the default DataAnnotationsModelValidator error message. // This error message will be overridden by the string returned by // the remote validation function. ErrorMessage = ErrorMessage, ValidationType = "remoteval" }; rule.ValidationParameters["url"] = GetUrl(); rule.ValidationParameters["parametername"] = this.Metadata.PropertyName; // Add the input name the remote validation function expects. If no // generic input name is specified, use the name of the parameter to be validated. rule.ValidationParameters["inputname"] = String.IsNullOrEmpty(Attribute.InputName) ? this.Metadata.PropertyName : Attribute.InputName; return new ModelClientValidationRule[] { rule }; } private string GetUrl() { RouteValueDictionary rvd = new RouteValueDictionary() { { "controller", Attribute.Controller }, { "action", Attribute.Action } }; var virtualPath = RouteTable.Routes.GetVirtualPath(ControllerContext.RequestContext, null, rvd); if (virtualPath == null) { throw new InvalidOperationException("No route matched!"); } return virtualPath.VirtualPath; } }Then, we add the following to Application_Start() in global.asax.cs:
// reusable remote validator DataAnnotationsModelValidatorProvider.RegisterAdapter( typeof(FormsAway.Models.RemoteReusableAttribute), typeof(FormsAway.Models.RemoteReusableAttributeAdapter));Then, finally, we add the following in our metadata class:
[DisplayName("Address")] [RemoteReusable("AddressCheck", "Validation", "addresspart")] [Required(ErrorMessage = "Address Required")] [StringLength(63, MinimumLength = 2, ErrorMessage = "Must be between 2 and 63 characters long")] public object Address1 { get; set; } [DisplayName("Address - Line Two")] [RemoteReusable("AddressCheck", "Validation", "addresspart")] [StringLength(63, ErrorMessage = "Must be < 63 characters long")] public object Address2 { get; set; }Now, when we have a form with the Address1 and Address2 fields, both will make a request to the server to validate that the input does not start with a space, and both will pass the generic variable "addresspart" to our function "AddressCheck".
Q.E.D.
counsellorben
ricka6
All-Star
15088 Points
2277 Posts
Microsoft
Moderator
Re: CustomValidation?
Nov 30, 2010 06:01 AM|LINK
Very cool counsellorben - thanks for posting this.
plarochelle
Member
7 Points
4 Posts
Re: CustomValidation?
Dec 02, 2011 02:44 PM|LINK
I would really love to use this with an Integer range lookup from a database. I have many (50) integer fields on my form that I'd love to use this for but all of the actual allowable ranges are a lookup in a database. Anyway to be able to pass the actual name of the element as an additional parameter to the JSon Function? Something like this in the Json...
public class ValidationController : Controller { public JsonResult CheckIntegerRange(int integervalue, string AttributeName) { var result = false; int MinInteger = 0; int MaxInteger = 100; //declare recipe entities var context = new MadicoRecipeEntities(); //set sql statements and get description, etc from attributes view var esqlIntegerAttributeDetails = "SELECT VALUE c FROM MadicoRecipeEntities.v_AttributeIntegerRangeDetails AS c " + "WHERE c.Attribute = '" + AttributeName + "'"; var queryAttributeDetails = context.CreateQuery<v_AttributeIntegerRangeDetails>(esqlIntegerAttributeDetails); var RecipeAttributes = queryAttributeDetails.ToList(); foreach (var AttributeDetails in RecipeAttributes) { MinInteger = AttributeDetails.Min; MaxInteger = AttributeDetails.Max; } return Json(result, JsonRequestBehavior.AllowGet); } }