Wednesday, 27 October 2010

ASP.NET ASCX controls and ViewState

I've written a scripting engine into the ASP.NET app that I am developing. It's pretty neat - the user can choose from a list of scripts to load and work through, the script is loaded into a panel and they can follow it through.

Identified a problem yesterday whereby the second and subsequent scripts that got loaded had a strange little error - the first server control in the script didn't work on its first event (i.e. click, selectedindexchanged). It would work the second time the control was used - but that's no good for the users.

So I set about trying to solve it. After about seven hours of reading, trying things out and getting really frustrated I did solve it thanks to a couple of articles on the web. I made a load of mistakes so hopefully someone will read this and avoid making the same ones.

First, I had the scripts declared globally, i.e. Dim scriptname As Global.namespace.scriptname. Wrong wrong wrong!

What you need to do is create a ViewState called LastLoadedControl and set this to be the physical path of the control. This will reside in memory and allow you to persist the control between postbacks of the parent form. Then create a Private Sub called LoadUserControl(). This should check to make sure that the LastLoadedControl isn't null, then load the control and attach it to the Panel / Placeholder that you're using to display the controls.

You need to call this LoadUserControl method in the Page_Load event every time. Also, in the method that you're using to drive the user interaction (i.e. the list of scripts to pick from) you need to declare the physical path of the script, set the LastLoadedControl ViewState to this value, then call the LoadUserControl method.

Check out the first example in this blog (it's in C# but easily converted) for a really good example: .

A couple of warnings. First, even though the example above solved my problem, it was causing an error whereby the aspx page was trying to load the same control into the Panel / Placeholder twice and falling over as a result. What you have to do in the LoadUserControl method is specifically set the name of the control otherwise the ViewState of the control (i.e. its values) won't carry across between postbacks. Obviously you can see a scenario where you're creating the same control twice. To get around this you have to remove the control from the Panel / Placeholder every time. The example says to use Panel.Controls.Clear() but this didn't work for me. What I did was check that the Panel had > 1 control, then did Panel.Controls.RemoveAt(1) to eradicte the control and then replace it.

Second warning - some people say to do the control loading in Page_Init, rather than Page_Load. Problem with that is the Page_Init can't reference any Panels / Placeholders etc. so it's a bit of a waste of time.

POSTSCRIPT: just tried loading something into Page_Init in the .ascx control rather than the Page_Load event to stop the value of a control changing every time and it worked. I wonder if it's because I had the Page_Init set to Private before, and now I have changed it to Protected it seems to work ok. My bad.

1 comment:

Unknown said...

Thanks so much Mark. I've been working on a similar project for six years, and had a nagging problem of a label that was not restoring from ViewState. It was because I was using _contentPanel.Controls.Clear()
in my LoadCurrentControl() method.
When I switched to
if (_contentPanel.Controls.Count > 1)

everything now works perfectly!