I'm laying out a view that compares two password strings. The two properties in one of my models are pretty straightforward:
[Required]
[RegularExpression(@"(\S)+", ErrorMessage = "White space is not allowed")]
[StringLength(20, MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Name = "New Password")]
public string NewPassword { get; set; }
[Required]
[DataType(DataType.Password)]
[RegularExpression(@"(\S)+", ErrorMessage = "White space is not allowed")]
[StringLength(20, MinimumLength = 6)]
[Display(Name = "Confirm Password")]
[Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
Here's my view code:
<table class="fieldset center" width="400">
<tbody>
<tr>
<th width="150">
@Html.LabelFor(m => m.NewPassword)
</th>
<td>
@Html.PasswordFor(m => m.NewPassword, new { @class = "itext3" })
<br /><br />@Html.ValidationMessageFor(m => m.NewPassword)
</td>
</tr>
<tr>
开发者_JAVA技巧 <th width="150">
@Html.LabelFor(m => m.ConfirmPassword)
</th>
<td>
@Html.PasswordFor(m => m.ConfirmPassword, new { @class = "itext3" })
<br /><br />@Html.ValidationMessageFor(m => m.ConfirmPassword)
</td>
</tr>
</tbody>
</table>
All of the attributes fire their client-side validation messages when tested, except for the CompareAttribute on ConfirmPassword which is not firing until I hit the server. However, in my controller the ModelState.IsValid = false.
I compared what I'm doing to the default MVC application which is working correctly. Any suggestions for troubleshooting and fixing this?
I'm using MVC 3 RTM.
There is a BUG in jquery.validate.unobtrusive.js (and jquery.validate.unobtrusive.min.js, the minified version). The client-side validation emitted as a result of the [Compare] attribute will ONLY work if the compare-to field is the FIRST field on the form. To fix this bug, locate this line in jquery.validate.unobtrusive.js:
element = $(options.form).find(":input[name=" + fullOtherName + "]")[0];
which results in this equivalent:
element = $(options.form).find(":input[name=MyExample.Control]")[0];
Unfortunately that's not the right syntax for find(), and causes it to reference the first input control on the form.
Change that line to:
element = $(options.form).find(":input[name='" + fullOtherName + "']")[0];
which results in this equivalent:
element = $(options.form).find(":input[name='MyExample.Control]'")[0];
Which is the correct syntax, and correctly matches the input control with the specified name.
Locate the same code block in jquery.validate.unobtrusive.min.js, which looks like this:
f=a(b.form).find(":input[name="+d+"]")[0];
and change it to:
f=a(b.form).find(":input[name='"+d+"']")[0];
Take a look at your script tags in your _Layout.cshtml. I'm guessing the issue is probably your jQuery references. Did you start the MVC 3 project form scratch or are you using an example project or something like that?
Here is what happend with me; I had similar issues...
- I was using some example code that pointed to ajax.microsoft.com in the src attributes
- So, for example one of the script tags looked like this:
<script src="http://ajax.microsoft.com/ajax/jquery.validate/1.7/jquery.validate.min.js"></script>
- I wanted to get a better handle on what js was executing on the client side so I changed it to this:
<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
- That is just an example, I think there were a couple more script tags that I changed as well
So, after changing to internally served jQuery files it worked. I went back and looked at my local .validate.js file... and it was version 1.6. That is how i realized the issue was due to the version of jQuery or it's compatibility with one of the other the js libs anyway.
Bottom line is that it looks like 1.7 doesn't fully function with the validate.unobtrusive.js lib that I had... there may be a newer version that does function with 1.7... like I said, I was tinkering with an example project so there are some unknowns there. I suppose it could also be an incompatibility with the MvcValidation.js lib between it and one of the other js libs too?
At any rate, I'd say the simplest way to state your issue is that you are most likely referencing a bad combination of js libs. I'd say the best failsafe way to get a good combination of js libs is to create a new Asp.Net MVC 3 Project in Visual Studio and see what versions it gives you by default/with the project template... that is assuming that you didn't start the project from scratch. If you DID start it from scratch then may be you changed your layout file to have bad js references or if none of that is true then I suppose it could be a problem with the VisualStudio project templates?... realistically I'd say that is doubtful though. All of that said- I'd say the most likely cause [that I'd wager on anyway] is that you just got in trouble like me trying to quickly use some example code =)
I've tested this with ASP.NET MVC 3 RTM and it worked fine for me:
Model:
public class MyViewModel
{
[Required]
[RegularExpression(@"(\S)+", ErrorMessage = "White space is not allowed")]
[StringLength(20, MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Name = "New Password")]
public string NewPassword { get; set; }
[Required]
[DataType(DataType.Password)]
[RegularExpression(@"(\S)+", ErrorMessage = "White space is not allowed")]
[StringLength(20, MinimumLength = 6)]
[Display(Name = "Confirm Password")]
[Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
}
Controller:
public class HomeController : Controller
{
public ActionResult Index()
{
return View(new MyViewModel());
}
[HttpPost]
public ActionResult Index(MyViewModel model)
{
return View(model);
}
}
View:
@model SomeAppName.Models.MyViewModel
@{
ViewBag.Title = "Home Page";
}
<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
@using (Html.BeginForm())
{
@Html.LabelFor(m => m.NewPassword)
@Html.PasswordFor(m => m.NewPassword)
@Html.ValidationMessageFor(m => m.NewPassword)
<br/>
@Html.LabelFor(m => m.ConfirmPassword)
@Html.PasswordFor(m => m.ConfirmPassword)
@Html.ValidationMessageFor(m => m.ConfirmPassword)
<br/>
<input type="submit" value="OK" />
}
In this configuration client side validation works perfectly fine for all attributes including the [Compare]
.
I was never able to make the default way to work. Seems like in JQuery it fires only those validation methods that are registered with JQuery.validator.
Initially I was doing something like this...
jQuery.validator.unobtrusive.adapters.add("equaltopropertyvalidation", ["param"], function (rule)
{
alert("attaching custom validation adapter.");
rule.rules["equaltopropertyvalidation"] = rule.params.param;
rule.messages["equaltopropertyvalidation"] = rule.message;
return function (value, element, param)
{
alert("now validating!");
return value == this.currentForm[param].value;
};
});
But my second alert (now validating!) would never fire even though the first one would. I had seen this article but I was hesitating to implement it like this because it requires me to do something extra but I was lazy.
Well after spending lots of time to figure out what's wrong I implemented it in the way this article suggests and it works. Now it is like this and it works.
<script type="text/javascript">
jQuery.validator.addMethod("equaltopropertyvalidation", function (value, element, param)
{
return value == this.currentForm[param].value;
});
jQuery.validator.unobtrusive.adapters.add("equaltopropertyvalidation", ["param"], function (rule)
{
rule.rules["equaltopropertyvalidation"] = rule.params.param;
rule.messages["equaltopropertyvalidation"] = rule.message;
});
</script>
If you like detail then here it is: Look in jquery.validate.js and search for
check: function (element)
This is the function that is fired to check if the field is valid or not by firing all the applicable rules on the field. Now first it creates an array or rules to fire. It does that using the following code.
var rules = $(element).rules();
Then it iterates over the rules collection, creates a rule element for every item in the collection and then tries to find a corresponding method to fire for that rule. Once the method is found it is fired. It does that using the following code.
var result = $.validator.methods[method].call(this, element.value.replace(/\r/g, ""), element, rule.parameters);
Now the important thing is that if your custom method is not added to the $.validator.methods collection using the JQuery.validator.addMethod IT WILL NOT BE FOUND, even if it is sitting there in the JQuery.validator.unobtrusive.adapters collection.
There might be a way to make this work without using the JQuery.validator.addMethod (and if yes then someone please clarify) but this I believe is the reason the second approach works and the first doesn't.
I was using the following versions of Jquery files
- JQuery: 1.6/1.7.1
- Jquery.Validate: 1.9.0
- JQuery.Unobtrusive.Validate: taken from Microsoft CDN on 2/14/2012.
精彩评论