Exploring the depths and potentials of ASP.NET RSS 2.0 or Subscribe to .BenRush by Email
<
 Saturday, March 17, 2007

This has been done before here, but I would like to do this in a different way: by actually showing you how to write a web control that utilizes ASP.Net AJAX in a step-by-step manner. Let's start.

First thing is a bit of cursory-level knowledge of what it means to be an IScriptControl. As it were, the ASP.Net AJAX is - just like everything else in the .Net runtime - strictly object-oriented. As a result of this object-oriented'ness, everything is done as a class - and a control that can utilize the ASP.Net AJAX runtime, therefore, is best done as its own, independent type. Therefore, an IScriptControl is the interface that a control type must implement if it is to take part in the rich AJAX experience (or just utilize the client script libraries supplied by ASP.Net AJAX). My goal is to write some stupid, useless control; but while doing so teach you (and probably me) something - so I'm writing a button that cannot be clicked :-). I told you it was useless.

First, the shell:

namespace UnclickableButton
{
public class TheUnclickableButton : Button, IScriptControl
{
public IEnumerable<ScriptDescriptor> GetScriptDescriptors()
{
return;
}

public IEnumerable<ScriptReference> GetScriptReferences()
{
return;
}

public String InactiveCSSClass
{
get
{
return null;
}
set
{
}
}
public String ActiveCSSClass
{
get
{
return null;
}
set
{
}
}
}
}


Here you see the very elementary basics behind my control. I simply implement the button by inheriting from it; and also implement the whole IScriptControl interface. First you notice the IEnumerable methods, these are implemented to implement the IScriptControl interface. Second, you'll notice the properties, these are accessor methods that will be used to grab hold of the CSS style sheets needed to implement my unclickable button. Let's proceed...

...we have the server-side code done. But no AJAX application would be complete without associated client-side code. When building web controls for use in ASP.Net AJAX, careful attention was made to the fact that whatever settings, options, properties, etc. you set from the server should be reflected on the client; and so you must implement a client-side object which corresponds to your server-side object and the two will be knitted together thanks to the ASP.net AJAX runtime.

My server-side object has two accessor methods InactiveCSSClass and ActiveCSSClass, so my script-version of thisobject should too. Even though my script object is written in ECMA script (JavaScript), thanks to the ASP.Net AJAX runtime it can be object-oriented, and so requires a namespace to be programmable. The following script can then be written
within a new JS file:

 

Type.registerNamespace('BensSamples');

BensSamples.UnclickableButton = function(element){
BensSamples.UnclickableButton.initializeBase(this,[element]);

this._ActiveCSSClass = null;
this._InactiveCSSClass = null;
}

 

 

Here you see I registered a rather self-serving namespace and created what can be likened to as my client object's constructor. JavaScript is a prototype-based language, and so the actual type needs to be designed as a prototype. To fill in the datatype, you must fill in the prototype for your object as such:

BensSamples.UnclickableButton.prototype = {

initialize : function() {
// initialize the base
BensSamples.UnclickableButton.callBaseMethod(this,'initialize');

// register delegates
this._onActiveHandler = Function.createDelegate(this,this._onBlur);
this._onInactiveHandler = Function.createDelegate(this,this._onFocus);

// register handlers
$addHandlers(this.get_element(),
{ 'focus' : this._onFocus,
'blur' : this._onBlue},
this);

// set default css style
this.get_element().className = this._ActiveCSSClass;
}

dispose : function() {
// release the handlers
$clearHandlers(this.get_element());

// call to the base to do its dispose
BensSamples.UnclickableButton.callBaseMethod(this,'dipose');
}

// flesh out the actual handlers
_onFocus : function(e) {
if(this.get_element() && !this.get_element().disabled){
// if we have something real and it's enabled, set it to the
// disabled class type.
this.get_element().className = this._InactiveCSSClass;
return;
}
}
_onBlur : function(e) {
if(this.get_element() && !this.get_element().disabled){
// if we have something real and it's enabled, set it to the
// enabled class type.
this.get_element().className = this._ActiveCSSClass;
return;
}
}

// create accessor methods corresponding to the server control
get_ActiveCSSClass : function(){
return this._ActiveCSSClass;
}
set_ActiveCSSClass : function(value){
if(this._ActiveCSSClass!=value){
this._ActiveCSSClass = value;
// let everyone know the property changed
this.raisePropertyChanged('ActiveCSSClass');
}
}

get_InactiveCSSClass : function(){
return this._InactiveCSSClass;
}
set_InactiveCSSClass : function(value){
if(this._InactiveCSSClass!=value){
this._InactiveCSSClass = value;
// let everyone know the property changed
this.raisePropertyChanged('InactiveCSSClass');
}
}
}

// register the class
BensSamples.UnclickableButton.registerClass('BensSamples.UnclickableButton',Sys.UI.Control);

if(typeof(Sys)!=='undefined')Sys.Application.notifyScriptLoaded();

We now have a ECMA script class that lays out all of the definitions for the client type corresponding to the server type. We now need to hook the two together. To do this, you utilize the methods of the IScriptControl interface. The two methods have the following purpose:

  • GetScriptDescriptors(): returns a ScriptDescriptor object that identifies the metadata or properties/events/etc. of the client type that will be instantiated and used on the client. This is what effectively tells the ASP.Net AJAX runtime about your class and what it will do.
  • GetScriptReferences(): this returns a ScriptReference object that contains a reference to an external script file that fleshes out the functionality of your object.

The two methods will return all that is required for the AJAX runtime to fully instantiate, utilize and give you power over your ASP.Net AJAX control, on the client. Let's go back to our C# code and fill in the methods:

namespace UnclickableButton
{
public class TheUnclickableButton : Button, IScriptControl
{
public IEnumerable<ScriptDescriptor> GetScriptDescriptors()
{
ScriptControlDescriptor descriptor = new ScriptControlDescriptor(
"BensSamples.UnclickableButton", this.ClientID);
descriptor.AddProperty("InactiveCSSClass", this.InactiveCSSClass);
descriptor.AddProperty("ActiveCSSClass", this.ActiveCSSClass);

return new ScriptDescriptor[] { descriptor };
}

public IEnumerable<ScriptReference> GetScriptReferences()
{
ScriptReference reference = new ScriptReference();
reference.Path = "UnclickableButton.js";

return new ScriptReference[] { reference };
}

private String _InactiveCSSClassPath = String.Empty;
public String InactiveCSSClass
{
get
{
return _InactiveCSSClassPath;
}
set
{
_InactiveCSSClassPath = value;
}
}
private String _ActiveCSSClassPath = String.Empty;
public String ActiveCSSClass
{
get
{
return _ActiveCSSClassPath;
}
set
{
_ActiveCSSClassPath = value;
}
}
}
}

Now, let's create a CSS file and add it to our project and the webform atop which our UnclickableButton will be placed:


(StyleSheet.css Contents:)

body
{}
.InactiveButton{visibility:hidden;}
.ActiveButton{visibility:visible; }

Let's reference our new control from the .ASPX page:

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
<%
@ Register Namespace="UnclickableButton" TagPrefix="BensSample" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<
link href="StyleSheet.css" rel="stylesheet" type="text/css" />
<
html xmlns="http://www.w3.org/1999/xhtml">
<
head runat="server">
<title>Untitled Page</title>
</
head>
<
body>
<form id="form1" runat="server">
<asp:ScriptManager ID="ScriptManager1" runat="server" />
<br />
<BensSample:TheUnclickableButton Text="Click Me!" ID="Button1" runat="server" 
   
InActiveCSSClass="InactiveButton" ActiveCSSClass="ActiveButton" />
</form>
</
body>
</
html>

Now, what happens when we run this? Unfortunately, nothing...why? The ScriptManager is the arbiter of all things ASP.Net AJAX; and for any control to participate within the ASP.net AJAX runtime, it must first be registered with the ScriptManager. So, we add the following.....

protected override void OnPreRender(EventArgs e)
{
if (!this.DesignMode)
{
_scriptManager = ScriptManager.GetCurrent(this.Page);
if (_scriptManager != null)
{
_scriptManager.RegisterScriptControl(this);
}
else
throw new ApplicationException("You must have a ScriptManager!");
}

base.OnPreRender(e);
}

protected override void Render(HtmlTextWriter writer)
{
if (!this.DesignMode)
_scriptManager.RegisterScriptDescriptors(this);

base.Render(writer);
}

We should now be able to run our software....the complete source code follows:

Server:

using System;
using System.Collections.Generic;
using System.Text;
using System.Web.UI;
using System.Web;
using System.Web.UI.WebControls;

namespace UnclickableButton
{
public class TheUnclickableButton : Button, IScriptControl
{
public TheUnclickableButton()
{
return;
}

private ScriptManager _scriptManager = null;
protected override void OnPreRender(EventArgs e)
{
if (!this.DesignMode)
{
_scriptManager = ScriptManager.GetCurrent(this.Page);
if (_scriptManager != null)
{
_scriptManager.RegisterScriptControl(this);
}
else
throw new ApplicationException("You must have a ScriptManager!");
}

base.OnPreRender(e);
}

protected override void Render(HtmlTextWriter writer)
{
if (!this.DesignMode)
_scriptManager.RegisterScriptDescriptors(this);

base.Render(writer);
}

public IEnumerable<ScriptDescriptor> GetScriptDescriptors()
{
ScriptControlDescriptor descriptor = new ScriptControlDescriptor(
"BensSamples.UnclickableButton", this.ClientID);
descriptor.AddProperty("InactiveCSSClass", this.InactiveCSSClass);
descriptor.AddProperty("ActiveCSSClass", this.ActiveCSSClass);

return new ScriptDescriptor[] { descriptor };
}

public IEnumerable<ScriptReference> GetScriptReferences()
{
ScriptReference reference = new ScriptReference();
reference.Path = "/AJAXTest/UnclickableButton.js";

return new ScriptReference[] { reference };
}

private String _InactiveCSSClassPath = String.Empty;
public String InactiveCSSClass
{
get
{
return _InactiveCSSClassPath;
}
set
{
_InactiveCSSClassPath = value;
}
}
private String _ActiveCSSClassPath = String.Empty;
public String ActiveCSSClass
{
get
{
return _ActiveCSSClassPath;
}
set
{
_ActiveCSSClassPath = value;
}
}
}
}

Client

Type.registerNamespace('BensSamples');

BensSamples.UnclickableButton = function(element){
BensSamples.UnclickableButton.initializeBase(this,[element]);
this._ActiveCSSClass = null;
this._InactiveCSSClass = null;
}

BensSamples.UnclickableButton.prototype = {

initialize : function() {
// initialize the base
BensSamples.UnclickableButton.callBaseMethod(this,'initialize');

// register delegates
this._onActiveHandler = Function.createDelegate(this,this._onBlur);
this._onInactiveHandler = Function.createDelegate(this,this._onFocus);

// register handlers
$addHandlers(this.get_element(),
{ 'focus' : this._onFocus,
'blur' : this._onBlur},
this);

// set default css style
this.get_element().className = this._ActiveCSSClass;
},

dispose : function() {
// release the handlers
$clearHandlers(this.get_element());

// call to the base to do its dispose
BensSamples.UnclickableButton.callBaseMethod(this,'dispose');
},

// flesh out the actual handlers
_onFocus : function(e) {
if(this.get_element() && !this.get_element().disabled){
// if we have something real and it's enabled, set it to the
// disabled class type.
this.get_element().className = this._InactiveCSSClass;
return;
}
},
_onBlur : function(e) {
if(this.get_element() && !this.get_element().disabled){
// if we have something real and it's enabled, set it to the
// enabled class type.
this.get_element().className = this._ActiveCSSClass;
return;
}
},

// create accessor methods corresponding to the server control
get_ActiveCSSClass : function(){
return this._ActiveCSSClass;
},
set_ActiveCSSClass : function(value){
if(this._ActiveCSSClass!=value){
this._ActiveCSSClass = value;
// let everyone know the property changed
this.raisePropertyChanged('ActiveCSSClass');
}
},

get_InactiveCSSClass : function(){
return this._InactiveCSSClass;
},
set_InactiveCSSClass : function(value){
if(this._InactiveCSSClass!=value){
this._InactiveCSSClass = value;
// let everyone know the property changed
this.raisePropertyChanged('InactiveCSSClass');
}
}
}

// register the class
BensSamples.UnclickableButton.registerClass('BensSamples.UnclickableButton',Sys.UI.Control);

if(typeof(Sys)!=='undefined')Sys.Application.notifyScriptLoaded();

 

 


kick it on DotNetKicks.com
Saturday, March 17, 2007 5:53:12 PM (Central Standard Time, UTC-06:00)  #    Comments [0] - Trackback
AJAX | ASP.Net | Programming
 Thursday, March 15, 2007

I'd like to make a visual version someday. Please let me know if you see any errors.

1. .ctor()
2. ScriptManager.OnInit()

 a. Register for Page.InitComplete, Page.PreRenderComplete, Page.PreRender
 b. PageRequestManager.OnInit()

3. ScriptManager.OnPageInitComplete()
 a. sets initCompleted boolean to true.
4. ScriptManager.OnPagePreRenderComplete()
 a. If postback is Async
     1. If page supports partial rendering
         a. IPage.ClientScript.GetPostBackEventReference
     2. ScriptManager.RegisterGlobalizationScriptBlock
         a. ClientCultureInfo.GetClientCultureScriptBlock(CurrentCulture)
         b. If culture script found, ScriptRegistrationManager.RegisterClientScriptBlock
            1. Adds reference to ScriptRegistrationManager.ScriptBlocks
     3. ScriptManager.RegisterScripts
         a. ScriptManager.CollectScripts()
         b. ScriptManager.AddFrameworkScript("MicrosoftAjax.js")
         c. If page supports partial rendering, 
               
ScriptManager.AddFrameworkScript("MicrosoftAjaxWebForms.js")
         d. Foreach script retrieved so far, ScriptManager.OnResolveScriptReference
         e. ScriptManager.RemoveDuplicates : removes duplicate script references so far
         f. Foreach script retrieved after duplicate checking, if should load script before UI then
             ScriptManager.RegisterClientScriptIncludeInternal
                 1. ScriptRegistrationManager.RegisterClientScriptInclude
                     a. Adds reference to ScriptRegistrationManager.ScriptBlocks
                         else ScriptManager.RegisterStartupScriptInternal
                 2. ScriptRegistrationManager.RegisterStartupScript
                     a. Adds reference to SCriptRegistrationManager.ScriptStartupBlocks
     4. ScriptManager.RegisterServices
         a. Foreach ServiceReference in _services, ServiceReference.Register()
         b. Foreach ScriptManagerProxy in _proxies, ScriptManagerProxy.RegisterServices
         c. If EnablePageMethods, PageClientProxyGenerator.GetClientProxyScript. If script
                then RegisterClientScriptBlockInternal.
             1. Adds to ScriptRegistrationManager.ScriptBlocks
 b. Else
     1. ScriptManager.RegisterScripts
         a. See above.
5. ScriptManager.OnPreRender() 
    a. If postback is Async
        1. PageRequestManager.OnPreRender
            a. IPage.SetRenderMethodDelegate, 
                sets to PageRequestManager.RenderPageCallback
6. PageRequestManager.RenderPageCallback
    a. PageRequestManager.ProcessUpdatePanels
    b. IHtmlForm.SetRenderMethodDelegate(PageRequestManager.RenderFormCallback)
    c. IHtmlForm.RenderControl
    d. Encode hiddenField to output HTML stream
    e. Encode asyncPostBackControlIDs to output HTML stream
    f. Encode postBackControlIDs to output HTML stream
    g. Encode updatePanelIDs to output HTML stream
    h. Encode childUpdatePanelIDs to output HTML stream
    i. Encode panelsToRefreshIDs to output HTML stream
    j. Encode asyncPostBackTimeout to output HTML stream
    k. If formAction is something, encode formAction to output HTML stream
    l. If pageTitle is something, encode pageTitle to output HTML stream
    m. PageRequestManager.RenderDataItems

         1. Foreach ScritpDataItem in PageRequestManager._scriptDataItems 
         
call EncodeString to "dataItemJson" : "dataItem"
    n. PageRequestManager.ProcessScriptRegistration
        
 1. RenderActiveArrayDeclarations(this._updatePanelsToRefresh, writer);
         2. RenderActiveScripts(this._updatePanelsToRefresh, writer);
         3. RenderActiveSubmitStatements(this._updatePanelsToRefresh, writer);
         4. RenderActiveExpandos(this._updatePanelsToRefresh, writer);
         5. RenderActiveHiddenFields(this._updatePanelsToRefresh, writer);
         6. RenderActiveScriptDisposes(this._updatePanelsToRefresh, writer);
    o. PageRequestManager.ProcesFocus
7. ScriptManager.Render() 
    a. PageRequestManager.Render()
        1. If Not postback is Async and page supports partial rendering
            a. IPage.VerifyRenderingInServerForm()
            b. PageRequestManager.RenderPageRequestManagerScript
    b. If Not postback is Async
        1. IPage.ClientScriptManager.RegisterStartupScript("AppInitialize")


kick it on DotNetKicks.com
Thursday, March 15, 2007 2:26:31 PM (Central Standard Time, UTC-06:00)  #    Comments [0] - Trackback
AJAX | ASP.Net

The task is to add a confirmation dialog whenever someone clicks a Delete or Add or whatever button/link/image on your GridView or Repeater. To accomplish this will require a bit of JavaScript, but it's really not hard at all.

The Repeater has the ItemDataBound and the GridView has the RowDataBound events, and in order to attach your JavaScript to an object within EACH row or item, you will need to hook this event (my samples will be with the GridView, but the Repeater uses the same methodology).

protected void GridView_Views_RowDataBound(object sender, GridViewRowEventArgs e)
{
if (e.Row.RowType == DataControlRowType.DataRow)
{
   ImageButton button =
      (ImageButton)e.Row.Cells[0].Controls[0];
   button.Attributes.Add(
      "onclick", "if(!confirm('Delete this item?'))return false;");

   return;
}
return;
}

Many people become confused on how to get at a particular control once rendered into the browser DOM with their script because the name changes for each row or item of the parent control (GridView or Repeater). They become curious how to attach their script to something seemingly random such as:

ctl00$ContentPlaceHolder1$Repeater1$ctl00$btnDelete

The answer is, you don't. You attach it to the object as it's being rendered into the newly bound row like above. Here, nothing has been scoped and named out for the client DOM, it's still just an object in memory.

The way the script works is simple: the event model of the browser will prevent something from bubbling if you return a false or 0 value from the on* handler (such as onclick above). If you return a success or 1 value, then the event will continue to trickle up the call chain to whomever else is registered to handle it (which, in this case, would be the ASP.Net client page framework which initiates the __doPostback() call).

Recommended reading:

kick it on DotNetKicks.com
Thursday, March 15, 2007 10:27:40 AM (Central Standard Time, UTC-06:00)  #    Comments [0] - Trackback
ASP.Net | Programming
 Monday, March 12, 2007

ASP.Net AJAX offers a few helper routines for writing debug/trace statements in your clientside scripts. One of these helper routines is Sys.Debug.trace(), and it is really only a thin wrapper over a block of code that identifies the host browser and then utilizes its built-in tracing facilities.

Within MicrosoftAjax.js you'll find that trace() internally calls appendConsole(). The internals of appendConsole() looks like this:

if(typeof Debug!=="undefined"&&Debug.writeln)
   Debug.writeln(a);
if
(window.console&&window.console.log)
   window.console.log(a);
if(window.opera)
   window.opera.postError(a);
if(window.debugService)
   window.debugService.trace(a);

The first conditional is detecting if you're running Internet Explorer by seeing if Debug and Debug.writeln have definition. An example of what Debug.writeln() does can be found here.  

It appears as though window.console.log() is for Safari, window.opera.postError() is for Opera (obviously), and window.debugService.trace() is for WebDevHelper (I believe). I don't see one for FireFox, does FireBug not have tracing support? Certainly it must.....


kick it on DotNetKicks.com
Monday, March 12, 2007 1:13:01 AM (Central Standard Time, UTC-06:00)  #    Comments [0] - Trackback
AJAX | ASP.Net
 Friday, March 09, 2007

Say you have a situation where you want to catch all unhandled web errors in your global.asax file. You reimplement part of your site in ASP.Net AJAX and suddenly some exceptions that were being caught are no longer caught, the question is, what's happening?

The answer lies within the PageRequestManager: the server-side "brains" of ASP.Net AJAX. This object is a control just like any other when used on an ASP.Net page and registers for errors on the page within it's OnInit() method:

if (_owner.IsInAsyncPostBack) {
_owner.IPage.Error += OnPageError;
}

The IPage reference is a reference to a page wrapper that ASP.Net AJAX uses internally; suffice it to say that it's simply registering for the event fired when an unhandled exception occurs on a page - that part is nothing new to AJAX.  

What happens, though is that the following block of code gets executed within the OnPageError() handler if you do not have custom errors redirect enabled:

if (showAsyncErrorMessage) {
_owner.IPage.Response.Clear();
EncodeString(_owner.IPage.Response.Output, ErrorToken, httpCode.ToString(CultureInfo.InvariantCulture), errorMessage);
_owner.IPage.Response.End();
}

The page response gets cleared, a special blob of text gets inserted, and then the response stream ends. Doing this effectively wipes the page response (including any error information in it) away - thereby short-circuiting the ability for your handler method in Global.asax to get called.

Run Fiddler on your machine to see the kind of stuff that gets pushed back to the browser when an error is handled by this OnPageError() method, it's a simple '|' seperated string.

If you wish to get your handler to work in the Global.asax file again, you must add some kind of <customErrors> redirect page - this drives the execution flow differently through the PageRequestManager object and leaves the error information together for your global error handler(s).


kick it on DotNetKicks.com
Friday, March 09, 2007 2:48:00 PM (Central Standard Time, UTC-06:00)  #    Comments [0] - Trackback
Under the Hood

I haven't done a whole lot with the now ASP.Net AJAX since it was "Atlas" back in the day; and so I had forgotten a few things I knew a the time. When adding ASP.Net AJAX to your projects, remember to include within the web.config file of your main application all of the templated configuration settings found in the web.config file installed with the AJAX Extensions.

You can find this web.config file here:

%ProgramFiles%\Microsoft ASP.NET\ASP.NET 2.0 AJAX Extensions\v1.0.61025

Basically just snag from it all of the sections and import them into your web.config file. It seems a bit archaic, yes, but that's what you have to do in order to setup all of the HTTP Modules and whatnot needed by the AJAX runtime.

....I couldn't find any kind of wizard in Visual Studio to do this automagically. If you find one, let me know.


kick it on DotNetKicks.com
Friday, March 09, 2007 10:14:46 AM (Central Standard Time, UTC-06:00)  #    Comments [1] - Trackback
Ranting | Short and Simple
 Wednesday, March 07, 2007

Older content from my old blog IBenRush has been preserved below. New stuff soon to follow.


kick it on DotNetKicks.com
Wednesday, March 07, 2007 7:49:22 PM (Central Standard Time, UTC-06:00)  #    Comments [0] - Trackback

 Tuesday, July 04, 2006

The June CTP of "Atlas" is out; I'd recommend downloading it if you're into that sort of thing.

The biggest change in the release is that you now have the ability to dynamically create and use UpdatePanel controls - this opens up the capability to use them in templated scenarios such as in GridViews, DataGrids, Repeaters, etc. The change the CTP made wasn't to "add" dynamic UpdatePanel controls, but to relax a constraint which pinched when Triggers had to reference the UpdatePanel.

Triggers are the feature of UpdatePanels which allows them to be connected to controls outside their content template region; that is, if the UpdatePanel surrounds a bunch of controls, but wants to also be "notified" when a control outside itself causes a post back, then it must use a Trigger (out of the box there are two types of triggers: those which update a panel when a control raises an event, and those which update a panel when a control's value changes). This is useful if you have a region that surrounds controls that must be asynchronously updated in the browser, but the control that causes the asynchronous update is  off somewhere else on the page (such as a submit button, et al).

When the client "fires" the trigger, the posted data comes back asynchronously and is retrieved/analyzed on the server during the load post data event in the page's lifecycle - which just so happens to occur before the Load, PreRender, etc. events. When the posted data is recieved on the server, the code immediately attempts to link the posted data of the trigger to an UpdatePanel in memory. But - for many controls that support templates - this is too early for an UpdatePanel control which resides within them to have been instantiated. Therefore - no UpdatePanel is found and the post back results in no change on the client....this is the constraint that was changed.

Now, as of the June CTP, you can add UpdatePanel's dynamically to a page - which means they can be inserted at any time and can therefore be a part of the template of GridViews, etc. The most blatant change which powers this new ability occured in the RegisterUpdatePanel public method of the ScriptManager object - a method called by the OnInit method of the UpdatePanel to register itself with the ScriptManager. Whenever the UpdatePanel is instantiated on the page it must register itself with the ScriptManager so that post back events can be filtered to it proxy the ScriptManager (recall that the ScriptManager is basically the king of all controls on the page). What changed in this method is that it now references two variables which are set during the load post data phase of the page lifecycle by the ScriptManager as a "this is what happened during that phase" diary for UpdatePanels that may come about later.

The first of the two new variables referenced in the RegisterUpdatePanel method stores the unique identifier for the UpdatePanel sent to it by the client if that panel isn't found by the time the load post data event occurs. The second is a boolean flag which gets set to True when the load post data phase has completed to prevent duplicate operations on the control (double initializations). Here is the code:

public void RegisterUpdatePanel(UpdatePanel panel)
{
   if (panel == null)
   {
      throw new ArgumentNullException("panel");
   }
   if ((this._allUpdatePanels != null) && this._allUpdatePanels.Contains(panel))
   {
      throw new ArgumentException("This UpdatePanel has already been registered.", "panel");   
   }
   if (this._allUpdatePanels == null)
   {
      this._allUpdatePanels = new List<UpdatePanel>();
   }
   this._allUpdatePanels.Add(panel);
   if ((this._updatePanelRequiresUpdate != null) && (panel.UniqueID == this._updatePanelRequiresUpdate))
   {
      if (panel.Mode == UpdatePanelMode.Conditional)
      {
         panel.Update();
      }   
      this._updatePanelRequiresUpdate = null;
   }
   if (this._panelsInitialized)
   {
      panel.Initialize();
   }
}

When the load post data phase has completed, and the update panel which caused the post back event on the client wasn't found, the _updatePanelRequiresUpdate variable is set to the unique id - in addition the _panelsInitialized boolean is set to true. When an UpdatePanel is instantiated dynamically at a later time, its OnInit method will be called by the page framework (when you add a control to a control collection, one of the operations done to the control is to have its OnInit method called since it wasn't around when the whole page trickled the event down to its child controls). The UpdatePanel's OnInit method calls RegisterUpdatePanel, which now has the unique identifier of the newly instantiated panel control to check against the _updatePanelRequiresUpdate variable. If the identifiers match and this new control's mode is conditional, then the panel is explicitly updated. Regardless, if the Initialize method was called for all early panel's on the page already, then the newly instantiated control has it's Initialize method called thanks to the _panelsInitialized boolean.

As a result...dynamically created UpdatePanels are now possible.


kick it on DotNetKicks.com
Tuesday, July 04, 2006 11:14:13 AM (Central Standard Time, UTC-06:00)  #    Comments [0] - Trackback
Computing

Computers Blogs - Blog Top Sites

Archive
<March 2007>
SunMonTueWedThuFriSat
25262728123
45678910
11121314151617
18192021222324
25262728293031
1234567
Blogroll
About the author/Disclaimer

Disclaimer
The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

© Copyright 2009
Benjamin Rush
Sign In
Statistics
Total Posts: 444
This Year: 0
This Month: 0
This Week: 0
Comments: 128
Themes
Pick a theme: