Thursday, April 12, 2007

Fixing validators in ASP.Net Ajax

Update 2nd of July 2008:
The solution below doesn't always work. For a validator to not work you need to have .NET 2.0 installed without installing Service Pack 1 for it and you also need to add the validator dynamically (so it is not there when the page is loaded, but it appears there during an async postback).

The problem lies in the BaseValidator class itself, after the postback, the validator is not in the Page_Validators array. I've tried rehooking the validators, even enumerating all validators in the page and manually entering them in the Page_Validators array. It does not work. Mainly because the html spans of the validators are not the same thing as the validators and stuff like the id of the control to validate and other details are never rendered.

So you absolutely need to install the .Net 2.0 SP1 in order to use UpdatePanels with validators.

Today I've encountered a strange error, where the validation did work inside an UpdatePanel, but the validator message would not appear. This in a project where I used validation in GridViews inside their own UpdatePanel and they worked!

So I guess part of the reason the error occurs could be any one of these:
  • validated control and validator are both in a UserControl
  • validator is loaded at start of the page, it does not appear during Ajax calls, but it is replaced during Ajax calls
  • validated control is a ListBox, worse, an object inherited from a ListBox

Anyway, the true reason why the validators behaved in this fashion was that they were different from the html node of the validators. In other words, the Javascript array Page_Validators was filled with the validators correctly, did have the evaluation function, did return isvalid=false, but then, when the style.display attribute was changed to inline, the validator was not part of the html page DOM, as it was changed by Ajax.

Solution: a Javascript function that enumerates the validators, gets the validator DOM node by way of document.getElementById, stores the validator properties into this node and then replaces the element in the Page_Validators array.
Problem: where should one load it? Possible solutions include submit button onclick events, form onsubmit events and in the Page_Load method. I would not use onclick, since I should have added onclick events on all possible submit buttons. I would not use RegisterOnSubmitStatement, since it ran the code after checking if the validators are valid or not (even if the validation itself took place afterward; now that is weird). The only solution is to use Page_Load.

There are various ways of doing that, too, since you could use MasterPages, custom made Page objects and even HttpHandlers or HttpModules. You also could have custom controls or objects that don't have a reference to the Ajax ScriptManager object or even to the Ajax library itself. Yes, I know, I am very smart. In my project, all the pages were inherited from a custom Page object, also in the project. So it was relatively easy.

Here is the code:

// in Page_Load
string script=@"
if (typeof(Page_Validators)!='undefined')
for (var i=0; i<Page_Validators.length; i++)
// get DOM node
var vld=document.getElementById(Page_Validators[i].id);
if (vld) {
for (var key in Page_Validators[i])
// check if the Page_Validators element has extra attributes
// and add them to the node
if ((vld[key]==null)&&(Page_Validators[i][key]!==null))
// replace the Page_Validators element with the reconstructed validator
// get current ScriptManager for this page
ScriptManager sm=ScriptManager.GetCurrent(this);
if ((sm!=null)&&(sm.IsInAsyncPostback)) {
// if we did an Ajax postback, fix validators
ScriptManager.RegisterStartupScript(Page, GetType(),"FixValidators",script,true);


Kramii said...

Thanks for the article. It was very helpful.

I think that there is a typo in your solution. The problem line is:

if ((vld[key]==null)&&(Page_Validators[i][k]!==null))

I think it should probably have 'key' instead of 'k'. I am not certain. I had trouble getting the code to do exactly what I wanted, as my scenario is somewhat different to yours.

There is another useful article on this topic here:

Siderite said...

Thanks for the comment. I am glad I could be of help. What was your use scenario?

ZoZo said...

Thanks, that was a life saver :D