Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / ASP.NET

Callback WebControls

4.80/5 (44 votes)
5 Jan 2010Ms-PL9 min read 128.3K   1.3K  
The power of callback and JavaScript to render controls without reloading the entire page.

Introduction

In this article, I will present an aspect of communication in ASP.NET 2.0, named "Callback", and how it can be used in a web application. For this, I created a set of "Web Controls" based on "Callback", not on "Postback", but that work exactly like the standard controls of ASP.NET 2.0. I will first start by explaining the important concepts for understanding the article, and finally, I'll show you an example in practice.

Callback?

Callback is among the novelties of ASP.NET 2.0, by which we can communicate with the server without posting the whole page .. The difference between callback and postback is that in postback, whenever you click a button, the entire HTML of the page and its controls is regenerated (Render). In comparison, in callback, this is not the case, and it is very important! Why regenerate the entire HTML page? Is it required to do it when I just want to add a row in my GridView, for example.

So for a web application, the "Render" of just a control should be targeted. I mean, no need to regenerate the entire HTML code of the page and its controls.

You can use "AJAX controls", but you will not know anything about the code generated behind, and so you will have no control on what happens in your application.

To better understand this, we will see the page life cycle for the two modes: postback and callback.

Page Life Cycle

Let's see the difference between postback and callback in the Page life cycle. For more details, see: Page Life Cycle.

In the example: when the user clicks on Button1, the text value of the button will be changed to Button1Update.

Postback

PageLifeCycle_PostBack.jpg

  1. User clicks on Button1: the entire page is sent to the server (postback).
  2. The page life cycle begins. In the Button1_Click event, the Text value of button1 is changed to Button1Update.
  3. C#
    protected void Button1_Click(object sender, EventArgs e)
    {
        this.Button1.Text = "Button1Update";
    }
  4. At the end of the page life cycle, the HTML of the entire page is generated and sent to the client browser for interpretation.
  5. The browser reloads and displays the entire page with the new text value of button1: "Button1Update".

Callback

PageLifeCycle_CallBack.jpg

  1. User click on Button1: the page is not sent to the server (callback).
  2. The page life cycle begins. Button1's Click event is caught by the RaiseCallBackEvent method:
  3. C#
    public void RaiseCallbackEvent(string eventArgument)
    {
        this.Button1.Text = "ButtonUpdate1";
    }
  4. At the end of the page life cycle, the method "GetCallbackResult" generates the HTML button and then returns it to the browser for interpretation.
  5. C#
    public string GetCallbackResult()
    {
        StringWriter sw = new StringWriter();
        HtmlTextWriter tw = new HtmlTextWriter(sw);
    
        System.Reflection.MethodInfo mi = 
           typeof(Control).GetMethod("Render", 
           System.Reflection.BindingFlags.Instance | 
           System.Reflection.BindingFlags.NonPublic);
    
        mi.Invoke(control, new object[] { tw });
    
       return  string.Format("{0}|{1}", control.ClientID, sw.ToString()); 
    }
  6. The browser displays the page with the new text value of Button1: "Button1Update", without reloading the entire page.

As you can see, there is a difference in the page life cycle between a postback and a callback: In the callback, three events on the page life cycle are not fired:

  • OnPreRender: The beginning of Render.
  • OnSaveStateComplete: A recursive event which invokes the SaveViewState event for all controls of the page.
  • Render: A recursive event which invokes the Render event for all controls of the page.

Now, how can a control that uses callback mode saves its viewstate and render its HTML code? This is what we'll see now.

ICallBackEventHandler

Any control that wants to use the callback method must absolutely implement the ICallBackEventhandler interface. This interface has two methods:

C#
// Summary:
//     Processes a callback event that targets a control.
//
// Parameters:
//   eventArgument:
//     A string that represents an event argument
//     to pass to the event handler.
void RaiseCallbackEvent(string eventArgument)

// Summary:
//     Returns the results of a callback event that targets a control.
//
// Returns:
//     The result of the callback.
string GetCallbackResult()

For more details, see: ICallBackEventHandler.

ViewState

Here is an excerpt:

Microsoft ASP.NET view state, in a nutshell, is the technique used by an ASP.NET Web page to persist changes to the state of a Web Form across postbacks. In my experiences as a trainer and consultant, view state has caused the most confusion among ASP.NET developers. When creating custom server controls or doing more advanced page techniques, not having a solid grasp of what view state is and how it works can come back to bite you. Web designers who are focused on creating low-bandwidth, streamlined pages oftentimes find themselves frustrated with view state, as well. The view state of a page is, by default, placed in a hidden form field named __VIEWSTATE. This hidden form field can easily get very large, on the order of tens of kilobytes. Not only does the __VIEWSTATE form field cause slower downloads, but, whenever the user posts back the Web page, the contents of this hidden form field must be posted back in the HTTP request, thereby lengthening the request time, as well.

For more details:

WebControl

System.Web.UI.WebControls.WebControl is the base class for most of the ASP.NET 2.0 standard controls (like Button, TextBox, CheckBox, ....). All web controls must implement this class.

ControlDesigner

All ASP.NET controls have a "ControlDesigner" associated with them. This class is used by the designer of "Visual Studio" to give form to the control when you drag it to the page, and not at runtime. For example, the "Button" control has its ControlDesigner set to System.Web.UI.Design.WebControls.ButtonDesigner. We can see this attribute in the Button class.

C#
[Designer("System.Web.UI.Design.WebControls.ButtonDesigner, 
         System.Design, Version=2.0.0.0, Culture=neutral, 
         PublicKeyToken=b03f5f7f11d50a3a")]
public class Button : WebControl, IButtonControl, IPostBackEventHandler
{...}

Practice: AEButton Control

To put into practice what has been said above, will create a button control that operates in callback mode: the AEButton (Asynchronous Event Button).

Create the Control

As said earlier, any web control must inherit from the WebControl base class:

C#
public class AEButton : WebControl
{
     public AEButton(): base(HtmlTextWriterTag.Input)
    {

    }
}

The WebControl constructor supports setting a specific HTML tag; in our example, this is the tag "Input" that corresponds to a button:

HTML
<input type="button" id="Button1" value="button" />

Like any other Web Control, this control will have properties, events, and methods.

Properties

In this section, we will see the properties to set for our AEButton control. I will cite only the most important ones:

  • Text: This property is the HTML attribute value in the input tag.
  • _ViewState: This property takes the final value of the page "ViewState", which will be saved in the hidden field __VIEWSTATE.
  • Result: This property will contain the generated HTML (Render) to update the control in a page. After this, we will see how to get the HTML code generated for a given control to send to a client page.
  • IsRenderControl: When you want to update other controls, this property must be set to True.

IsRenderControl.jpg

Events

Each control has its events; in our example, we will implement the "Click" event:

  • Click
  • C#
    /// <summary>
    /// Occurs when the AEButton is clicked
    /// </summary>
    
    public event EventHandler Click
    {
        add
        {
            Events.AddHandler(EventClick, value);
        }
        remove
        {
            Events.RemoveHandler(EventClick, value);
        }
    }

    Implementation of the event is named "OnClick". The notation is "On + the name of the event".

    C#
    /// <summary>
    ///  Raises the  Click event of the AEButton controls
    /// </summary>
    /// <param name="e">
    protected virtual void OnClick(EventArgs e)
    {
        EventHandler handler = (EventHandler)Events[EventClick];
        if (handler != null)
        {
            handler(this, e);
        }
    }

    Aebutton_Click.jpg

Render Methods

As seen earlier in the page life cycle, the Render event is called for all controls on the page, so we must define and implement this event for our AEButton control. In this event, we will call a method that will generate the HTML code corresponding to an "input button", taking into account most of the properties such as Text, BackColor, Width, Height... The parameter for the Render event is an object of type HtmlTextWriter which supports the generation of HTML code for the button.

  • Render
  • C#
    /// <summary>
    /// Displays the Button on the client
    /// </summary>
    /// <param name="writer">
    protected override void Render(HtmlTextWriter writer)
    {
        this.RenderButton(writer);
    }
  • RenderButton
  • C#
    public void RenderButton(HtmlTextWriter writer)
    {
        AddAttributesToRender(writer);
        ...
        RenderInputTag(writer, clientID, onClick, text);
    }
  • RenderInputTag
  • C#
     /// Summary
    /// Render Input Tag
    /// param name="writer" 
    /// param name="clientID"
    /// param name="onClick" 
    /// param name="text" 
    internal virtual void RenderInputTag(HtmlTextWriter writer, 
                     string clientID, string onClick, string text)
    {
    
        writer.AddAttribute(HtmlTextWriterAttribute.Type, "button");
    
        if (UniqueID != null)
        {
            writer.AddAttribute(HtmlTextWriterAttribute.Name, UniqueID);
        }
        if (Text != null)
        {
            writer.AddAttribute(HtmlTextWriterAttribute.Value, Text);
        }
        if (!IsEnabled)
        {
            writer.AddAttribute(HtmlTextWriterAttribute.Disabled, "disabled");
        }
        if (Page != null && !Page.IsCallback && !Page.IsPostBack)
        {
            Page.ClientScript.RegisterForEventValidation(this.UniqueID);
        }
        if (onClick != null)
        {
            writer.AddAttribute(HtmlTextWriterAttribute.Onclick, onClick);
        }
    
        string s = AccessKey;
        if (s.Length > 0)
            writer.AddAttribute(HtmlTextWriterAttribute.Accesskey, s);
    
        int i = TabIndex;
        if (i != 0)
            writer.AddAttribute(HtmlTextWriterAttribute.Tabindex, 
                                i.ToString(NumberFormatInfo.InvariantInfo));
    
        writer.RenderBeginTag(HtmlTextWriterTag.Input);
        writer.RenderEndTag();
    }

In this section, we have seen the events and methods that generate the HTML code for our AEButton control. The work is not finished yet, let's now see how to create the designer for our AEButton control.

Create the Control Designer

The control we want to create is a button, so logically, the designer that will be created must be the same as a standard button in ASP.NET. That is why our AEButtonDesigner will inherit the class System.Web.UI.Design.WebControls.ButtonDesigner. It takes exactly the form of a standard button in ASP.NET 2.0 in an ASPX page.

C#
namespace WebGui.AEButton
{
    public class AEButtonDesigner : ButtonDesigner
    {        
        public override bool AllowResize
        {
            get
            {
                return true;
            }
        }
        public override string GetDesignTimeHtml()
        {
            // Component is the control instance, defined in the base
            // designer
            AEButton aeb = (AEButton)Component;

            if (aeb.ID != "" && aeb.ID != null)
            {
                StringWriter sw = new StringWriter();
                HtmlTextWriter tw = new HtmlTextWriter(sw);
                aeb.RenderButton(tw);
                return sw.ToString();
            }
            else
                return GetEmptyDesignTimeHtml();
        }
    }
}

The GetDesignTimeHtml method is used by the designer of Visual Studio to shape our control. You will observe that I call the RenderButton method which returns the HTML code corresponding to an "Input Button".

Here is how we specify the class attribute [Designer ...].

C#
/// <summary>
/// Represents a AEButton control
/// </summary>
[
  DataBindingHandler("System.Web.UI.Design.TextDataBindingHandler"),
  DefaultEvent("Click"),
  Designer("WebGui.AEButton.AEButtonDesigner"),
  DefaultProperty("Text"),
]

[AspNetHostingPermission(SecurityAction.LinkDemand, 
  Level = AspNetHostingPermissionLevel.Minimal)]
[AspNetHostingPermission(SecurityAction.InheritanceDemand, 
  Level = AspNetHostingPermissionLevel.Minimal)]
public class AEButton : WebControl

ControlsToolBar.jpg

Implement ICallBackEventHandler

To use callback, the AEButton control must implement the ICallBackEventHandler interface.

  • Control Load
  • In postback mode, ASP.NET generates the __doPostBack method, which is invoked by any control that supports the postback method.

    JavaScript
    function __doPostBack(eventTarget, eventArgument) {
        if (!theForm.onsubmit || (theForm.onsubmit() != false)) {
            theForm.__EVENTTARGET.value = eventTarget;
            theForm.__EVENTARGUMENT.value = eventArgument;
            theForm.submit();
        }
    }

    Except that, in the "callback", this does not exist, and so, we must do the same thing: which is why it will override the OnLoad event of the control.

    C#
    protected override void OnLoad(EventArgs e)
    {
        base.OnLoad(e);
    
        string postCommandFunctionName = 
          string.Format("PostCommand_{0}", base.ClientID);
    
        //Call Server
        string eventReference = this.Page.ClientScript.GetCallbackEventReference(
          this, "arg", "GetReponse", "context", true);
    
        //Post Command
        string postCommandScript = "\r\nfunction " + 
          postCommandFunctionName + 
          "(arg,context) {\r\n   __theFormPostCollection.length" + 
          " = 0;__theFormPostData =\"\";  WebForm_InitCallback();" + 
          eventReference + ";} \r\n";
        this.Page.ClientScript.RegisterClientScriptBlock(this.GetType(), 
          postCommandFunctionName, postCommandScript, true); 
    }

    The JavaScript code that will be generated on the page is as follows:

    JavaScript
    function PostCommand_AEButton1(arg,context) {
       __theFormPostCollection.length = 0;
    __theFormPostData ="";  WebForm_InitCallback();
    WebForm_DoCallback('AEButton1',arg,GetReponse,context,null,true);}

    The Click event of the AEButton control calls the JavaScript method "PostCommand_AEButton1()". This has been defined in the RenderButton method.

    C#
    string onClick = string.Format("PostCommand_{0}()", ClientID);
    HTML
    <input id=" onclick="PostCommand_AEButton1()" 
      value="AE Button" name="AEButton1" type="button">
  • RaiseCallBackEvent
  • The RaiseCallbackEvent method executes the callback events of a given control. So when you click the AEButton control, the method "raises the callback event", which will run this method.

    C#
    public void RaiseCallbackEvent(string eventArgument)
    {
        this.OnClick(new EventArgs());
    }
  • UpdateControl
  • The UpdateControl method calls the Render method to get the HTML of the control that has been updated. The result is put into _result. The value of _result will be sent to the browser client for interpretation using the GetCallbackResult method.

    C#
    /// summary
    /// This method must be invoked after a any updating
    // of controls properties (backcolor,width...)
    /// summary
    /// param name="control"
    public void UpdateControl(Control control) 
    {
        
        StringWriter sw = new StringWriter();
        HtmlTextWriter tw = new HtmlTextWriter(sw);
    
        System.Reflection.MethodInfo mi = 
          typeof(Control).GetMethod("Render", 
          System.Reflection.BindingFlags.Instance | 
          System.Reflection.BindingFlags.NonPublic);
        mi.Invoke(control, new object[] { tw });
    
        this._result = string.Format("{0}|{1}", 
                          control.ClientID , sw.ToString()); 
    }

    The result is as follows: "ControlID | Control Html Tag".

    C#
    protected void AEButton1_Click(object sender, EventArgs e)
    {
        this.GridView1.Rows[1].Visible = false ;
        this.AEButton1.UpdateControl(this.GridView1);
    }

    In this example, the second row of the "GridView" is hidden. After that, AEbutton1 evokes the Render of the GridView by calling the UpdateControl method.

  • GetCallBackResult
  • The GetCallbackResult method returns the result of a callback event. This means that in this method, I'll render the control which has been modified. It should not forget to save the state's control. That means, invoking the SaveViewState method for all the controls on the page is very important. Then, retrieve the ViewState of the page after editing, and save it in the hidden field, __VIEWSTATE.

    C#
    public string GetCallbackResult()
    {
        if (this._IsRenderControl)
        {
            // Save All  controls State of the page
            System.Reflection.MethodInfo mi = typeof(Page).GetMethod(
              "SaveAllState", System.Reflection.BindingFlags.Instance | 
              System.Reflection.BindingFlags.NonPublic);
            mi.Invoke(this.Page, null);
    
    
            //Get serialized viewstate from Page's ClientState
    
            System.Reflection.PropertyInfo stateProp = typeof(Page).GetProperty(
              "ClientState", System.Reflection.BindingFlags.Instance | 
              System.Reflection.BindingFlags.NonPublic);
            this._ViewState = stateProp.GetValue(this.Page, null).ToString();
    
            this._result = string.Format("{0}§{1}", 
              this._ViewState, this._result);// GetRender());
        }
    
        return this._result;
    }

    After saving to ViewState, I get its value from the property page "ClientState". I then return the final result. To retrieve and process the results returned by the callback, this JavaScript function must be added to the page:

    JavaScript
    function GetReponse(argument)
    {
        var elementId;
        var innerHtmlStr;      
        if (argument != null & argument != "")
        {
             var tableauM=argument.split("§");         
             //document.getElementById("__VIEWSTATE").value=tableauM[0];
             document.getElementById("__VIEWSTATE").setAttribute("value",tableauM[0]);
             var tableau=tableauM[1].split("|");
             elementId=tableau[0] ;
             innerHtmlStr=tableau[1] ;
             document.getElementById(elementId).outerHTML=innerHtmlStr;
        }
    }

    Do not forget to add these directives to the page:

    HTML
    EnableEventValidation ="false" 
      viewstateencryptionmode="Never" validaterequest="false">

AEButton Click Event Scenario

Image 6

Sample

In this demo, there are four controls in the page, each of them update another standard ASP.NET control:

sample.jpg

  • AEDropDownList changes the backcolor the ASP.NET button:
  • C#
    protected void AEDropDownList1_SelectedIndexChanged(object sender, EventArgs e)
    {
        this.Button1.BackColor = 
          Color.FromName(this.AEDropDownList1.SelectedItem.Value);
        this.AEDropDownList1.UpdateControl(this.Button1);
    }
  • AEbutton checks/unchecks the ASP.NET CheckBox:
  • C#
    protected void AEButton1_Click(object sender, EventArgs e)
    {
        this.CheckBox1.Checked = !this.CheckBox1.Checked;
        this.AEButton1.UpdateControl(this.CheckBox1);
    }
  • AECheckbox makes the ASP.NET TextBox read-only:
  • C#
    protected void AECheckBox1_CheckedChanged(object sender, EventArgs e)
    {
        this.TextBox1.ReadOnly = this.AECheckBox1.Checked;
        this.AECheckBox1.UpdateControl(this.TextBox1);
    }
  • AETextbox changes the Text of the ASP.NET Button:
  • C#
    protected void AETextBox1_TextChanged(object sender, EventArgs e)
    {
        this.Button2.Text = this.AETextBox1.Text;
        this.AETextBox1.UpdateControl(this.Button2);
    }

Conclusion

The four controls that I have made show the power of callback combined with JavaScript methods that can make an ASP.NET application lighter. The goal is simple: update just a specific control in a page and not the entire page. Try it with other controls like GridView!

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)