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
- User clicks on
Button1
: the entire page is sent to the server (postback). - The page life cycle begins. In the
Button1_Click
event, the Text
value of button1
is changed to Button1Update
.
protected void Button1_Click(object sender, EventArgs e)
{
this.Button1.Text = "Button1Update";
}
- At the end of the page life cycle, the HTML of the entire page is generated and sent to the client browser for interpretation.
- The browser reloads and displays the entire page with the new text value of
button1
: "Button1Update
".
Callback
- User click on
Button1
: the page is not sent to the server (callback). - The page life cycle begins.
Button1
's Click
event is caught by the RaiseCallBackEvent
method:
public void RaiseCallbackEvent(string eventArgument)
{
this.Button1.Text = "ButtonUpdate1";
}
- At the end of the page life cycle, the method "
GetCallbackResult
" generates the HTML button and then returns it to the browser for interpretation.
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());
}
- 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:
void RaiseCallbackEvent(string eventArgument)
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.
[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:
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:
<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
.
Events
Each control has its events; in our example, we will implement the "Click" event:
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".
protected virtual void OnClick(EventArgs e)
{
EventHandler handler = (EventHandler)Events[EventClick];
if (handler != null)
{
handler(this, e);
}
}
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.
protected override void Render(HtmlTextWriter writer)
{
this.RenderButton(writer);
}
RenderButton
public void RenderButton(HtmlTextWriter writer)
{
AddAttributesToRender(writer);
...
RenderInputTag(writer, clientID, onClick, text);
}
RenderInputTag
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.
namespace WebGui.AEButton
{
public class AEButtonDesigner : ButtonDesigner
{
public override bool AllowResize
{
get
{
return true;
}
}
public override string GetDesignTimeHtml()
{
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 ...]
.
[
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
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.
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.
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
string postCommandFunctionName =
string.Format("PostCommand_{0}", base.ClientID);
string eventReference = this.Page.ClientScript.GetCallbackEventReference(
this, "arg", "GetReponse", "context", true);
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:
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.
string onClick = string.Format("PostCommand_{0}()", ClientID);
<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.
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.
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".
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
.
public string GetCallbackResult()
{
if (this._IsRenderControl)
{
System.Reflection.MethodInfo mi = typeof(Page).GetMethod(
"SaveAllState", System.Reflection.BindingFlags.Instance |
System.Reflection.BindingFlags.NonPublic);
mi.Invoke(this.Page, null);
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);
}
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:
function GetReponse(argument)
{
var elementId;
var innerHtmlStr;
if (argument != null & argument != "")
{
var tableauM=argument.split("§");
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:
EnableEventValidation ="false"
viewstateencryptionmode="Never" validaterequest="false">
AEButton Click Event Scenario
Sample
In this demo, there are four controls in the page, each of them update another standard ASP.NET control:
AEDropDownList
changes the backcolor the ASP.NET button:
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
:
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:
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
:
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
!