Pages

Tuesday, April 28, 2009

How to import data from Excel to SQL Server

This step-by-step article demonstrates how to import data from Microsoft Excel worksheets into Microsoft SQL Server databases by using a variety of methods.


Description of the TechniqueThe samples in this article import Excel data by using:
SQL Server Data Transformation Services (DTS)
Microsoft SQL Server 2005 Integration Services (SSIS)
SQL Server linked servers
SQL Server distributed queries
ActiveX Data Objects (ADO) and the Microsoft OLE DB Provider for SQL Server
ADO and the Microsoft OLE DB Provider for Jet 4.0


Import vs. AppendThe sample SQL statements that are used in this article demonstrate Create Table queries that import Excel data into a new SQL Server table by using the SELECT...INTO...FROM syntax. You can convert these statements to Append queries by using the INSERT INTO...SELECT...FROM syntax while you continue to reference the source and destination objects as shown in these code samples.
Use DTS or SSISYou can use the SQL Server Data Transformation Services (DTS) Import Wizard or the SQL Server Import and Export Wizard to import Excel data into SQL Server tables. When you are stepping through the wizard and selecting the Excel source tables, remember that Excel object names that are appended with a dollar sign ($) represent worksheets (for example, Sheet1$), and that plain object names without the dollar sign represent Excel named ranges.
Use a Linked ServerTo simplify queries, you can configure an Excel workbook as a linked server in SQL Server. For additional information, click the article number below to view the article in the Microsoft Knowledge Base:
306397 HOWTO: Use Excel with SQL Server Linked Servers and Distributed QueriesThe following code imports the data from the Customers worksheet on the Excel linked server "EXCELLINK" into a new SQL Server table named XLImport1:

SELECT * INTO XLImport1 FROM EXCELLINK...[Customers$]

You can also execute the query against the source in a passthrough manner by using OPENQUERY as follows:

SELECT * INTO XLImport2 FROM OPENQUERY(EXCELLINK,
'SELECT * FROM [Customers$]')


Use Distributed QueriesIf you do not want to configure a persistent connection to the Excel workbook as a linked server, you can import data for a specific purpose by using the OPENDATASOURCE or the OPENROWSET function. The following code samples also import the data from the Excel Customers worksheet into new SQL Server tables:

SELECT * INTO XLImport3 FROM OPENDATASOURCE('Microsoft.Jet.OLEDB.4.0',
'Data Source=C:\test\xltest.xls;Extended Properties=Excel 8.0')...[Customers$]
SELECT * INTO XLImport4 FROM OPENROWSET('Microsoft.Jet.OLEDB.4.0',
'Excel 8.0;Database=C:\test\xltest.xls', [Customers$])
SELECT * INTO XLImport5 FROM OPENROWSET('Microsoft.Jet.OLEDB.4.0',
'Excel 8.0;Database=C:\test\xltest.xls', 'SELECT * FROM [Customers$]')


Use ADO and SQLOLEDBWhen you are connected to SQL Server in an ADO application by using Microsoft OLE DB for SQL Server (SQLOLEDB), you can use the same "distributed query" syntax from the Using Distributed Queries section to import Excel data into SQL Server.The following Visual Basic 6.0 code sample requires that you add a project reference to ActiveX Data Objects (ADO). This code sample also demonstrates how to use OPENDATASOURCE and OPENROWSET over an SQLOLEDB connection.

Dim cn As ADODB.Connection
Dim strSQL As String
Dim lngRecsAff As Long
Set cn = New ADODB.Connection
cn.Open "Provider=SQLOLEDB;Data Source=;" & _
"Initial Catalog=;User ID=;Password="
'Import by using OPENDATASOURCE.
strSQL = "SELECT * INTO XLImport6 FROM " & _
"OPENDATASOURCE('Microsoft.Jet.OLEDB.4.0', " & _
"'Data Source=C:\test\xltest.xls;" & _
"Extended Properties=Excel 8.0')...[Customers$]"
Debug.Print strSQL
cn.Execute strSQL, lngRecsAff, adExecuteNoRecords
Debug.Print "Records affected: " & lngRecsAff
'Import by using OPENROWSET and object name.
strSQL = "SELECT * INTO XLImport7 FROM " & _
"OPENROWSET('Microsoft.Jet.OLEDB.4.0', " & _
"'Excel 8.0;Database=C:\test\xltest.xls', " & _
"[Customers$])"
Debug.Print strSQL
cn.Execute strSQL, lngRecsAff, adExecuteNoRecords
Debug.Print "Records affected: " & lngRecsAff
'Import by using OPENROWSET and SELECT query.
strSQL = "SELECT * INTO XLImport8 FROM " & _
"OPENROWSET('Microsoft.Jet.OLEDB.4.0', " & _
"'Excel 8.0;Database=C:\test\xltest.xls', " & _
"'SELECT * FROM [Customers$]')"
Debug.Print strSQL
cn.Execute strSQL, lngRecsAff, adExecuteNoRecords
Debug.Print "Records affected: " & lngRecsAff
cn.Close
Set cn = Nothing


Use ADO and the Jet ProviderThe sample in the preceding section uses ADO with the SQLOLEDB Provider to connect to the destination of your Excel-to-SQL import. You can also use the OLE DB Provider for Jet 4.0 to connect to the Excel source.The Jet database engine can reference external databases in SQL statements by using a special syntax that has three different formats:
[Full path to Microsoft Access database].[Table Name]
[ISAM Name;ISAM Connection String].[Table Name]
[ODBC;ODBC Connection String].[Table Name]This section uses the third format to make an ODBC connection to the destination SQL Server database. You can use an ODBC Data Source Name (DSN) or a DSN-less connection string:

DSN:
[odbc;DSN=;UID=;PWD=]
DSN-less:
[odbc;Driver={SQL Server};Server=;Database=;
UID=;PWD=]

The following Visual Basic 6.0 code sample requires that you add a project reference to ADO. This code sample demonstrates how to import Excel data to SQL Server over an ADO connection by using the Jet 4.0 Provider.

Dim cn As ADODB.Connection
Dim strSQL As String
Dim lngRecsAff As Long
Set cn = New ADODB.Connection
cn.Open "Provider=Microsoft.Jet.OLEDB.4.0;" & _
"Data Source=C:\test\xltestt.xls;" & _
"Extended Properties=Excel 8.0"

'Import by using Jet Provider.
strSQL = "SELECT * INTO [odbc;Driver={SQL Server};" & _
"Server=;Database=;" & _
"UID=;PWD=].XLImport9 " & _
"FROM [Customers$]"
Debug.Print strSQL
cn.Execute strSQL, lngRecsAff, adExecuteNoRecords
Debug.Print "Records affected: " & lngRecsAff

cn.Close
Set cn = Nothing

You can also use this syntax, which the Jet Provider supports, to import Excel data into other Microsoft Access databases, indexed sequential access method (ISAM) ("desktop") databases, or ODBC databases.





Importing and Exporting Data

Importing data is the process of retrieving data from sources external to Microsoft® SQL Server™ (for example, an ASCII text file) and inserting it into SQL Server tables. Exporting data is the process of extracting data from an instance of SQL Server into some user-specified format (for example, copying the contents of a SQL Server table to a Microsoft Access database).

Importing data from an external data source into an instance of SQL Server is likely to be the first step you perform after setting up your database. After data has been imported into your SQL Server database, you can start to work with the database.

Importing data into an instance of SQL Server can be a one-time occurrence (for example, migrating data from another database system to an instance of SQL Server). After the initial migration is complete, the SQL Server database is used directly for all data-related tasks, rather than the original system. No further data imports are required.

Importing data can also be an ongoing task. For example, a new SQL Server database is created for executive reporting purposes, but the data resides in legacy systems updated from a large number of business applications. In this case, you can copy new or updated data from the legacy system to an instance of SQL Server on a daily or weekly basis.

Usually, exporting data is a less frequent occurrence. SQL Server provides tools and features that allow applications, such as Access or Microsoft Excel, to connect and manipulate data directly, rather than having to copy all the data from an instance of SQL Server to the tool before manipulating it. However, data may need to be exported from an instance of SQL Server regularly. In this case, the data can be exported to a text file and then read by the application. Alternatively, you can copy data on an ad hoc basis. For example, you can extract data from an instance of SQL Server into an Excel spreadsheet running on a portable computer and take the computer on a business trip.

SQL Server provides tools for importing and exporting data to and from data sources, including text files, ODBC data sources (such as Oracle databases), OLE DB data sources (such as other instances of SQL Server), ASCII text files, and Excel spreadsheets.

Additionally, SQL Server replication allows data to be distributed across an enterprise, copying data between locations and synchronizing changes automatically between different copies of data.

Output Parameters with Stored Procedure in ADO.NET

Here is a sample sproc that populates output parameters
from the Northwind Products table:

CREATE PROCEDURE CustOrderOne
@CustomerID nchar(5),
@ProductName varchar(50) output,
@Quantity int output

AS
SELECT TOP 1 @ProductName=PRODUCTNAME, @Quantity =quantity
FROM Products P, [Order Details] OD, Orders O, Customers C
WHERE C.CustomerID = @CustomerID
AND C.CustomerID = O.CustomerID AND O.OrderID = OD.OrderID AND OD.ProductID = P.ProductID

And here is an example of some C# code to return and display the output parameters:


using System;
using System.Data;
using System.Data.SqlClient;
namespace OutPutParms
{
class OutputParams
{
[STAThread]
static void Main(string[] args)
{
using( SqlConnection cn = new SqlConnection("server=(local);Database=Northwind;user id=sa;password=;"))
{
SqlCommand cmd = new SqlCommand("CustOrderOne", cn);
cmd.CommandType=CommandType.StoredProcedure ;
SqlParameter parm=new SqlParameter("@CustomerID",SqlDbType.NChar) ;
parm.Value="ALFKI";
parm.Direction =ParameterDirection.Input ;
cmd.Parameters.Add(parm);
SqlParameter parm2=new SqlParameter("@ProductName",SqlDbType.VarChar);
parm2.Size=50;
parm2.Direction=ParameterDirection.Output;
cmd.Parameters.Add(parm2);
SqlParameter parm3=new SqlParameter("@Quantity",SqlDbType.Int);
parm3.Direction=ParameterDirection.Output;
cmd.Parameters.Add(parm3);
cn.Open();
cmd.ExecuteNonQuery();
cn.Close();
Console.WriteLine(cmd.Parameters["@ProductName"].Value);
Console.WriteLine(cmd.Parameters["@Quantity"].Value.ToString());
Console.ReadLine();
}
}
}
}

Friday, April 24, 2009

Dynamic Template Columns in the ASP.NET 2.0 GridView Control

One of the nice things about ASP.NET is its depth: the sheer number of tools and techniques built into this Web application framework can be quite staggering. Recently I was involved in a project where we needed to present the results of a database query as part of an ASP.NET application, but we needed extensive control over the on-screen formatting, down to the level of controls used to present individual columns of data, CSS classes used, and more. To make matters even trickier, we didn't know until runtime what the query would be. After some discussion and experimentation among the design team, though, we decided that there was no need to buy a third-party control to handle these demands. The built-in GridView could handle all of our requirements. The key lay in understanding and using the little-known ability to add columns to the GridView dynamically using templates at runtime.

A GridView template is a class that implements the ITemplate interface. It defines the controls that will be displayed on the GridView in a column, how they will bind to data, and can have special-case code to handle headers and footers. In this article I'll show you a simplified example of building a GridView up from scratch using a template to respond to a dynamic query; the technique can be extended to cover much more complex situations.

The Template Class

Let's start with the Template class itself. This is the class that holds the code that will do the actual heavy lifting of putting controls in the DataGrid, as well as formatting them and binding them to data. It starts off with some private member variables and a constructor to set them:

// dynamically added label column
public class GridViewLabelTemplate : ITemplate
{

private DataControlRowType templateType;
private string columnName;
private string dataType;

public GridViewLabelTemplate(DataControlRowType type,
string colname, string DataType)
{
templateType = type;
columnName = colname;
dataType = DataType;
}
The next block of code gets called whenever an instance of this template is instantiated. If you think of a template as corresponding to a column in the GridView, this happens every time a header, cell, or footer of the GridView is created for that column. You can inspect the templateType member to figure out which of these is the case. Here, you want to create whatever control or controls you need to display the data. You're not limited to a single control, though for this article I'm only using one label for display. You can also do whatever you need to format the control to your liking. I'm going to grab the container for the control (which ends up being the wrapping table cell) and set its CSS style so that I can right-justify numeric columns. This method also sets up for data-binding by registering an event handler.

public void InstantiateIn(System.Web.UI.Control container)
{
DataControlFieldCell hc = null;

switch (templateType)
{
case DataControlRowType.Header:
// build the header for this column
Literal lc = new Literal();
lc.Text = "" + BreakCamelCase(columnName) + "";
container.Controls.Add(lc);
break;
case DataControlRowType.DataRow:
// build one row in this column
Label l = new Label();
switch (dataType)
{
case "DateTime":
l.CssClass = "ReportNoWrap";
break;
case "Double":
hc = (DataControlFieldCell)container;
hc.CssClass = l.CssClass = "ReportNoWrapRightJustify";
break;
case "Int16":
case "Int32":
hc = (DataControlFieldCell)container;
hc.CssClass = l.CssClass = "ReportNoWrapRightJustify";
break;
case "String":
l.CssClass = "ReportNoWrap";
break;
}
// register an event handler to perform the data binding
l.DataBinding += new EventHandler(this.l_DataBinding);
container.Controls.Add(l);
break;
default:
break;
}
}
As you'd expect, the event handler you set up for databinding gets called when data is bound to the GridView. In this case, I'm going to use this event handler to do some formatting of the bound data:

private void l_DataBinding(Object sender, EventArgs e)
{
// get the control that raised this event
Label l = (Label)sender;
// get the containing row
GridViewRow row = (GridViewRow)l.NamingContainer;
// get the raw data value and make it pretty
string RawValue =
DataBinder.Eval(row.DataItem, columnName).ToString();
switch (dataType)
{
case "DateTime":
l.Text = String.Format("{0:d}", DateTime.Parse(RawValue));
break;
case "Double":
l.Text = String.Format("{0:###,###,##0.00}",
Double.Parse(RawValue));
break;
case "Int16":
case "Int32":
l.Text = RawValue;
break;
case "String":
l.Text = RawValue;
break;
}
}
The last thing in my template class is a little helper method that's used in displaying column headers. Here I'm making an assumption about naming conventions in my database - that column names are all CamelCase, and that I'd prefer to display these on the GridView interface as individual words broken at the obvious points.

// helper method to convert CamelCaseString to Camel Case String
// by inserting spaces
private string BreakCamelCase(string CamelString)
{
string output = string.Empty;
bool SpaceAdded = true;

for (int i = 0; i < spaceadded =" false;" spaceadded =" true;">

The Test Page

To test this, I banged together a simple ASP.NET page consisting of three controls:

  • A TextBox control named txtQuery
  • A Button control named btnDisplay
  • A GridView control named grdMain. The GridView control has its AutoGenerateColumns property set to False.

The idea is simple: when the user clicks the button, I'll build a new DataTable based on whatever text is entered in the TextBox (so I'm depending on the user to enter a valid SQL query; naturally, in a production application, you'd want to do some error checking!). Then the code will walk through all of the columns of the DataTable and add one dynamic column to the GridView for each DataTable column. Here's how it looks in code:

protected void btnDisplay_Click(object sender, EventArgs e) {     // create new DataTable from user input     string connectionString =          "Data Source=localhost;Initial Catalog=AdventureWorks;"         + "Integrated Security=True";     SqlConnection conn;     conn = new SqlConnection(connectionString);     DataTable dtReport = new DataTable();     SqlCommand cmd = new SqlCommand(txtQuery.Text);     cmd.CommandType = CommandType.Text;     cmd.Connection = conn;     SqlDataAdapter da = new SqlDataAdapter();     da.SelectCommand = cmd;     da.Fill(dtReport);      // clear any existing columns     grdMain.Columns.Clear();      // walk the DataTable and add columns to the GridView     for (int i = 0; i < tf =" new" itemtemplate ="              new" headertemplate ="              new" datasource =" dtReport;" visible =" true;">

The only tricky part is the little bit of plumbing that actually hooks the template class up to the GridView. This is accomplished by creating a new TemplateField object, and telling the TemplateField what template to use for its ItemTemplate and HeaderTemplate (you can set other templates as well, such as the AlternatingItemTemplate and FooterTemplate, and they need not all point to the same template class).

Figure 1 shows the whole thing in action. This version isn't excessively pretty because I stripped it down to just the essentials, but it demonstrates enough that you should be able to add your own formatting back in when you need it.




Where Do You Go From Here?

This technique can be extremely powerful when you want to use some of the built-in services of the GridView framework (such as the overall databinding and ability to set cell foreground and background colors) and yet maintain close control over your data. While I can't share much code from our production application with you, I can indicate a couple of the areas where we pushed this technique even further. First, depending on the nature of your data, it may make sense to build special cases within your code to handle particular columns. For example, we've also implemented a hyperlink template column that accepts both text to display and a URL to link to:

public class GridViewHyperlinkTemplate : ITemplate {     private DataControlRowType templateType;     private string columnName;     private string url;     private string text;      public GridViewHyperlinkTemplate(DataControlRowType type,          string colname, string URL, string Text)     {         templateType = type;         columnName = colname;         url = URL;         text = Text;     }      public void InstantiateIn(System.Web.UI.Control container)     {         switch (templateType)         {             case DataControlRowType.Header:                 Literal lc = new Literal();                 lc.Text = "" + columnName+ "";                  container.Controls.Add(lc);                 break;             case DataControlRowType.DataRow:                 HyperLink hl = new HyperLink();                 hl.Target = "_blank";                 hl.CssClass = "ReportNoWrap";                 hl.DataBinding += new EventHandler(this.hl_DataBinding);                  container.Controls.Add(hl);                 break;             default:                 break;         }     }      private void hl_DataBinding(Object sender, EventArgs e)     {         HyperLink hl = (HyperLink)sender;         GridViewRow row = (GridViewRow)hl.NamingContainer;         hl.NavigateUrl = DataBinder.Eval(row.DataItem, url).ToString();         hl.Text = DataBinder.Eval(row.DataItem, text).ToString();     }  } 

Note that the data binding code for this template sets both the Text and the NavigateUrl of the Hyperlink control. We use this template in some cases where we can recognize patterns in the underlying SQL Server data thanks to naming conventions in our data columns:

for (int i = 0; i < columnname ="=" urlfound =" true;" tf =" new" itemtemplate ="              new" headertemplate ="              new" columnname ="=">

The other thing to note is that you may also want to get specific formatting on a row-by-row as well as a column-by-column basis. In this case, don't spend a lot of time barking up the template tree! Instead, you'll need to dig into the RowDataBound event of the GridView.

The built-in GridView with automatic column creation can probably handle 95% of your data display needs. But for the other 5%, it's nice to know that these powerful techniques exist. Microsoft's designers didn't think of everything, but in ASP.NET 2.0 they did a lot of work to expose the functionality we need to extend the basic framework, and it's certainly made life a lot easier for those of us working with Web applications.

Click here to download the code.







ASP.NET AJAX Controls and Extenders

Introduction

When you open up Visual Studio 2008 to create a project, you will notice that it has two new web templates designed specifically for building AJAX controls: ASP.NET AJAX Server Control and ASP.NET AJAX Server Control Extender. You'll also find an old friend, the ASP.NET Server Control project template.

What are the differences between the Server Control, the ASP.NET AJAX Server Control, and the ASP.NET AJAX Extender, and when should each be used?

Now, it is time to implement the onLoad handler for the control and, in doing so, fulfill the three requirements we outlined above regarding what a proactive session timeout watcher needs to be able to do. First, we said that the timeout watcher must know what the session lifespan is set to. We can retrieve this information programmatically from the Session object itself by pulling the Session object out of the HttpContext class. If the session timeout period is set through the web.config file, then this is the value that will be returned, in minutes. If it is not set, then the default value of 20 minutes will be returned. The System.Web.UI.Timer class reads time in milliseconds, however, so the Session.Timeout value needs to be converted from seconds to milliseconds using our readonly MINUTES variable.

The second requirement, that our control be aware of any resets of the session timeout, is met by putting our code in the OnLoad method. The OnLoad method will be called whenever there is a full or partial postback of the page that hosts the TimoutWatcherControl. Normally, the ASP.NET lifecycle will actually call the OnLoad handlers for each child control before it calls the OnLoad handler of the host page itself.

In consuming the TimeoutWatcherControl, our ideal user will most likely want to place the control on a MasterPage rather trying to place a new instance of the control on each individual page. In this case, just because it is good to know, the normal lifecycle appears to call OnLoad handlers in the following order:

  1. controls on the Master Page,
  2. controls in the Content Page,
  3. the Master Page, and
  4. the Content Page.

Finally, we said we want the TimeoutWatcherControl to handle timeouts and postbacks in an economical manner. This is accomplished by adding an UpdatePanel to our control, and a Timer control to that UpdatePanel. (For now, we will put in stub methods for the construction code for these two components.)

By design, when an AJAX Extensions Timer control is placed inside an UpdatePanel, it will automatically know to update that panel when it runs out of time. This works out well for us, since we want to be able change the content of the UpdatePanel when the timer determines that our session has expired.

In nesting our controls, you will notice that we do not use the UpdatePanel's Controls property. This is because the UpdatePanel content actually goes into a template object called the ContentTemplateContainer rather than the Controls property itself and, in fact, trying to add to the Controls property will generate an exception. Here is our code, so far:

Collapse

protected void SessionTimer_Tick(object sender, EventArgs e)
{
}
 
private System.Web.UI.Timer GetSessionTimer()
{
    if (null == _sessionTimer)
    {
        _sessionTimer = new System.Web.UI.Timer();
        _sessionTimer.Tick += 
            new EventHandler(SessionTimer_Tick);
        _sessionTimer.Enabled = true;
        _sessionTimer.ID = this.ID + "SessionTimeoutTimer";
    }
    return _sessionTimer;
}
 
private UpdatePanel GetTimeoutPanel()
{
    if (null == _timeoutPanel)
    {
        _timeoutPanel = new UpdatePanel();
        _timeoutPanel.ID = this.ID + "SessionTimeoutPanel";
        _timeoutPanel.UpdateMode = 
            UpdatePanelUpdateMode.Always;
    }
    return _timeoutPanel;
}

To finish up this control, we just need to handle the different cases for what should happen when we think the session has expired. Here, to simplify the code, I will once again insert some place holder calls:

Collapse

private void Redirect(string redirectPage)
{
    if (!string.IsNullOrEmpty(redirectPage))
    {
            Context.Response.Redirect(
                VirtualPathUtility.ToAbsolute(
                redirectPage));
    }
}

For the popup message, we want to create a floating DIV and inject it into our UpdatePanel content template. We also want to find a way to disable our control, since we do not want multiple popup controls to appear if the end-user is away for a long time. This is a bit involved, since we need to be able to access the current session timer in order to disable it, as well as save the fact that we have disabled the timer between postbacks, so it doesn't just turn itself on the next postback. Calling on the principle that we discussed above, that is that the page markup is actually more permanent than the code-behind page, it turns out that we can actually preserve the timer's enabled state in the page viewstate object. This ensures that if we disable the timer, it will stay disabled when the page undergoes either a partial or a full page postback.

We probably also want the timer to re-enable itself on a non-postback page initialization. Fortunately, the viewstate object comes back as a new object on non-postbacks, and since we have set the TimerEnabled property to default to true, a non-postback page view always creates an enabled timer control. This also happens to work when TimoutWatcherControl is hosted in a MasterPage, rather than a regular web form.

Collapse

public bool TimerEnabled
{
    get
    {
        object timedOut = ViewState[this.ID + "TimedOutFlag"];
        if (null == timedOut)
            return true;
        else
            return Convert.ToBoolean(timedOut);
    }
    set 
    {
        GetSessionTimer().Enabled = value;
        ViewState[this.ID + "$TimedOutFlag"] = value;
    }
}
 
private void DisableTimer()
{
    this.TimerEnabled = false;
}

To fully implement the DisableTimer() method, a final change needs to be made to our GetSessionTimer(), which was originally written to set the internal timer's Enabled property to true. Instead, we will now pull this value from the viewstate.

Collapse

//_sessionTimer.Enabled = true;
_sessionTimer.Enabled = this.TimerEnabled;

Now, we just need to retrieve the current UpdatePanel and add a floating DIV to it. We accomplish this by building a simple Panel control that is set to position: absolute and has a z-index. This Panel contains both the popup message set in the TimoutWatcherControl's markup, as well as a button to start the timer again. We start the timer again by hooking up an event handler to the floating DIV's OK button.

Collapse

void but_Click(object sender, EventArgs e)
{
    this.TimerEnabled = true;
}
 
private void BuildPopup()
{
    UpdatePanel p = GetTimeoutPanel();
    Panel popup = new Panel();
 
    AddCSSStylesToPopupPanel(popup);
 
    popup.Height = 50;
    popup.Width = 125;
    popup.Style.Add("position", "absolute");
    popup.Style.Add("z-index", "999");
 
    AddMessageToPopupPanel(popup, TimeoutMessage);
    EventHandler handlePopupButton = new EventHandler(but_Click);
    AddOKButtonToPopupPanel(popup, handlePopupButton);
    p.ContentTemplateContainer.Controls.Add(popup);
}

Finally, in order to throw a timeout event to the control's host page, we implement the OnTimeout() method.

Collapse

<div>  
    <asp:ScriptManager ID="ScriptManager1" runat="server">
    asp:ScriptManager>
    This page first loaded at .
    
    <asp:UpdatePanel ID="UpdatePanel1" 
         runat="server" UpdateMode="Conditional">
    <ContentTemplate>
    This panel refreshed at .
    <br /><asp:Button Text="Refresh Panel" ID="Button1"
        runat="server"/>
    ContentTemplate>
    asp:UpdatePanel>  
div>

The point of this test is to make sure that when a partial update occurs in the UpdatePanel, our TimeoutWatcher re-extends its internal timeout just as the session extends its timeout period. We can test out the PageRedirect option by creating a new WebForm called SessionExpired.aspx and hard-coding it as a property of the TimeoutWatcherConrol in the markup.

And, mutatis mutandis, after about two minutes from the time in the UpdatePanel, you should be redirected to the page specified in the RedirectPage parameter.

There's only one potential problem with our custom control. Since the AJAX Extensions Timer causes a postback when its counter runs down, we are, in effect, creating a new session object in order to notify the user that the old session object has expired. In some sense, we have simply re-invoked the Uncertainty Principle mentioned above. A cleaner solution would implement our various timeout events without using postbacks at all. In order to arrive at this cleaner solution, however, we will need to build an ASP.NET AJAX Server Control.

 

II. The ASP.NET AJAX Server Control

What do you get when you create a new AJAX Server Control project?

For the most part, it looks very similar to the ASP.NET Server Control, though it doesn't come with a default implementation of the Text property. Instead, you will find two new methods associated with AJAX behavior: GetScriptDescriptors() and GetScriptReferences(). I will discuss these at some length in a bit. A new AJAX Server Control project also comes with an automatic reference to the System.Web.Extensions assembly, as well as a JScript file and a resource file, both named TimeoutWatcherBehavior. You, typically, will want to rename these files, or just get rid of them and create your own. Finally, and not so obviously, your AssemblyInfo file will contain special instructions to make your script file available as a resource; these instructions need to be modified if you rename your script.

For this section of the tutorial, I need you to create a new ASP.NET AJAX Server Control project. Since we want to extend the current implementation, rather than replace it, you will want to save all the code you have written so far (everything between the class declaration and the final close bracket the class) over to the toolbox. You may also want to save the using-namespace directives from the TimeoutWatcherControl.

For simplicity, I'm going to use the same project name, SessionTimeoutTool, for this new AJAX Server Control, which requires me to remove the current project with that name and move it to a new location, before creating the new one. If you want to simply use a different project name, this is fine, be sure to remain cognizant of the minor naming discrepancies that will result from this between your code and the code I will be describing in this section of the tutorial.

The first thing we want to do in our new control project is rename all the default files: ServerControl1.cs, TimeoutWatcherBehavior.js, and TimeoutWatcherBehavior.resx. These files are named this way by default, no matter the name of your project.

Let's rename ServerControl1.cs to TimeoutWatcherAjaxControl.cs. VS generously takes care of renaming our class declaration and constructor for us. Let's also rename the JScript file as TimoutWatcherBehavior.js. Sadly, VS is a bit more miserly here, and we will have to open the JScript file and rename our methods and initializers a bit more manually. Do an Edit | Find and Replace | Quick Replace to switch all instances of "ClientControl1" with "TimeoutWatcherBehavior". You should do this for the entire project, rather than just this file, in order to make sure the cs file also gets updated with the correct JScript file reference.

Here is what the JScript file should look like after the changes, if you have built it with the SessionTimeoutTool project name. If not, it should use the name of your specific project as a namespace.

Collapse

/// 
 
Type.registerNamespace("SessionTimeoutTool");
 
SessionTimeoutTool.TimeoutWatcherBehavior = function(element) {
    SessionTimeoutTool.TimeoutWatcherBehavior.initializeBase(this, [element]);
}
 
SessionTimeoutTool.TimeoutWatcherBehavior.prototype = {
    initialize: function() {
        AjaxServerControl1.TimeoutWatcherBehavior.callBaseMethod(this, 'initialize');
        
        // Add custom initialization here
    },
    dispose: function() {        
        //Add custom dispose actions here
        SessionTimeoutTool.TimeoutWatcherBehavior.callBaseMethod(this, 'dispose');
    }
}
SessionTimeoutTool.TimeoutWatcherBehavior.registerClass(
       'SessionTimeoutTool.TimeoutWatcherBehavior', Sys.UI.Control);
 
if (typeof(Sys) !== 'undefined') Sys.Application.notifyScriptLoaded();

There are four interesting sections in this JScript file. The first is the call to Type.Namespace, a new language feature included in the ASP.NET AJAX Library to avoid name collisions and make JavaScript a bit more mature. In an AJAX control, you typically want to use your project name as your namespace.

Skipping over the body of the code (we will come back to this), there is a registerClass method. This is a method implemented by the Microsoft AJAX Library (sometimes called the AJAX Framework) which establishes your JavaScript as a class. Even more interesting than this, the registerClass parameters after the class name allow you to specify any other AJAX classes your custom class inherits. We will not be exploring this language feature in this tutorial, but it is worth noting because it demonstrates the lengths Microsoft has gone to in order to make JavaScript behave like an object-oriented language.

The last line, which begins "if (typeof(Sys) !== 'undefined')", needs to be present at the bottom of every script file. It basically just informs the ScriptManager that the file has finished being processed. It is a kludge for Safari browsers, which do not have a native way to indicate that a client-script file has completed loading.

Going back to the body of the code, the final thing worthy of note is how your JScript class is split between a main function and a prototype function. This is the prototype convention promoted by Microsoft for building AJAX behaviors. It is a bit of a mix of native JavaScript functionality, repurposed to make JavaScript behave in a more object-oriented manner, and the Microsoft AJAX Library.

Here are some cursory pointers on this programming convention:

  1. First, splitting your code between a main body and a prototype basically gives you the flexibility of the partial class model employed in C# programming. The prototype property can also be thought of as a way to extend your base code. You typically want to put your initialization code and any local variables in your main body. Any methods or properties of your object should go into the prototype.
  2. initializeBase and callBaseMethod are language enhancements of the MS AJAX Library, as are the initialize and dispose methods. Your base class, in this case, happens to be Sys.UI.Control.
  3. Class level variables are, by convention, preceded by an underscore and then a Camel-cased variable name. Keep in mind, though, that this is only a convention. There are no secrets in JavaScript, and even your class level variables are accessible by external code.
  4. Property constructors are preceded by "get_" and "set_".
  5. All methods, including your class declaration, follow the paradigm function name, colon, function declaration (e.g., myFunction: function(){}).
  6. JavaScript methods can generally be thought of as static methods. In order to support the notion of class instances, there needs to be a way to indicate that you are using a field or property or method of an instantiated object, rather than static accessors. The MS AJAX Library provides the this keyword for this purpose. In your custom code, you want to use this early, and you want to use it often.
  7. The MS AJAX Library supports delegates and event handlers. We will discuss how to implement these later in the tutorial, but it is important to be aware that they are part of your arsenal as you develop AJAX classes.
  8. Debugging: unlike in C# or VB, where you can debug simply by setting a breakpoint in your source, debugging JavaScript is a bit more involved. This is because client-script, in .NET, has to be processed in order to generate a result script. This result script, however, is not available until runtime. In order to work around this, you will need to place a call to the JavaScript debug object, like this: Sys.Debug.fail(""), somewhere early in your instantiation code in order to force a breakpoint. This will force the IDE to break in the result code as it is being processed. At that point, when you have access to the result script, you will be able to start setting breakpoints in your script, just as with any other language in Visual Studio.

Once the namespace, class name, and filename for the script file have been determined, you must also change the resource file name to match the script file: in this case, it should be renamed to TimeoutWatcherBehavior.resx. The way we are using it, the resource file basically serves as a place holder, letting the assembly know that there is a resource by that name. That name can now be used in order to set our script file up as a web resource.

Go into the properties of the .js file, and set its Build Action property to Embedded Resource.

Now, go into the project Properties folder, and open AssemblyInfo.cs for editing. It is here that we will set up metadata to make the JScript file available as a resource, and then as a web resource. As a web resource, it will be automatically referenced from a dynamic location through the ScriptResource.axd, rather than through a file location.

If you have had a chance to examine the AjaxControlToolkit, a separate assembly of custom AJAX controls and extenders, you will notice that it handles web resources by applying custom attributes to each custom control class, specifying the script resource that are coupled with the class. This is possible because the Toolkit has implemented internal code that uses reflection to automatically hook up scripts as web resources. It's all rather cool.

However, we will be writing this control without reference to third-party tools such as the Toolkit base classes. Instead, we will try to only use what Visual Studio 2008 provides.

In the AssemblyInfo file, you should find two assembly attributes referencing your JScript file. If they aren't there, you may need to add them. For the TimeoutWatcherAjaxControl, they should look something like this:

Collapse

[assembly: System.Web.UI.WebResource("SessionTimeoutTool.Common.TimeoutWatcherBehavior.js", 
                                     "text/javascript")]
[assembly: System.Web.UI.ScriptResource("SessionTimeoutTool.Common.TimeoutWatcherBehavior.js",
   "SessionTimeoutTool.Common.TimeoutWatcherBehavior", "SessionTimeoutTool.Resource")]

Even though we have added the necessary metadata to identify our script file as a Script Resource, we still need to make sure it gets instantiated. This is done back in the GetScriptReferences() method (one of the two methods we inherit from the ScriptControl base class) of our custom control.

To implement the GetScriptReferences() method, all we need to do is add the following line of code:

Collapse

yield return new ScriptReference("SessionTimeoutTool.TimeoutWatcherBehavior.js", 
             this.GetType().Assembly.FullName); 

Again, if the resource is in a subfolder, then the subfolder name will need to be included when you specify the resource name. Behind the scenes, this yield statement ensures that, at some point, a script reference like:

Collapse

<script src="/ScriptResource.axd?d=8O8TXUV..." type="text/javascript">script>

will be inserted into our web page, making our JavaScript file available to our code, though in a somewhat obfuscated (and arguably more secure) manner. This is actually all that this method is used for.

The other method of the ScriptControl base class which needs to be overridden is GetScriptDescriptors(). This method also generates code in our resultant web page. It basically generates a call to the MS AJAX Library specific $create() method, for instance:

Collapse

$create(SessionTimeoutTool.TimeoutWatcherBehavior, 
  {"interval":120000,"message":"Timed out"}, 
  null, null, $get("TimeoutWatcherControl1"));

which instantiates our JavaScript behavior class. This method is a bit more complicated, because it can be used to modify the generated $create() method by adding additional properties (such as the "interval" property in the sample above) and even give them an initial value. This simplest implementation in C# would look like this:

Collapse

ScriptControlDescriptor descriptor = 
          new ScriptControlDescriptor("SessionTimeoutTool.TimeoutWatcherBehavior", 
          this.ClientID);
yield return descriptor;

The final step in coupling our custom control with our JavaScript behavior class is to set some property values in the generated $create() method. We already know that we need to pass the interval of the session timeout to our client script, which has no other way of ascertaining this. We also will want to pass a popup message text to the client script, as well as the functionality (e.g., PopupMessage, PageRedirect) the consumer requests. All these are already available in the code we wrote previously. Finally, we want a way to indicate whether client-script will be used, or the server-side code we have already written will be used.

Drag all of our previous code, saved to the toolbar, into the TimeoutWatcherAjaxControl class. Fortunately, all of this code will work in a class derived from ScriptControl just as well as it does in one derived from WebControl.

We will now allow the user to determine whether they want to use server-side code, which starts a new empty session when the current session expires, or pure client-side code, which does not. This is accomplished with a class level variable, a new enum, and a public property:

Collapse

public enum ScriptMode
{
    ServerSide,
    ClientSide
}
 
private ScriptMode _scriptMode = ScriptMode.ServerSide;
 
public ScriptMode RunMode
{
    get { return _scriptMode; }
    set { _scriptMode = value; }
}

We should modify our OnLoad event so an UpdatePanel is only created and added to the current control if the consumer chooses the server-side option.

Collapse

if (RunMode == ScriptMode.ServerSide)
{
    //create new ajax components
    UpdatePanel timeoutPanel = GetTimeoutPanel();
    
    ...
 
    //add update panel to timeout watcher control
    this.Controls.Add(timeoutPanel);
}

In the GetScriptDescriptors() itself, we can now add the following properties to our descriptor: interval, timoutMode, redirectPage, and message. These will let our AJAX class know the session lifespan, the way the consumer wants a timeout to be handled, the page to redirect to, and the message to be shown in a popup if a popup is requested.

Collapse

protected override IEnumerable
GetScriptDescriptors()
{
    if (RunMode == ScriptMode.ClientSide)
    {
     ScriptControlDescriptor descriptor = 
        new ScriptControlDescriptor("SessionTimeoutTool.TimeoutWatcherBehavior", 
        this.ClientID);
     descriptor.AddProperty("interval", _interval);
     descriptor.AddProperty("timeoutMode", _timeoutMode);
     descriptor.AddProperty("message", _message);
     descriptor.AddProperty("redirectPage", 
        string.IsNullOrEmpty(_redirectPage) ? "" : 
        VirtualPathUtility.ToAbsolute(_redirectPage));
     yield return descriptor;
    }
}

AddProperty basically allows us to pass server values to our client-script. Keep in mind that the code here is not aware in any way of the contents of our JavaScript code. Instead, the descriptor simply provides instructions on how to emit a $create call into our web page, with our properties hard-coded into it. The emitted script is then called when all client-scripts have finished loading; it instantiates our AJAX object (by leveraging the MS AJAX Framework), and then initializes the client-side object's properties the way it has been instructed to in the descriptor object.

If you try to run this code now, however, you should get an exception message of some sort, since we still have to script these properties in our TimeoutWatcherBehavior class.

Creating properties in a JavaScript behavior class is similar to creating one in C#. The main difference is in where you place your code and the fact that you have to use the this keyword everywhere. Your class level variables go in your main JavaScript class, while your property accessors go into the prototype function. Since the $create call emitted by our custom control is looking for an "interval" property, we need to script a get_interval method and a set_interval method. Notice that "interval" is lowercase here, just as it is in the property name we are trying to map. We will need to do the same for timoutMode, redirectPage, and message. Note in the code sample below that, in the prototype definition, all methods are followed by a comma, except the last method in the prototype.

Collapse

SessionTimeoutTool.TimeoutWatcherBehavior = function(element) {
    SessionTimeoutTool.TimeoutWatcherBehavior.initializeBase(this, [element]);
    this._interval = 1000; 
    this._message = null;
    this._timeoutMode = null;
}
 
SessionTimeoutTool.TimeoutWatcherBehavior.prototype = {
    initialize: function() {
        SessionTimeoutTool.TimeoutWatcherBehavior.callBaseMethod(this
        , 'initialize');
    },
    get_interval: function() {
        return this._interval;
    },
    set_interval: function(value) {
        this._interval = value;
    }
    
    ...
}

While the new intellisense for JavaScript is generally very nice, a minor annoyance is that code in your prototype will not recognize variables in your main class. Thus, when you begin typing "this.", "_interval" is not one of the values that intellisense will suggest to you. It is a bit odd that this isn't supported in intellisense when, at the same time, this style of coding is also encouraged by Microsoft.

You may remember that timeoutMode is being passed in as an enum. In order to make it intelligible in our client-side code, we need a way to translate this value into something familiar. We know that enums are really integers beneath the covers, so we could just try to keep in mind that a timoutMode value of 0 is a page redirect, a value of 1 is a popup message, and so on.

For readability, however, we are better off scripting a client-side enum for this. Client-side enums are yet one more feature supported by the MS Atlas Library. The code for the client-side enumerator should be placed just before the "typeof(Sys) !== 'undefined'" line. I'll place the enumerator from our custom control next to the JavaScript version so you can see the similarities:

Collapse

//C#
public enum mode
{
    PageRedirect,
    PopupMessage,
    ExtendTime,
    CustomHandler
}
 
//JScript
SessionTimeoutTool.Mode = function(){};
SessionTimeoutTool.Mode.prototype = 
{
    PageRedirect: 0,
    PopupMessage: 1,
    ExtendTime: 2,
    CustomHandler: 3
}
SessionTimeoutTool.Mode.registerEnum("SessionTimeoutTool.Mode");

In order to track the session timeout in client-side code, we need to create an internal timer for our JavaScript class. To this end, add an additional instance variable to the main class, _timer:

Collapse

this._timer = null;

This will be used to handle our internal timer. We could try to handle the window.setInterval call by ourselves in order to set a timer. However, in this case, we are going to use a timer class, which I believe I originally found on Bertrand LeRoy's blog, but which can also be found in the AjaxControlToolkit.

Adding a new script file to our project and making it available to the TimeoutWatcherBehavior class only requires that we go through the same steps we did to make our behavior class into a ScriptResource.

  1. Set the script file's Build Action to "Embedded Resource".
  2. Add a resource file with the same name (i.e., Timer.resx).
  3. Tag the script file as both a WebResource and a ScriptResource in AssemblyInfo.cs.
  4. Add a yield statement for it in the GetScriptReferences() method of your custom control class so that a ScriptResource.axd reference will be created for it.

Here is the Sys.Timer code, with license, which effectively wraps the window.setInterval method:

Collapse

// (c) Copyright Microsoft Corporation.
// This source is subject to the Microsoft Permissive License.
// See http://www.microsoft.com/resources/sharedsource/
//                licensingbasics/sharedsourcelicenses.mspx.
// All other rights reserved.
 
 
///  name="MicrosoftAjax.js" />
///  name="MicrosoftAjaxTimer.js" />
///  name="MicrosoftAjaxWebForms.js" />
 
 
/////////////////////////////////////////////////////////////////////////
// Sys.Timer
 
 
Sys.Timer = function() {
    Sys.Timer.initializeBase(this);
    this._interval = 1000;
    this._enabled = false;
    this._timer = null;
}
 
Sys.Timer.prototype = {
    get_interval: function() {
        
        return this._interval;
    },
    set_interval: function(value) {
        
        if (this._interval !== value) {
            this._interval = value;
            this.raisePropertyChanged('interval');
            
            if (!this.get_isUpdating() && (this._timer !== null)) {
                this._stopTimer();
                this._startTimer();
            }
        }
    },
    
    get_enabled: function() {
        
        return this._enabled;
    },
    set_enabled: function(value) {
        
        if (value !== this.get_enabled()) {
            this._enabled = value;
            this.raisePropertyChanged('enabled');
            if (!this.get_isUpdating()) {
                if (value) {
                    this._startTimer();
                }
                else {
                    this._stopTimer();
                }
            }
        }
    },
 
    
    add_tick: function(handler) {
        
        
        this.get_events().addHandler("tick", handler);
    },
 
    remove_tick: function(handler) {
        
        
        this.get_events().removeHandler("tick", handler);
    },
 
    dispose: function() {
        this.set_enabled(false);
        this._stopTimer();
        
        Sys.Timer.callBaseMethod(this, 'dispose');
    },
    
    updated: function() {
        Sys.Timer.callBaseMethod(this, 'updated');
 
        if (this._enabled) {
            this._stopTimer();
            this._startTimer();
        }
    },
 
    _timerCallback: function() {
        var handler = this.get_events().getHandler("tick");
        if (handler) {
            handler(this, Sys.EventArgs.Empty);
        }
    },
 
    _startTimer: function() {
        this._timer = window.setInterval(Function.createDelegate(this, 
                      this._timerCallback), this._interval);
    },
 
    _stopTimer: function() {
        window.clearInterval(this._timer);
        this._timer = null;
    }
}
 
Sys.Timer.descriptor = {
    properties: [   {name: 'interval', type: Number},
                    {name: 'enabled', type: Boolean} ],
    events: [ {name: 'tick'} ]
}
 
Sys.Timer.registerClass('Sys.Timer', Sys.Component);
 
if (typeof(Sys) !== 'undefined')
    Sys.Application.notifyScriptLoaded();

Here is the metadata information in AssemblyInfo:

Collapse

[assembly: WebResource("SessionTimeoutTool.Timer.js", "text/javascript")]
[assembly: ScriptResource("SessionTimeoutTool.Timer.js",
   "SessionTimeoutTool.Timer", "SessionTimeoutTool.Resource")]

And, here is the modified GetScriptReferences() implementation. Note that we add a new yield statement for each Script Resource we want to make available.

Collapse

// Generate the script reference
protected override IEnumerable
        GetScriptReferences()
{
    if (RunMode == ScriptMode.ClientSide)
    {
        yield return new ScriptReference("SessionTimeoutTool"
        + ".TimeoutWatcherBehavior.js"
         , this.GetType().Assembly.FullName);
        yield return new ScriptReference("SessionTimeoutTool.Timer.js"
         , this.GetType().Assembly.FullName);
    }
}

The Sys.Timer class can be instantiated by using the new keyword. This should be done in the initialize method of the prototype function. Next, we need to add a handler for the tick event of the Sys.Timer object. Though the syntax is a bit different, the AJAX Library delegates work much the same way they do in C#. Use the createDelegate() of the AJAX Library Function class to create a delegate which points to a method in the TimeoutWatcherBehavior class, named tickHandler. For now, tickHandler will not be doing much, but we will fill it out in a bit. Pass this delegate to the add_tick method of our Sys.Timer object.

The next thing we want to do is to make sure our internal timer resets itself every time a page reloads. We were able to do this in C# by handling the OnLoad event. We can do something similar here using an event exposed by the MS AJAX Library. The Library exposes a function called Sys.Application.add_load which calls any delegates passed to it every time the web page does either a full or partial update. To take advantage of this, we just need to create a delegate for a class method, setTime, that will reset the timer and pass this delegate to the add_load function.

Finally, in our set_Time method, we just need to turn the timer off, reset it with the _interval value we received from our custom control, and start it running again.

Collapse

    initialize: function() {
        SessionTimeoutTool.TimeoutWatcherBehavior.callBaseMethod(this
            , 'initialize');
        //create timer
        this._timer = new Sys.Timer();
        //create timer handler           
        tickHandlerDelegate = Function.createDelegate(this, this.tickHandler);
        this._timer.add_tick(tickHandlerDelegate);
        //create onload handler
        setTime = Function.createDelegate(this,this.setTimer);
        Sys.Application.add_load(setTime);
    },
    tickHandler: function(){
    },
    setTimer:function()
    {
        if(this._timer)
        {
            this._timer.set_enabled(false);
            this._timer.set_interval(this.get_interval());
            this._timer.set_enabled(true);
        }
    },
    
    ...
    
    dispose: function() {        
        SessionTimeoutTool.TimeoutWatcherBehavior.callBaseMethod(this
        , 'dispose');
        if (this._timer) {        
            this._timer.dispose();
            this._timer = null;
        }
        $clearHandlers;
    }

To complete this section of the code, we need to make sure that when our TimoutWatcherBehavior class gets disposed, we also call the internal timer object's dispose method. Additionally, we will call $clearHandlers for safe measure. This is a generic method to disconnect all event handlers we may have hooked up inside our class.

All that remains for us to do is to handle each possible value of the timeoutMode property in our tickHandler method.

We'll start by creating a skeleton implementation for tickHandler, with some stub methods.

Collapse

     tickHandler: function(){
        if(this._timeoutMode == SessionTimeoutTool.Mode.PageRedirect)
        {
            this.pageRedirect();
            return;
        }
        if(this._timeoutMode == SessionTimeoutTool.Mode.PopupMessage)
        {
            this.popup();
            return;
        }
        if(this._timeoutMode == SessionTimeoutTool.Mode.ExtendTime)
        {
            this.extendTime();
            return;
        }
        if(this._timeoutMode == SessionTimeoutTool.Mode.CustomHandler)
        {
            this.customHandler();
            return;
        }  
    },

Both the pageRedirect and popup methods are pretty easy to implement, especially since we will simply be using a JavaScript alert to handle the popup request. Just be sure to disable the timer before the popup, otherwise we will end up getting multiple alert windows.

Collapse

    pageRedirect: function(){
        window.location = this._redirectPage;
    },
    popup: function(){
        this._timer._stopTimer();
        if(this._message == null)
        {
            alert("The session has expired.");
        }
        else
        {
            alert(this._message);
        }
    },

The astute reader will remark that in this section of the tutorial, we have not actually used any real AJAX functionality, up to this point. "Real" AJAX involves using the XMLHttpRequest object to interact with the server from JavaScript. Even following the broader definition of AJAX as implemented in the AJAXControlToolkit, that is, either communication with Server objects or manipulation of the page DOM, we have still failed to do anything particularly AJAXesque. So far, we have only encapsulated JavaScript in a Server Control and managed to pass some information obliquely from the server to our client-script.

In order to implement the extendTime method, however, we will need to talk to the Server. The technique we employ next, moreover, should serve as a template for any real AJAX you might want to do in your own projects.

The basic solution is pretty simple. The MS AJAX Library supports calls to web services from JavaScript. So, all we need to do is create a web service that extends the session by writing a value to it.

In your control project, add a reference to the assembly System.Web.Services. Next, we will want to add a web service to the project. Unfortunately, the template for a web service does not show up under an AJAX Server Control project, so you will need to create it for the test project, if you have it open, and drag it over to the control project. You can use the default name for this service (for reasons to be explained later). Make sure the service is decorated with the ScriptService attribute, which allows it to be called from a client-script. Create a method to extend the session, called ExtendSessionTimeout, and be sure to mark it as a service that requires access to the current session, by marking it with this tag: WebMethod(EnableSession = true). The web service should end up looking something like this:

Collapse

using System.ComponentModel;
using System.Web.Services;
using System.Web.Services.Protocols;
 
namespace SessionTimeoutTool
{
    [WebService(Namespace = "http://tempuri.org/")]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    [ToolboxItem(false)]
    [System.Web.Script.Services.ScriptService]
    public class WebService1 : System.Web.Services.WebService
    {
 
        [WebMethod(EnableSession = true)]
        public void ExtendSessionTimeout()
        {
            Session["extendTimeout"]=true;
        }
    }
}

The standard way to use ASP.NET AJAX to call a web service requires adding a reference to our service in the ScriptManager. This is not very useful for us, since the user's ScriptManager is in a different assembly, and we do not have access to it. Fortunately, ASP.NET AJAX also provides the Sys.Net.WebServiceProxy.invoke method, which allows us to make web service calls without needing such a reference. The call to our service would look like this:

Collapse

var webRequest = Sys.Net.WebServiceProxy.invoke("WebService1.asmx"
, "ExtendSessionTimeout", false, null
, null, null, "User Context");

But here's the rub. In .NET, web services are implemented using *.asmx pages that reference underlying web service classes. The AJAX function that calls the code is run in the assembly that implements our custom control. The WebService1.asmx that the AJAX function calls, however, does not exist in that assembly. If we try to run our code as it stands now, we will throw an exception since the file cannot be found. There is also no way to set up an asmx page as a resource file in order to expose it in the client assembly.

One solution would be to make the consumer of our control implement the WebService1.asmx in their own project. While this would work, it is rather un-cool. It would be preferable to have a self-contained AJAX Server Control that is able to find its own internal web service no matter where it is used.

HttpHandlers offer a less invasive solution. Implementing an HttpHandlerFactory requires the control consumer to make a small modification of his web.config file, but this is still preferable to forcing him to implement a whole class to our specifications. The purpose of an HttpHandlerFactory is to provide instructions on how certain file extensions are handled by the web server. In effect, they can be configured in the web.config file to provide an alias that can then be mapped to a class or web object which we specify in our HttpHandlerFactory. The trick, then, is to find a way to implement an HttpHandlerFactory to return our internal web service when it is called using JavaScript from any client of our custom control.

First, let's set up our TimeoutWatcherBehavior class to call this alias. We can write our extendTime function like this:

Collapse

    extendTime: function(){
        this.callWebService();
    },
    callWebService: function()
    {
        var webRequest = Sys.Net.WebServiceProxy.invoke(
        "SessionTimeoutTool.asmx" //path
        , "ExtendSessionTimeout" //methodName
        , false //useHttpGet
        , null //parameters 
        , this.succeededCallback
        , this.failedCallback
        , "User Context");//userContext 
    },
    succeededCallback: function(result, eventArgs)
    {
        if(result !== null)
            alert(result);
    },
    failedCallback: function(error)
    {
        alert(error);
    },

Specifications for this AJAX Library function can be found here. Even though our web method does not return a value, I have included stub methods for the callback functions, for reference. If you do not need callbacks, the success and fail parameters can be null. There is also a final optional parameter, which I have left out of the reference code above, that sets a timeout for the call. The AJAX Library documentation says that it can be set to null, but this actually causes an exception to be thrown. If you do not need a timeout, you should just leave off the parameter.

The project that consumes our control will need to include an HttpHandler element for our web service alias. Whenever the web server receives a call to our alias, "SessionTimeoutTool.asmx", from an assembly that references our TimeoutWatcherAjaxControl, we will redirect the call to an HttpHandlerFactory called "SessionTimeoutTool.SessionTimeoutHandlerFactory" (which we have yet to write).

Collapse

  <system.web>
    <httpHandlers>
      <add verb="*" path="SessionTimeoutTool.asmx" 
           type="SessionTimeoutTool.SessionTimeoutHandlerFactory" 
           validate="false"/>
      ...
    httpHandlers>
  system.web>

Writing our HttpHandlerFactory class requires a bit of black magic. Microsoft message boards are full of entries by Microsoft employees saying that you simply cannot call a web service in one project from another project. This isn't true, of course, but it is tricky. Hugo Batista offers a solution in his blog. Unfortunately, this solution was obviated when, in .NET 3.5, the WebServiceHandlerFactory was replaced with the ScriptHandlerFactory as the main class for handling calls to files with an asmx extension. The ScriptHandlerFactory, which is found in the System.Web.Extensions assembly, delegates regular web service calls to the WebServiceHandlerFactory. Calls to web services from JavaScript, however, are implemented through the RestHandlerFactory. The RestHandlerFactory, moreover, allows us to pass a type definition for our web service, rather than requiring us to pass a path to an *.asmx page.

There is one additional element of complexity, however. All the HttpHandlerFactory classes in System.Web.Extensions are scoped internal. In order to use classes, consequently, we have to use some Reflection. A solution for doing all this is provided by Robertjan Tuit in a CodeProject article. His solution is simply brilliant, but apparently under-appreciated. I highly encourage you to give him your fives. I had to read through several Chinese hacker sites using Google translator to even figure out what exactly he was doing. Basically, he has used a reflection tool to peer into the ScriptHandlerFactory in order to figure out what it is doing, and re-implemented the whole thing in order to pass a web service type reference to it rather than an *.asmx file path.

I have streamlined his code a bit, since it is intended only to handle web service calls from JavaScript, and only for one specific web service. I've also added a little additional code, based on Reflection on the RestHandlerFactory implementation, in order to pass session information.

The implementation is pretty generic, and copy-and-paste ready. You will be able to re-use it, as-is, in your future projects. The only thing that ever changes is the value of the web service class, which is set in the webServiceType variable. Here is the complete code to be added to the SessionTimeoutTool project:

Collapse

using System;
using System.Collections.Generic;
using System.Text;
using System.Web;
using System.Reflection;
using System.Web.Services.Protocols;
using System.Web.Script.Services;
using System.Web.SessionState;
 
namespace SessionTimeoutTool
{
    class SessionTimeoutHandlerFactory: IHttpHandlerFactory
    {
        #region IHttpHandlerFactory Members
 
        IHttpHandlerFactory factory = null;
        Type webServiceType = typeof(WebService1);
 
        public IHttpHandler GetHandler(HttpContext context, string requestType
            , string url, string pathTranslated)
        {
            Assembly ajaxAssembly = typeof(GenerateScriptTypeAttribute).Assembly;
 
            factory = (IHttpHandlerFactory)System.Activator.CreateInstance(
                       ajaxAssembly.GetType(
                       "System.Web.Script.Services.RestHandlerFactory"));
 
            IHttpHandler restHandler = (IHttpHandler)System.Activator.CreateInstance(
                ajaxAssembly.GetType("System.Web.Script.Services.RestHandler"));
 
            ConstructorInfo WebServiceDataConstructor = ajaxAssembly.GetType(
                "System.Web.Script.Services.WebServiceData").GetConstructor(
                BindingFlags.NonPublic | BindingFlags.Instance
                , null, new Type[] { typeof(Type), typeof(bool) }, null);
 
            MethodInfo CreateHandlerMethod = restHandler.GetType().GetMethod(
                "CreateHandler", BindingFlags.NonPublic | BindingFlags.Static, 
                null, new Type[] { ajaxAssembly.GetType(
                "System.Web.Script.Services.WebServiceData"), 
                typeof(string) }, null);
 
            IHttpHandler originalHandler = 
             (IHttpHandler)CreateHandlerMethod.Invoke(restHandler, 
             new Object[]{ WebServiceDataConstructor.Invoke(
             new object[] { webServiceType, false }), 
             context.Request.PathInfo.Substring(1)
            });
            Type t = ajaxAssembly.GetType(
               "System.Web.Script.Services.ScriptHandlerFactory");
            Type wrapperType = null;
            if (originalHandler is IRequiresSessionState)
                wrapperType = t.GetNestedType("HandlerWrapperWithSession"
                    , BindingFlags.NonPublic | BindingFlags.Instance);
            else
                wrapperType = t.GetNestedType("HandlerWrapper"
                    , BindingFlags.NonPublic | BindingFlags.Instance);
 
            return (IHttpHandler)System.Activator.CreateInstance(
                wrapperType, BindingFlags.NonPublic | BindingFlags.Instance
                , null, new object[] { originalHandler, factory }, null);
        }
 
        public void ReleaseHandler(IHttpHandler handler)
        {
            factory.ReleaseHandler(handler);
        }
 
        #endregion       
    }
}

The last thing we need to do is to make sure that our code does not wait until the session has already expired before calling the extendTime method. As with our TimeoutWatcherControl, we will configure the TimeoutWatcherAjaxControl to set the internal timer to fire off 45 seconds early when the ExtendTime option is selected. We will do this in the setTimer method of our behavior class.

Collapse

setTimer:function()
{
    if(this._timer)
    {
        this._timer.set_enabled(false);
        if(this._timeoutMode == SessionTimeoutTool.Mode.ExtendTime)
            this._timer.set_interval(this.get_interval()- 45000);
        else
            this._timer.set_interval(this.get_interval());
        this._timer.set_enabled(true);
    }

Inside our test project, we can test this functionality using the same markup we used in order to test the page redirection option. This markup for our custom control will look like this:

Collapse

<cc1:TimeoutWatcherAjaxControl 
    ID="TimeoutWatcherAjaxControl1" 
    TimeoutMode="ExtendTime" 
    RunMode="ClientSide" 
    runat="server" />

We will also add a third UpdatePanel to the test page that will check to see if the session is still alive by checking for a variable we add to the Session object. When the variable does not exist, which is the case on the first page hit, and if the session expires, the panel will tell us that the session is new; otherwise, it will return false.

Collapse

<asp:UpdatePanel ID="UpdatePanel2" runat="server">
<ContentTemplate>
<div style="border: medium solid Yellow; padding: 5px; width:400px;">
At  
this session is brand new: 
.
     <br /><asp:Button Text="Check Session" ID="Button2" runat="server"/>
div>
ContentTemplate>
asp:UpdatePanel>

Clicking the "Check Session" button will extend the session, since it causes a partial postback, so be sure to wait a good two minutes (or whatever length you have set your session timeout to) after the last page update before clicking it.

check session state

The technique described above is key to creating an AJAX control that can call server-side code from client-side code. As far as I know, Microsoft has not provided any other way to build true AJAX functionality into a server control, which is a shame. But, at least, we have a work-around.

We still need to script the customHandler method. There are several ways of doing this. One option is to expose additional properties in our server control to pass a web service and method name. We could then use the WebServiceProxy.invoke method described above to call this web service and run our user's code.

This type of functionality is already available through the ServerModeTimeout event, however, and there is no sense in simply finding a different way to do the same thing here. Instead, we will expose a new property that allows the user to pass custom JavaScript to our control, and we will execute it when the session expires.

Add a new property called CustomHandlerJScript to the TimeoutWatcherAjaxControl C# class.

Collapse

private string _customHandlerJScript;
 
public string CustomHandlerJScript
{
    get { return _customHandlerJScript; }
    set { _customHandlerJScript = value; }
}

Pass this to the TimeoutWatcherBehavior JavaScript class in the GetScriptDescriptors method.

Collapse

descriptor.AddProperty("customHandlerJScript", _customHandlerJScript);

Now, in our TimeoutWatcherBehavior, add an accessor to receive this value:

Collapse

SessionTimeoutTool.TimeoutWatcherBehavior = function(element) {
    ...
    this._customHandlerJScript = null;
}
SessionTimeoutTool.TimeoutWatcherBehavior.prototype = {
    ...
    get_customHandlerJScript:function()
    {
        return this._customHandlerJScript;
    },
    set_customHandlerJScript:function(value)
    {
        this._customHandlerJScript = value;
    },
    ...
}

Inside the customHandler function, we will simply use the classic JavaScript function eval to execute the script passed in by the control's consumer.

Collapse

    customHandler: function(){
        this._timer._stopTimer();
        eval(this.get_customHandlerJScript());
    },

To test this functionality, the markup for our TimeoutWatcherAJAXControl should look like this:

Collapse

<cc1:TimeoutWatcherAjaxControl 
    ID="TimeoutWatcherAjaxControl1" 
    TimeoutMode="CustomHandler" 
    RunMode="ClientSide"
    CustomHandlerJScript="alert('this is the custom handler');"
    runat="server" />

and if all goes well, after about two minutes, you should see this:

customhandler

Our ASP.NET AJAX Server Control is complete.

III. The ASP.NET AJAX Server Control Extender

As is indicated by its name, the ASP.NET AJAX Server Control Extender is just the AJAX Server Control plus a little something extra. You will recall that the class declaration for our AJAX behavior class takes a parameter called element. Though we did not discuss this, the element passed in can be accessed throughout our JavaScript code with a call to this.get_element(). The value of the element parameter, in turn, is passed to the behavior class inside our server control's GetScriptDescriptors method. It is the second parameter, there, of ScriptControlDescriptor's constructor.

Collapse

ScriptControlDescriptor descriptor = new ScriptControlDescriptor(
    "SessionTimeoutTool.TimeoutWatcherBehavior", this.ClientID);

In the AJAX Server Control, we simply pass the ID of the custom control, and the get_element() function in our behavior class uses this to get a DOM element. In an AJAX Server Control Extension, however, we pass the ID of another control on our web page.

In an Extension Control, we can then use this id to hook into the DOM element for another page control and add hook into its methods to provide custom behaviors. In effect, this gives us two different ways to add AJAX functionality to a server-side control. We can use the AJAX Server Control Template and implement, for instance, a TextBox control with some JavaScript attached. Alternatively, we can create a standalone set of behaviors that are then attached to a regular TextBox control.

This is, essentially, the only important difference between an AJAX Server Control and an AJAX Server Control Extension: whether the JavaScript associated with a custom control applies to itself or applies to an external control. The Extender model, however, is much more flexible, since with it, you can go into a pre-existing application and simply start adding behaviors to your pre-existing controls, rather than having to start replacing each of them with your own AJAX-customized control. Extensions also have the added benefit of allowing you to add multiple behaviors, from multiple Server Control Extensions, to a single control. You might think of this as a way to allow any control to inherit from multiple base classes, whereas the AJAX Server Control only allows you to inherit from one.

The Server Control we built above had a rather humble implementation of the popup functionality. What would be much more cool is if we allowed the user to point to an external panel, and in our implementation, we turned it into a floating DIV. We can do this by turning our Server Control into an Extension Control.

There are two ways to go about creating our TimeoutWatcherAjaxControlExtension. We could do what we did above and create an entirely new project based on the ASP.NET AJAX Server Control Extension template, then copy all of our code over to it. But the differences between the regular AJAX Control and the AJAX Extender are rather minor, so I'm going to opt for simply creating a new class based on the TimoutWatcherAJAXControl and just making a few adjustments to it. This will obviate our having to copy all the *.js files and AssemblyInfo settings into the new project (though you can certainly choose to do this, if you like).

Doing it my way, simply create a new class file called TimeoutWatcherAjaxControlExtender.cs. Copy all of the AJAX Control code we wrote above into it. The Extension Control inherits from ExtenderControl rather than ScriptControl, so we will need to make that change. Also, the class declaration takes an attribute that specifies what kind of control we intend to extend. In our case, this will be a Panel control. I have commented out the original class declaration so you can see the differences.

Collapse

//public class TimeoutWatcherAjaxControl : ScriptControl
[TargetControlType(typeof(Panel))]
public class TimeoutWatcherAjaxControlExtender: ExtenderControl
{

Next, the GetScriptDescriptors method has a different signature in an Extender class. It takes a control as a parameter. When we create a new ScriptBehaviorDescriptor object in our method implementation, we simply need to pass the id of our target control rather than the ID of the Server Control.

Collapse

protected override IEnumerable
//GetScriptDescriptors() -- old  
GetScriptDescriptors(Control targetControl)
{
    if (RunMode == ScriptMode.ClientSide)
    {
        ScriptControlDescriptor descriptor = 
            new ScriptControlDescriptor("SessionTimeoutTool."
                + "TimeoutWatcherBehavior", targetControl.ClientID);
        ...

Those are the only changes we really need to make. JavaScript behavior classes have the same structure whether you are building a Server Control or an Extender, so we can actually just reuse the class we wrote in the previous section. Because we inherit from the ExtenderControl class, our custom Extender also automatically exposes a new property called TargetControlID, which the Extender Control's consumer will use in his markup to identify the Panel control that will be turned into a floating DIV.

Normally, you would now use the get_element() function inside your JavaScript prototype to hook into the Panel's properties and events in order to add new behaviors. You would combine it with the AJAX Library $addHandlers method to capture a DOM event and then pass it your own custom function, like this:

Collapse

ControlNamespace.ClientControl1.prototype = {
    initialize: function() {
        $addHandlers(this.get_element(), 
                 { 'click' : this._onClick,
    },
    _onClick: function()
    {
        alert("clicked");
    },

The AJAX Control Toolkit already contains a really good Popup Extender JavaScript class, however, so we will take a shortcut and use that behavior class rather than trying to script up our own. Additionally, it will afford us an opportunity to see how to pull out JavaScript classes from third-party assemblies and use them in our own Control Extenders.

To use the Toolkit, we will need to add the ACT assembly to our bin directory and then add a reference to it. You can get the assembly either from the sample project for this tutorial, or by downloading it from the Microsoft website.

We do not need to add any entries to the AssemblyInfo class in order to use ACT scripts, since they are already tagged as resources in the ACT assembly. All we need to do is to make sure they get instantiated as *.axd resources, and are accessible through the ScriptResource.axd path. In the GetScriptReferences method, add three additional yield statements in order to make the ACT's PopupBehavior class accessible. One is for the PopupExtender itself, while the other two are for some base classes that the PopupBehavior requires in order to run properly.

Collapse

yield return new ScriptReference("AjaxControlToolkit"+ 
             ".ExtenderBase.BaseScripts.js", "AjaxControlToolkit");
yield return new ScriptReference("AjaxControlToolkit" + 
             ".Common.Common.js", "AjaxControlToolkit");
yield return new ScriptReference("AjaxControlToolkit" + 
             ".PopupExtender.PopupBehavior.js", "AjaxControlToolkit");

We can now instantiate the PopupBehavior class from our own custom JavaScript behavior class. Add a new variable called this._popupBehavior to the main class. Then, in the prototype's initialize routine, set it to a new PopupBehavior instance by using the AJAX Library $create function.

Collapse

this._popupBehavior = $create(AjaxControlToolkit.PopupBehavior
        , {"id":this.get_id()+'PopupBehavior'}
        , null
        , null
        , this.get_element());

Our popup can now be rewritten so that all it does is to turn off the internal timer and call the PopupBehavior class' show() method.

Collapse

popup: function(){
        this._timer._stopTimer();
        this._popupBehavior.show();
        },

This control can be tested by adding a Panel control to a web form and setting its id as the TargetControlID of the Extender in markup.

Collapse

    <asp:Panel ID="timeoutPanel" runat="server" 
    style="display:none; 
        text-align: center; width:200px; background-color:White; 
        border-width:2px; border-color:Black; border-style:solid; 
        padding:20px;">
    This session timed out.
    <br /><br />
    <center>
    <asp:Button ID="ButtonOk" runat="server" Text="OK" />
    center>
    asp:Panel>
        <cc1:TimeoutWatcherAjaxControlExtender
        TargetControlID="timeoutPanel"
         ID="TimeoutWatcherAjaxControlExtender1"
         TimeoutMode="PopupMessage"
         RunMode="ClientSide"
         runat="server" />

One warning, however. An Extender Control requires that a TargetControlID be set, whether it is used in your control or not. It must also be of the type specified in the TargetControlType attribute used to decorate the class declaration. This means that a user of our Extender will have to set up a dummy Panel for the TargetControlID if they want to use any of the functionality other than a PopupMessage, which isn't particularly graceful. To make things a little more convenient, if still not perfect, I'm going to change the TargetControlType attribute to a Control instead of a Panel, which at least will give the consumer the ability to point to any control on the page when the Popup option isn't selected.

This completes our Extender control, and the last section of this tutorial. It is my hope that this tutorial has given you the skills and insights required to build your own advanced controls.

JavaScript is always going to be hairy, and the various attempts to clean it up and make it more OOP-like occasionally resemble like slapping lipstick on a pig. However, it really is much cleaner than it used to be, and with the help of the Visual Studio 2008 AJAX Server Control and AJAX Extender templates, we now have the option of hiding much of this code inside custom controls, so that developers who have no interest in client-scripting need never look at it, but can still benefit from it.

The last thing you may want to know, at the end of this rather long tutorial, is how to add an icon for your control to the Toolbox. You actually just need to add a bitmap or icon file to your project and set its Build Action to "Embedded Resource". Then, add these two Toolbox attributes to your class declaration, placing your class name where it is required. In this example, I am using a Catbert icon.

Collapse

[TargetControlType(typeof(Control))]
[System.Drawing.ToolboxBitmap(typeof(TimeoutWatcherAjaxControlExtender)
    , "Catbert.ico")]
[ToolboxData("<{0}:TimeoutWatcherAjaxControlExtender runat="server">
    ")]
public class TimeoutWatcherAjaxControlExtender: ExtenderControl
{...}

The icon will not show up if your control is referenced as a project reference. It will only show up if you compile your control and then reference its assembly.