Client-Side Functionality in a Server Control

In Web programming, client-side functionality is traditionally the responsibility of the Web page developer and is not encapsulated in server components. ASP.NET departs from this paradigm and enables server controls to emit client-side script that allows server controls to combine client-side processing with server-side processing.

The simplest case of client-side functionality is seen when a Web server control renders an event handler for a client-side event through the control's Attributes property. The following sample shows a control (ClientClickButton) that derives from the System.Web.UI.WebControls.Button Web server control, and provides an event handler for the client-side click event.

Note   A page developer can easily provide client-side event handlers by using the Web server control's Attributes collection. The purpose of the sample is to show how a control itself can encapsulate such functionality. To build this sample, see the instructions in Server Control Samples.

using System;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace CustomControls
{
public class ClientClickButton : Button
  {
   protected override void AddAttributesToRender(HtmlTextWriter writer) 
      {
          
      base.AddAttributesToRender(writer);   
      writer.AddAttribute("onclick", "alert('Thanks');");
    }

  }
}
[Visual Basic]

Option Explicit
Option Strict

Imports System
Imports System.Web
Imports System.Web.UI
Imports System.Web.UI.WebControls

Namespace CustomControls
   Public Class ClientClickButton
      Inherits Button
      Protected Overrides Sub AddAttributesToRender(writer As HtmlTextWriter)
         MyBase.AddAttributesToRender(writer)
         writer.AddAttribute("onClick", "alert('Thanks');")
      End Sub
   End Class 
End Namespace

The following page uses the ClientClickButton custom control. To check that the onClick attribute on ClientClickButton is rendered on the client, request the page in your browser and view the source.

<%@ Register TagPrefix="Custom" Namespace="CustomControls" Assembly = "CustomControls" %>
<html>
   <body>         
      <form  runat=server>        
       This button handles a client-side click event.             
   <Custom:ClientClickButton Id = "Button" BackColor = "Red"  runat=server/> <br> <br>                           
      </form>              
   </body>           
</html>

The preceding example is a very simple illustration of client-side functionality. ASP.NET enables a control to participate in more complex client-side scenarios such as those in the following list:

  • Providing a client-side script library.
  • Emitting script at the top or bottom of a page.
  • Ensuring that a script block appears only once on a page (even if there are multiple instances of the control on the page).
  • Enabling a control to associate an event handler with the form's client-side submit event (if the page includes a form).
  • Adding the client-side element rendered by a control to an array variable declared on the client.

These scenarios are enabled through methods exposed by the System.Web.UI.Page class and are accessible to an ASP.NET server control through its Page property. The following table lists the methods of the Page class that provide client-side functionality.

Method Description When to Use
Page.RegisterClientScriptBlock Enables a control to emit a script block (that contains inline script or specifies the location of a script file). The script block is rendered at the top of the page and is registered (using a key) with the page so that the script block is emitted only once, even if there are multiple instances of the control on the page.
Note   A script block emitted at the top of the page is called a client script block in an ASP.NET page.
Use when you want to include a script library or render a script block that contains general functions (invoked later in the page). In particular, functions that are called during the render phase need to be at the top of the page.
Page.RegisterStartupScript Enables a control to emit a script block (that contains inline script or specifies the location of a script file). The script block is rendered at the bottom of the page and is registered (using a key) with the page so that the script block is emitted only once, even if there are multiple instances of the control on the page.
Note   A script block emitted at the bottom of the page is called a startup script block on an ASP.NET page.
Use when you want to emit script that calls elements on the page or script that needs to execute on startup. Because this script is emitted at the bottom of the page, the elements it references are guaranteed to exist before the script is executed.
Page.RegisterArrayDeclaration Enables a control to add itself to a client-side array variable with a specified name. The array name is registered with the containing page (using a key) so that only one array with that name is rendered in client-side script on the page. Use when you want the element rendered by your control to belong to an array variable in client script. The array enables elements of the same type to be grouped for easy access by client script. For example, validator controls add themselves to an array named Page_Validators.
Page.RegisterOnSubmitStatement Associates an event handler with the client-side submit event of the form and registers it (using a key) with the containing page. The handler is rendered as an onSubmit attribute of the form element. Registering the handler ensures that multiple instances of the control do not emit multiple handlers. Use when you want a client-side handler to be invoked when the form is submitted.
Note   You can pass the inline script directly as an argument to this method or pass the call to an event handler. If you pass a call to an event handler, the handler must be defined independently in a script block or in a script library.
Page.IsClientScriptBlockRegistered Determines whether a client script block with the specified key is registered with the containing page. Use the return value of this method to determine whether Page.RegisterClientScriptBlock needs to be invoked.
Page.IsStartupScriptRegistered Determines whether a startup script block with the specified key is registered with the containing page. Use the return value of this method to determine whether Page.RegisterStartupScript needs to be invoked.
Page.RegisterHiddenField Enables a server control to register a hidden field on its containing page that is emitted when the page is rendered. This field can be accessed by client script; when the form is posted back to the server, the hidden field is available to the server as postback data. Use when a server control needs to send a hidden variable that can be accessed in client-side script. (This is useful when the variable cannot be rendered as an attribute of the control or when client-side processing requires a variable that is independent of the control). For an example, see Persisting Client-Side Changes in a non-Form Control.

The validator controls are the only ASP.NET server controls that fully exploit the client-side features provided by ASP.NET. Because the document object model supported by different browsers is not consistent, most server controls that ship with the .NET Framework SDK use client-side script minimally and use it at most for auto-postback. (For details about using script for postback, see Generating Client-Side Script for Postback). However, if you target a specific browser, or target browsers that comply with a specific document object model, you can use client-side script in your controls to provide a richer client experience and to limit round trips to the server when possible.

Using the Scripting Methods Exposed by Page

The following code fragment from the Base Validator Control Sample uses several of the client-side scripting methods exposed by the Page class. In the fragment, the client-side scripting method invocations are in boldface font. The initial code in the fragment defines string constants that contain the file name of the script file to include at the top of the page, a key to register a script block with the page, a script block to include at the bottom of the page, and a string that provides formatting information.

private const string ValidatorFileName = "DomValidation.js";
// The key to register a script key. In this example, the same key
// is used for registering both the script at the top of the
// page (called the client-side script block) and the script
// that is rendered at the bottom of the page (called  startup
// script).
private const string ValidatorIncludeScriptKey = "DomValidatorIncludeScript";

// The script block that is rendered at the bottom of the page.
private const string ValidatorStartupScript = @"
<script language=""javascript"">
<!--

var Page_ValidationActive = false;
if (typeof(Page_DomValidationVer) == ""undefined"")
    alert(""{0}"");
else
    ValidatorOnLoad();

function ValidatorOnSubmit() {{
    if (Page_ValidationActive) {{
        return ValidatorCommonOnSubmit();
    }}
}}

// -->
</script>
        ";
// Provides formatting information for emitting the script 
// block at the top of the page. 
private const string IncludeScriptFormat = @"
<script language=""{0}"" src=""{1}{2}""></script>";

// This method is called from OnPrerender in the
// Base Validator Control Sample.
protected void RegisterValidatorCommonScript() {
            
   string location = null;
   if (!Page.IsClientScriptBlockRegistered(ValidatorIncludeScriptKey)) {            
      // Provide the location of the script file.     
       location = Page.Request.ApplicationPath + "/script/";

      // Create client script block.
      string includeScript = String.Format(IncludeScriptFormat, "javascript",  location, ValidatorFileName);
      Page.RegisterClientScriptBlock(ValidatorIncludeScriptKey, includeScript);   
   }
                  
  if (!Page.IsStartupScriptRegistered(ValidatorIncludeScriptKey)) {     
     if (location == null) location = Page.Request.ApplicationPath + "/script/";
      // Provide error message, which is localized.
       string missingScriptMessage = "Validation script is missing '" + location + ValidatorFileName + "'";
      // Create startup script block.
        string startupScript = String.Format(ValidatorStartupScript, new object [] {missingScriptMessage,});                                     
        Page.RegisterStartupScript(ValidatorIncludeScriptKey, startupScript);
   }              
  Page.RegisterOnSubmitStatement("ValidatorOnSubmit", "return ValidatorOnSubmit();");
}
[Visual Basic]
Private Const ValidatorFileName As String = "DomValidation.js"
' The key to register a script key. In this example, the same key
' is used for registering both the script at the top of the
' page (called the client-side script block) and the script
' that is rendered at the bottom of the page (called startup
' script).
Private Const ValidatorIncludeScriptKey As String = "DomValidatorIncludeScript"

' The script block that is rendered at the bottom of the page.
Private Const ValidatorStartupScript As String = ControlChars.CrLf & _
"<script language=""javascript"">" & ControlChars.CrLf & _
"<!--" & ControlChars.CrLf & _
"var Page_ValidationActive = false;" & ControlChars.CrLf & _
"if (typeof(Page_DomValidationVer) == ""undefined"")" & ControlChars.CrLf & _
"    alert(""{0}"");" & ControlChars.CrLf & _
"else" & ControlChars.CrLf & _
"    ValidatorOnLoad();" & ControlChars.CrLf & ControlChars.CrLf & _
"function ValidatorOnSubmit() {{" & ControlChars.CrLf & _
"    if (Page_ValidationActive) {{" & ControlChars.CrLf & _
"        return ValidatorCommonOnSubmit();" & ControlChars.CrLf & _
"    }}" & ControlChars.CrLf & _
"}}" & ControlChars.CrLf & _
"// -->" & ControlChars.CrLf & _
"</script>"
' Provides formatting information for emitting the script 
' block at the top of the page. 
Private Const IncludeScriptFormat As String = ControlChars.CrLf & _
"<script language=""{0}"" src=""{1}{2}""></script>"

' This method is called from OnPrerender in the
' Base Validator Control Sample.
Protected Sub RegisterValidatorCommonScript()
   Dim location As String = Nothing
   If Not Page.IsClientScriptBlockRegistered(ValidatorIncludeScriptKey) Then
      ' Provide the location of the script file.
      ' When using a script library, deployment can be 
      ' a problem because the runtime is
      ' tied to a specific version of the script file. 
      ' This sample takes the easy way out and insists that
      ' the file be placed in the /script subdirectory 
      ' of the application.
      ' In other cases, you should place it where it
      ' can be shared by multiple applications and is placed 
      ' in a separate directory so that different versions 
      ' of a control library can run side by side.
      ' The recommended pattern is to put script files in the 
      ' path /aspnet_client/<assembly name>/<assembly version>/".
      location = Page.Request.ApplicationPath + "/script/"
      
      ' Create the client script block.
      Dim includeScript As String = [String].Format(IncludeScriptFormat, "javascript", location, ValidatorFileName)
      Page.RegisterClientScriptBlock(ValidatorIncludeScriptKey, includeScript)
   End If
   
   If Not Page.IsStartupScriptRegistered(ValidatorIncludeScriptKey) Then
      
      If location Is Nothing Then
         location = Page.Request.ApplicationPath + "/script/"
      End If 
      ' Provide an error message, which is localized.
      Dim missingScriptMessage As String = "Validation script is missing '" & location & ValidatorFileName & "'"
      
      ' Create the startup script block.
      Dim startupScript As String = [String].Format(ValidatorStartupScript, New Object() {missingScriptMessage})
      Page.RegisterStartupScript(ValidatorIncludeScriptKey, startupScript)
   End If
   
   Page.RegisterOnSubmitStatement("ValidatorOnSubmit", "return ValidatorOnSubmit();")
End Sub

The following code fragment from the Base Validator Control Sample shows how to invoke the Page.RegisterArrayDeclaration method (highlighted in boldface font in the fragment), which adds the element rendered by the control to an array variable (Page_Validators) rendered in client script. Registering the array variable with the page (using the key Page_Validators) ensures that only one variable by this name is created on the page.

// This method is called from Render in the
// Base Validator Control Sample.
protected virtual void RegisterValidatorDeclaration() {
   string element = "document.getElementById(\"" + ClientID + "\")";
   Page.RegisterArrayDeclaration("Page_Validators", element);
  }
[Visual Basic]
' This method is called from Render in the
' Base Validator Control Sample.
Protected Overridable Sub RegisterValidatorDeclaration()
   Dim element As String = "document.getElementById(""" & ClientID & """)"
   Page.RegisterArrayDeclaration("Page_Validators", element)
End Sub

Accessing a Control in Client Script

The base Control class exposes a property named ClientID, which it renders (in HTML) as the ID attribute of the element rendered. ASP.NET generates the ClientID for a control dynamically, and the ClientID of each control on a page is guaranteed to be unique. A control (that is, the element rendered by the control) can thus be accessed on the client using its ID in a document object model. A control can also use the ClientID to generate unique names for any additional elements that it might render (such as hidden fields).

Emitting the value of the ClientID into inline script (or into code in a script library) can be tricky, because the ClientID has to be inserted at the right location in a string variable. The following example uses escape characters to insert the ClientID into a string that constitutes inline script.

   string element = "document.getElementById(\"" + ClientID + "\")";
   Page.RegisterArrayDeclaration("Page_Validators", element);
[Visual Basic]
   Dim element As String = "document.getElementById(""" & ClientID & """)"
   Page.RegisterArrayDeclaration("Page_Validators", element)

You can also use the overloaded Format methods of the String class to compose client-side script that uses the ClientID. For more information about formatting, see Formatting Types.

Deploying a Script File

The script block emitted by a script-enabled control can contain inline script, or it can provide the location of a script file. Both cases are shown in the sample discussed earlier in this topic. If the block provides the location of a script file, you must deploy the script file in such a manner that it can be used from other applications and does not create versioning conflicts. In the sample, the script file is placed in a subdirectory named script in the Web application's virtual rooted directory.

      // Provides the location of the script file.    
       location = Page.Request.ApplicationPath + "/script/";
[Visual Basic]
      ' Provides the location of the script file.
      location = Page.Request.ApplicationPath & "/script/"

While this location works for the sample, the recommended location of a script file that is intended for use by other applications is as follows.

/aspnet_client/<your assembly name>/<your assembly version>/

The directory aspnet_client is a virtual rooted Web application directory that is created on your computer when you install the .NET Framework SDK or Visual Studio .NET. For example, the script files that ship with ASP.NET are in the following location.

/aspnet_client/system_web/<version of SDK installed>

If you have multiple versions of the SDK installed, you will see multiple subdirectories under aspnet_client/system_web. Because a control library is tied to a specific version of the script file, the recommended deployment pattern allows different versions of a control library to run side by side.

Note also that one aspnet_client directory is created for each Web site hosted on the computer. Generally a server hosts only one Web site. However, more than one site is permissible, and multiple sites lead to multiple copies of the aspnet_client directory.

Checking Whether to Render Script

A script-enabled control should provide its consumers the choice to toggle the rendering of client-side script. The validator Web server controls in ASP.NET expose a Boolean property named EnableClientScript that determines whether the control renders script to the client. You can define a property that provides this functionality as follows.

public bool EnableClientScript {
            get {
                object o = ViewState["EnableClientScript"];
                return((o == null) ? true : (bool)o);
            }
            set {
                ViewState["EnableClientScript"] = value;
            }
        }
[Visual Basic]
Public Property EnableClientScript() As Boolean
   Get
      Dim o As Object = ViewState("EnableClientScript")
      If o Is Nothing Then
         Return True
      Else
         Return CBool(o)
      End If
   End Get
   Set
      ViewState("EnableClientScript") = value
   End Set
End Property

Before rendering, a script-enabled control should check client capabilities and also check whether the user (page developer) has turned off scripting. These checks are generally done in the prerender and render phases. The following code fragment from the Base Validator Control Sample performs checks in the prerender phase.

private boolean renderUplevel;
protected override void OnPreRender(EventArgs e) {
            base.OnPreRender(e);
            ...            
            // Work out uplevelness now.
            renderUplevel = DetermineRenderUplevel();

            if (renderUplevel) {
                // Helper method that creates script blocks
                // and registers them with the page.
                RegisterValidatorCommonScript();
            }
        }
        // Helper method to check whether script should be rendered.
        protected virtual bool DetermineRenderUplevel() {

            // Must be on a page.
            Page page = Page;
            if (page == null || page.Request == null) {
                return false;
            }

            // Check  whether the user has turned off scripting and
            // check browser capabilities. This custom control 
            // needs the W3C DOM level 1 for control manipulation
            // and at least ECMAScript 1.2.
            return (EnableClientScript 
                        && page.Request.Browser.W3CDomVersion.Major >= 1
                        && page.Request.Browser.EcmaScriptVersion.CompareTo(new Version(1, 2)) >= 0);
        }
[Visual Basic]
Private _renderUplevel As Boolean
Protected Overrides Sub OnPreRender(e As EventArgs)
   MyBase.OnPreRender(e)
   _preRenderCalled = True
   ' Force a re-query of properties for render.
   _propertiesChecked = False
   ' Work out uplevelness now.
   _renderUplevel = DetermineRenderUplevel()
   If _renderUplevel Then
      RegisterValidatorCommonScript()
   End If
End Sub
' Helper method to check whether script should be rendered.
Protected Overridable Function DetermineRenderUplevel() As Boolean
   ' Must be on a page.
   Dim page As Page = Page
   If page Is Nothing Or page.Request Is Nothing Then
      Return False
   End If
   
   ' Check the browser capabilities. 
   ' This is how you can get automatic fallback to server-side 
   ' behavior. These validation controls need 
   ' the W3C DOM level 1 for control manipulation
   ' and need at least ECMAScript 1.2 for the 
   ' regular expressions.
   Return EnableClientScript AndAlso _
         page.Request.Browser.W3CDomVersion.Major >= 1 AndAlso _
         page.Request.Browser.EcmaScriptVersion.CompareTo(New Version(1, 2)) >= 0
End Function

For checks done in the render phase, see the Render method of the Base Validator Control Sample.

Script Rendered on the Client

To see how the script encapsulated by the control is rendered on the client, compile and deploy the samples provided in Validator Control Samples, request the page provided in Test Page for Validator Sample, and view the source for the page in your browser. You will see the HTML and script shown in the following example. (The script emitted by the controls is highlighted here in boldface font.) The page has several controls that emit script, all of which derive from the base control described in the Base Validator Control Sample. As a result, the source includes some script that is not discussed in this topic. Note that when you view the source in your browser, the application's directory name (samples in the example) will be replaced by the name of your application's virtual directory.

<HTML>
  <HEAD>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
<meta content="Microsoft Visual Studio .NET" name=GENERATOR>
<meta content=C# name=CODE_LANGUAGE>
<meta content="JavaScript (ECMAScript)" name=vs_defaultClientScript>
<meta content="Internet Explorer 3.02 / Navigator 3.0" name=vs_targetSchema>
<title>DOM Validators Test</title>
  </HEAD>
<body>
<form name="ValTest" method="post" action="valtest.aspx" language="javascript" onsubmit="return ValidatorOnSubmit();" id="ValTest">
<input type="hidden" name="__VIEWSTATE" value="dDwxOTkwOTM1MDA5O3Q8O2w8aTwxPjs+O2w8dDw7bDxpPDEzPjs+O2w8dDw7bDxpPDE+O2k8Mz47aTw1PjtpPDc+Oz47bDx0PHA8cDxsPFRleHQ7PjtsPElFOz4+Oz47Oz47dDxwPHA8bDxUZXh0Oz47bDw1LjU7Pj47Pjs7Pjt0PHA8cDxsPFRleHQ7PjtsPDEuMjs+Pjs+Ozs+O3Q8cDxwPGw8VGV4dDs+O2w8MS4wOz4+Oz47Oz47Pj47Pj47Pj47Pg==" />
   
<script language="javascript" src="/samples/script/DomValidation.js"></script>


<P><FONT face=Verdana size=5>DOM Validators Test</FONT></P>
<P>
<TABLE cellSpacing=1 cellPadding=1 width=602 border=0 height=131>
  <TR>
    <TD style="WIDTH: 82px">Name:</TD>
    <TD style="WIDTH: 164px"><input name="txtName" type="text" id="txtName" /></TD>
    <TD><span id="valRequired" controltovalidate="txtName" errormessage="Required." evaluationfunction="RequiredFieldValidatorEvaluateIsValid" initialvalue="" style="color:Red;visibility:hidden;">Required.</span></TD>
    </TR>
  <TR>
    <TD style="WIDTH: 82px">Postal&nbsp;Code:</TD>
    <TD style="WIDTH: 164px"><input name="txtPostcode" type="text" id="txtPostcode" /></TD>
    
    <TD><span id="valRegex" controltovalidate="txtPostcode" errormessage="Postcode must be 9999." evaluationfunction="RegularExpressionValidatorEvaluateIsValid" validationexpression="\d{4}" style="color:Red;visibility:hidden;">Postcode must be 9999.</span></TD>
    </TR>
    </TABLE></P>
<P>
<input type="submit" name="cmdSubmit" value="Submit" onclick="if (typeof(Page_ClientValidate) == 'function') Page_ClientValidate(); " language="javascript" id="cmdSubmit" />&nbsp;&nbsp; 
<input type="submit" name="cmdCancel" value="Cancel" id="cmdCancel" />
</P>
<div id="Panel1" style="border-color:#00C000;border-width:2px;border-style:Solid;height:55px;width:197px;">
   
<P>
Browser: <span id="lblBrowserName">IE</span><br>
Version: <span id="lblBrowserVersion">5.5</span><br>
Script Version: <span id="lblScriptVersion">1.2</span><br>
DOM Version: <span id="lblDomVersion">1.0</span><br>
Submit clicks: <span id="lblSubmitCount"> 0 </span><br>
Cancel clicks: <span id="lblCancelCount"> 0 </span><br>
</P>
</div>

<script language="javascript"><!--   var Page_Validators =  new Array(document.getElementById("valRequired"), document.getElementById("valRegex"));      // --></script><script language="javascript"><!--var Page_ValidationActive = false;if (typeof(Page_DomValidationVer) == "undefined")    alert("Validation script is missing '/samples/script/DomValidation.js'");else    ValidatorOnLoad();function ValidatorOnSubmit() {    if (Page_ValidationActive) {        return ValidatorCommonOnSubmit();    }}// --></script>

      </form>
   
  </body>
</HTML> 

See Also

Base Validator Control Sample | Validator Control Samples