Oct 31 2011

ClientCallback custom control for web applications By S Mewara

Category: Tutorialsabhishek.shukla @ 10:54 | |

 ClientCallback custom control for web applications  By S Mewara

TestPageImage2.JPG

Introduction

There are scenarios where we have a list of items on one side of a page (the list can be ordered or hierarchical) and on selection of an item from this list, all the details related to it is shown on the other half of the page. All this without a postback or a flicker would give a smooth and good feel to the user.

For example, we have a finance related Web application. We need to see the details related to a stock script in our portfolio (a number of scripts are listed on the same page). The moment a script is selected, all the details and the current status of the script is shown on the same page without any postback or flicker.

Out here, the above described scenario was achieved using the Client Callback feature introduced in ASP.NET 2.0. I found that a good amount of code related to this feature can be embedded inside a custom control such that it is easy to use. Moreover, if we use callback a number of times on a page, it would reduce lots of code. So, I tried to develop a "user friendly" custom control to use the Client Callback feature.

Background

In my current project, we had a scenario where different manufacturing parts were categorized (because of the hierarchical structure in the categorization, we decided to go with a tree view control). On selection of any one part (node), we were supposed to show the status and the data related to it in a grid placed on the same page. Performance was one of the major demands from the client. So, based on the needs, we decided to go with the Client Callback feature. The sample is based on this experience of mine.

Using the code

First, the important code parts of the custom control:

Apart from inheriting the control from WebControl, we also inherit it from ICallbackEventHandler.

public class MyCustomCallBackControl : WebControl, ICallbackEventHandler {}

The OnInit() method of the control is overridden. The callback scripts related to the control are embedded here.

// OnInit was overriden in order to attach a callback handler to the control
protected override void OnInit(EventArgs e)
{
     base.OnInit(e);
            
     string callback = Page.ClientScript.GetCallbackEventReference(this, "input", 
                       string.Concat(ID, "OnSuccess"), "context");
     Page.ClientScript.RegisterClientScriptBlock(typeof(MyCustomCallBackControl), 
                       ID, string.Concat("function ", ID, 
            "Callback(input, context) { ", callback, "; }"), true);
     //Above line - Script added during runtime:
     //function MyCustomCallBackControl1Callback(input, context) 
     //{      
     //    WebForm_DoCallback('MyCustomCallBackControl1',
     //          input,MyCustomCallBackControl1OnSuccess,context,null,false);      
     //}

     //General meaning of it:
     // WebForm_DoCallback(eventTarget, eventArgument, eventCallback, 
     //                    context, errorCallback, useAsync)
}

We will need to write the GetCallBackResult and RaiseCallBackEvent methods related to implement the ICallbackEventHandler interface.

// Event handler for code logic at server side on client-callback
// Event bubbling done here 
public event EventHandler MyCallBackEvent;
public void RaiseCallbackEvent(string eventArgument)
{
      argumentParameter = eventArgument;
      if (MyCallBackEvent != null)
      {
          MyCallBackEvent(this, EventArgs.Empty);
      }
}

public string GetCallbackResult()
{
      //Returns back the output set during the callback
      return renderedOutput;
}

The Client Callback custom control is now ready to use.

We will now work on how to use the control on our web page. We drag drop the control on the page, and define the code-behind handler for it, basically, the server code that will run when the callback control is called.

// Bubbled event for Callback control placed
// One can handle the operations required during the callback out here.
protected void CallBackControl_Perform(object sender, EventArgs e)
{
    DataTable dt = RetrieveDataTable(((
      MyCustomControls.MyCustomCallBackControl)sender).ArgumentParameter);
    gvTest.DataSource = dt;
    gvTest.DataBind();

    //Setting of the response output for callback
    using (System.IO.StringWriter sw = new System.IO.StringWriter())
    {
        gvTest.RenderControl(new HtmlTextWriter(sw));
        ((MyCustomControls.MyCustomCallBackControl)sender).RenderedOutput = sw.ToString();
    }
}

In our sample, we will bind the tree nodes to this callback control. This is done using JavaScript.

//Client Side callback event attached
tNode.NavigateUrl = "javascript:OnNodeClick('" + tNode.Value + "');";

We are just left with defining the JavaScript functions that are attached to the custom callback control. The functions will be bound in the OnNodeClick method defined above.

//Node Callback Click Event
function OnNodeClick(nodeID)
{         
    // Method name to call has a fixed naming convention
    // CustomCallbackControl name + "Callback" 
        // Parameters to the function would be:
            // 1st : Input 
            // 2nd : Context                     
    MyCustomCallBackControl1Callback(nodeID, null); 
}        

// Function name has a fixed naming convention
// CustomCallbackControl name + "OnSuccess"
function MyCustomCallBackControl1OnSuccess(responseText) 
{       
    // Based on responseText, action taken on client side     
    document.getElementById("tdGridView").style.display = "block";
    document.getElementById("gvTest").outerHTML = responseText;   
}

We are finished with using the control now. The naming conventions for the JavaScript functions are defined, and need to be given accordingly.

Points of interest

Performance came out really good, that too with a simple and sleek UI. It was a good experience learning the comparisons and differences between using an UpdatePanel and a callback control.

Making it a custom control helped a lot. Even a beginner can use it without knowing much of what is happening inside it (it can be used multiple times on the same page). The control was easy to use and came out to be a good weapon in my Web stock! :)

Tags:

Oct 31 2011

Client Callbacks in ASP.NET 2.0 by Karl Seguin

Category: Tutorialsabhishek.shukla @ 10:44 | |

Original Post location  :Client Callbacks in ASP.NET 2.0 by Karl Seguin

Please read Fredrik Norm�n's fabulous introduction on client-call back before reading this. This brief tutorial is simply a more realistic example of using the technology which he explains.

Introduction

One of the new features in ASP.Net 2.0 is the ability to call server-side code from client-side code. While this isn't something new, a new simpler and cleaner mechanism is now available: client callback. The general idea is to have some type of hook, client-side functions (JavaScript) can use to utilize powerful server-side functions. A key benefit to this closing the gap on desktop applications with respect to cleaner, richer and more responsive applications. Fredrik Norm�n has a great blog entry explaining how it works and how to use it. This brief tutorial is simply written to showcase a more realistic example.

 

Compatibility

Its important to note that Client Callback uses XmlHttp, which, as far as I know, only works in IE. Hopefully workarounds and third party solutions aren't far away, which'll make this a truly powerful weapon to have in our arsenal.

 

Linked Drop Downs

The example I'll be doing will be a simple example of having values in a dropdownlist linked to values in another dropdownlist. A couple such examples would be a list of states/provinces for a selected country, or a list of departments in an organization. Our example will use the organization/departments example. Without using a method like client callback, we'd either have to dump large amounts of [hard to maintain] Javascript arrays and do a lot of client-side processing, or postback each time the value in the dropdownlist changed. Each solution, while workable, had some serious drawbacks. Using JavaScript could quickly become unwieldy, posting-back can cause even the best surfers to be confused for a couple seconds.

 

Requirements

Our requirements are simple:

  1. Two dropdownlists are displayed to users
  2. The first dropdownlist contains all of the Canadian Governments Departments
  3. The 2nd dropdownlist contains all of the primary sections within the selected department
  4. A department may not have any sections
  5. We cannot use postback, must only support IE, and have to keep the Javascript to a minimum

No problem!

 

Database Structure

Keeping with our entire "simple" theme, our database structure is very simple:

The SQL script to create our table:

      CREATE TABLE dbo.Organization (
          OrganizationId int IDENTITY (1, 1) NOT NULL CONSTRAINT 
             [PK_Organization] PRIMARY KEY CLUSTERED,
          [Name] varchar (255),
          ParentId int NULL CONSTRAINT [FK_Parent_Organization] 
             FOREIGN KEY REFERENCES Organization(OrganizationId)
      ) ON [PRIMARY]
      GO
   

Basically, all our top-level departments will have a ParentId of NULL, while our sections will have a ParentId that is the same as their Departments OrganizationId. In your case you might have a many-many relationship, or your entities might not reside in the same table, in that case simply change the data-access code we'll be using, but the client callback code stays the same.

And to make this a complete example, some sample data:

insert into Organization
   VALUES ('Agriculture and Agri-Food Canada', NULL)

insert into Organization
   VALUES ('Bank of Canada', NULL)

insert into Organization
   VALUES ('Cadets Canada', NULL)

insert into Organization
   VALUES ('Canada Lands Company Limited', NULL)


insert into Organization
   VALUES ('Canadian Rural Partnership ', 1)

insert into Organization
   VALUES ('Co-operatives Secretariat', 1)

insert into Organization
   VALUES ('Fish and Seafood On-line', 1)

insert into Organization
   VALUES ('Bank Notes', 2)

insert into Organization
   VALUES ('Currency Museum', 2)

insert into Organization
   VALUES ('Old Port of Montr�al Corporation Inc.', 3)

insert into Organization
   VALUES ('Parc Downsview Park Inc.', 3)

 

Code

 

Basics

Our HTML will consist of two dropdownlist webcontrols:

   1:  <asp:DropDownList ID="ParentOrganizations" Runat="server"  />
   2:  <asp:DropDownList ID="ChildOrganizations" Runat="Server" />

For now, our Page_Load will be pretty basic:

   1:   private void Page_Load(object source, EventArgs e) {
   2:     if (!Page.IsPostBack) {
   3:        DataTable dt = GetOrganizations();
   4:        DataView dv = dt.DefaultView;
   5:        dv.RowFilter = "ParentId IS NULL";
   6:        ParentOrganizations.DataSource = dv;
   7:        ParentOrganizations.DataTextField = "Name";
   8:        ParentOrganizations.DataValueField = "OrganizationId";
   9:        ParentOrganizations.DataBind();
  10:        ParentOrganizations.Items.Insert(0, new ListItem("Select", "0"));
  11:     }
  12:  }

We get a datatable (we'll look at the GetOrganization() function next) [line: 3], get its default view [line: 4] and filter it so we only get our top-level Departments [line: 5]. We then do normal databinding stuff [line: 6-9] and add an option at the top for improved user-friendliness [line: 10].

The GetOrganizations() function is your typical DAL method:

   1:     private DataTable GetOrganizations() {
   2:        DataTable dt = (DataTable)Cache["Organizations"];
   3:        if (dt == null) {
   4:           SqlConnection connection = new SqlConnection(connectionString);
   5:           SqlCommand command = new SqlCommand(
                    "SELECT * FROM Organization ORDER BY Name", connection);
   6:           command.CommandType = CommandType.Text;
   7:   
   8:           SqlDataAdapter da = new SqlDataAdapter(command);
   9:           dt = new DataTable();
  10:           try {
  11:              connection.Open();
  12:              da.Fill(dt);
  13:              Cache.Insert("Organizations", dt, null, 
                        DateTime.Now.AddHours(6), TimeSpan.Zero);
  14:           } finally {
  15:              connection.Dispose();
  16:              command.Dispose();
  17:              da.Dispose();
  18:           }
  19:        }
  20:        return dt;
  21:     }

There's really nothing fancy here. Notice though that we get all Departments/Sections, that's why we have to filter for ParentId IS NULL in the Page_Load. If your entities were in two separate table, you could get your top level entities here, and simply create a 2nd function for your child-entities, which we'll be using shortly.

 

Client Callback

Now we'll make all the changes necessary to make this magical callback work. Again, you really have to read Fredrik's blog before going on as I'm not going to go into any details.

The first thing we do is add to our Page_Load in order to hookup and register all our Client Callback code:

   1:   private void Page_Load(object source, EventArgs e) {
   2:     if (!Page.IsPostBack) {
   3:        DataTable dt = GetOrganizations();
   4:        DataView dv = dt.DefaultView;
   5:        dv.RowFilter = "ParentId IS NULL";
   6:        ParentOrganizations.DataSource = dv;
   7:        ParentOrganizations.DataTextField = "Name";
   8:        ParentOrganizations.DataValueField = "OrganizationId";
   9:        ParentOrganizations.DataBind();
  10:        ParentOrganizations.Items.Insert(0, new ListItem("Select", "0"));
  11:   
  12:        ParentOrganizations.Attributes.Add("onchange", 
           "GetChildren(this.options[this.selectedIndex].value, 'ddl');");
  13:        string callBack = Page.GetCallbackEventReference(this, "arg", 
             "ClientCallback", "context", "ClientCallbackError");
  14:        string clientFunction = "function GetChildren(arg, context){ "
                    + callBack + "; }";
  15:        Page.ClientScript.RegisterClientScriptBlock(this.GetType(), 
                 "GetChildren", clientFunction, true);
  16:     }
  17:  }

On [line: 12] we add an onChange JavaScript event to our dropdownlist so that it calls the GetChildren() Javascript function. This is the proxy-function to our server-side callback function. Notice that the first JavaScript parameter will be the value of the selectedOption (in other words, the OrganizationId).d Next we use the Page to create the JavaScript string that'll actually do the callback [line: 13]. We wrap the string in our proxy GetChildren Javascript function [line: 14]. Finally we register the script on the page [line: 15].

Next, make sure that your page implements ICallbackEventHandler:

   1:  public partial class index_aspx : ICallbackEventHandler {
   2:  ...
   3:  }

The last thing to do in our codebehind is implement ICallbackEventHandler.RaiseCallbackEvent, which is the server-side function which handles the client-side call.

   1:     public string RaiseCallbackEvent(string eventArgument) {
   2:        int parentId;
   3:        if (Int32.TryParse(eventArgument, out parentId)) {
   4:           DataTable dt = GetOrganizations();
   5:           DataView dv = dt.DefaultView;
   6:           dv.RowFilter = "ParentId = " + parentId.ToString();
   7:           StringBuilder sb = new StringBuilder();
   8:           for (int i = 0; i < dv.Count; ++i) {
   9:              sb.Append(dv[i]["OrganizationId"]);
  10:              sb.Append("^");
  11:              sb.Append(dv[i]["Name"]);
  12:              sb.Append("|");
  13:           }
  14:           return sb.ToString();
  15:        }
  16:        return "";
  17:     }

Again, there's really nothing fancy here. All we are doing is using the organizationId which is passed as eventArgument to get all sections who have a matching ParentId [line: 6]. We loop through the matching sections [line: 8] and create a string in the format of Id^Name|Id^Name|Id^Name [line: 9-12]. Finally we return our string [line: 14]. If anything went wrong or no records were found, an empty string will be returned.

The last step is to create our Javascript function that'll handle the return of our server-side function. Back in our modified Page_Load,we said this function would be named ClientCallBack:

   1:     <script language="Javascript">
   2:     function ClientCallback(result, context){
   3:        var childOrganizations = document.forms[0].elements[
                  '<%=ChildOrganizations.UniqueID%>'];
   4:        if (!childOrganizations){
   5:           return;
   6:        }
   7:        childOrganizations.length = 0;
   8:        if (!result){
   9:           return;
  10:        }
  11:        
  12:        var rows = result.split('|'); 
  13:        for (var i = 0; i < rows.length; ++i){
  14:           var values = rows[i].split('^');
  15:           var option = document.createElement("OPTION");
  16:           option.value = values[0];
  17:           option.innerHTML = values[1];     
  18:           childOrganizations.appendChild(option);
  19:        }
  20:     }
  21:     </script>

We get a reference to our child dropdownlist [line: 3], clear out any existing values it might have had [line: 7] and split our string into id^name chunks [line: 12]. We then loop through each pair, and add a new option [line: 15], with the right text and value [line: 16, 17], to our child dropdownlist.

 

Observations

I just had a couple observations I wanted to pass along. These are possibly wrong and hopefully someone will correct me, but:

  1. Note that you are limited to a single string argument into and out of your your server-side function. If you need to pass more, like we did coming out, you'll need to serialize the data somehow (using | is obviously a highly efficient serializing algorithm ;) ).
  2. It would be really wonderful if in our codebehind RaiseCallbackEvent we could simply bind our ChildOrganization dropdownlist to our DataView and not need any JavaScript, but that obviously can't work. The idea behind Client callback is to allow you to do server-side processing without postback, not rerender parts/all of the page.
  3. As far as I can tell, RaiseCallbackEvent doesn't happen inside the same instance of the the actual request. If you set a private field during Page_Load, you won't have access to it in RaiseCallbackEvent unless its static.

 

Complete Code

Copy and paste, change the connection string and create Organization table with data The complete HTLM source is:

<%@ Page Language="C#" CompileWith="index.aspx.cs" ClassName="index_aspx" %>
<html>
<head>
   <title>Client CallBack</title>
</head>
<body>
<form runat="server">
   <asp:DropDownList ID="ParentOrganizations" Runat="server"  />
   <asp:DropDownList ID="ChildOrganizations" Runat="Server" />

   <script language="javascript">
   function ClientCallback(result, context){
      var childOrganizations = document.forms[0].elements[
            '<%=ChildOrganizations.UniqueID%>'];
      if (!childOrganizations){
         return;
      }
      childOrganizations.length = 0;
      if (!result){
         return;
      }
      
      var rows = result.split('|'); 
      for (var i = 0; i < rows.length; ++i){
         var values = rows[i].split('^');
         var option = document.createElement("OPTION");
         option.value = values[0];
         option.innerHTML = values[1];     
         childOrganizations.appendChild(option);
      }
   }
   </script>
   function ClientCallbackError(result, context){
      alert(result);
   }
</form>
</body>

</html>

The complete code file is:

using System;
using System.Data.SqlClient;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Data;
using System.Text;

public partial class index_aspx : ICallbackEventHandler {
   private const string connectionString = @"YOUR CONNECTION STRINGS";

    private void Page_Load(object source, EventArgs e) {
      if (!Page.IsPostBack) {
         DataTable dt = GetOrganizations();
         DataView dv = dt.DefaultView;
         dv.RowFilter = "ParentId IS NULL";
         ParentOrganizations.DataSource = dv;
         ParentOrganizations.DataTextField = "Name";
         ParentOrganizations.DataValueField = "OrganizationId";
         ParentOrganizations.DataBind();
         ParentOrganizations.Items.Insert(0, new ListItem("Select", "0"));

         ParentOrganizations.Attributes.Add("onchange", 
"GetChildren(this.options[this.selectedIndex].value, 'ddl');");         
         string callBack = Page.GetCallbackEventReference(this, "arg", 
"ClientCallback", "context", "ClientCallbackError");
         string clientFunction = "function GetChildren(arg, context){ " 
+ callBack + "; }";
         Page.ClientScript.RegisterClientScriptBlock(this.GetType(), 
"GetChildren", clientFunction, true);
      }
   }

   public string RaiseCallbackEvent(string eventArgument) {
      int parentId;
      if (Int32.TryParse(eventArgument, out parentId)) {
         DataTable dt = GetOrganizations();
         DataView dv = dt.DefaultView;
         dv.RowFilter = "ParentId = " + parentId.ToString();
         StringBuilder sb = new StringBuilder();
         for (int i = 0; i < dv.Count; ++i) {
            sb.Append(dv[i]["OrganizationId"]);
            sb.Append("^");
            sb.Append(dv[i]["Name"]);
            sb.Append("|");
         }
         return sb.ToString();
      }
      return "";
   }

   private DataTable GetOrganizations() {
      DataTable dt = (DataTable)Cache["Organizations"];
      if (dt == null) {
         SqlConnection connection = new SqlConnection(connectionString);
         SqlCommand command = new SqlCommand(
"SELECT * FROM Organization ORDER BY Name", connection);
         command.CommandType = CommandType.Text;

         SqlDataAdapter da = new SqlDataAdapter(command);
         dt = new DataTable();
         try {
            connection.Open();
            da.Fill(dt);
            Cache.Insert("Organizations", dt, null, 
DateTime.Now.AddHours(6), TimeSpan.Zero);
         } finally {
            connection.Dispose();
            command.Dispose();
            da.Dispose();
         }
      }
      return dt;
   }
}

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

 

Karl Seguin

Tags:

Oct 31 2011

Callback WebControls By kadaoui el mehdi

Category: Tutorialsabhishek.shukla @ 08:47 | |

Original Article is located here :  Callback WebControls by kadaoui el mehdi

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

protected void Button1_Click(object sender, EventArgs e)
{
    this.Button1.Text = "Button1Update";
}
  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. At the end of the page life cycle, the HTML of the entire page is generated and sent to the client browser for interpretation.
  4. The browser reloads and displays the entire page with the new text value of button1: "Button1Update".

Callback

PageLifeCycle_CallBack.jpg

public void RaiseCallbackEvent(string eventArgument)
{
    this.Button1.Text = "ButtonUpdate1";
}
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()); 
}
  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. At the end of the page life cycle, the method "GetCallbackResult" generates the HTML button and then returns it to the browser for interpretation.
  4. 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:

// 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.

[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.

IsRenderControl.jpg

Events

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

/// <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".

/// <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

  • Click

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.

/// <summary>
/// Displays the Button on the client
/// </summary>
/// <param name="writer">
protected override void Render(HtmlTextWriter writer)
{
    this.RenderButton(writer);
}
public void RenderButton(HtmlTextWriter writer)
{
    AddAttributesToRender(writer);
    ...
    RenderInputTag(writer, clientID, onClick, text);
}
 /// 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();
}
  • Render
  • RenderButton
  • RenderInputTag

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()
        {
            // 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 ...].

/// <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.

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);

    //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:

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">

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());
}

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.

/// 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".

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.

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)
    {
        // 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:

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:

EnableEventValidation ="false" 
  viewstateencryptionmode="Never" validaterequest="false">
  • Control Load
  • RaiseCallBackEvent
  • UpdateControl
  • GetCallBackResult

AEButton Click Event Scenario

Sample

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

sample.jpg

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

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

Tags: