Click here to Skip to main content
15,881,559 members
Articles / Web Development / IIS

Implement Script Callback Framework in ASP.NET 1.x

Rate me:
Please Sign up or sign in to vote.
4.84/5 (56 votes)
2 Aug 20048 min read 468.4K   4.2K   122  
It allows calls to server events from client script code without causing the page to post back and refresh.
using System;
using System.IO;
using System.Web;
using System.Web.UI;
using System.Text;
using System.Collections;
using System.Collections.Specialized;


namespace ScriptCallbackFramework
{	
	#region IClientCallbackEventHandler
	/// <summary>
	/// Control to have the callback capability must implement this interface
	/// </summary>
	public interface IClientCallbackEventHandler
	{
		string RaiseClientCallbackEvent(string eventArgument);
	}
	#endregion
		
	/// <summary>
	/// Client Script Callback Framework Implementation
	/// </summary>
	public class PageTemplate : System.Web.UI.Page
	{	
		#region Member Vars
		private bool isCallback;	// Is client script callback?
		#endregion
		
		#region Const
		//	Custom HTTP Header for client script to determine the callback status
		private const string CALLBACK_STATUSKEY = "__SCRIPTCALLBACKSTATUS";
		//	Client callback script initialization function name
		private const string INITCALLBACK_KEY	= "WebForm_InitClientCallback";
		//	Client asynchrounous callback function name
		private const string ASYNCCALLBACK_KEY	= "WebForm_DoAsyncCallback";
		//	Client synchronous callback function name
		private const string SYNCCALLBACK_KEY	= "WebForm_DoSyncCallback";
		//	JavaScript contains the client callback functions
		private const string CALLBACKSCRIPT_KEY	= "ScriptCallback.js";
		//	Closing Tag for HTML <HEAD> element
		private const string HEAD_EndTag = "</HEAD>";
		//	Closing Tag for HTML <FORM> element
		private const string FORM_EndTag = "</FORM>";
		#endregion
		
		#region Ctor
		public PageTemplate()
		{
			isCallback = false;
		}
		#endregion
		
		#region OnInit
		protected override void OnInit(EventArgs e)
		{
			//	Inspect the QueryString to determine whether this is a callback request or not
			if (
				Request.QueryString["__SCRIPTCALLBACKID"] != null 
				&&
				Request.QueryString["__SCRIPTCALLBACKID"] != String.Empty
				)
			{
				isCallback = true;
			}
				
			base.OnInit (e);
		}
		#endregion
		
		#region OnLoad
		protected override void OnLoad(EventArgs e)
		{
			base.OnLoad(e);
			
			if (IsCallback)
				HandleClientCallback();
		}
		#endregion

		#region Render
		protected override void Render(HtmlTextWriter writer)
		{
			PrepareCallbackScript(writer);
		}
		#endregion
		
		#region PrepareCallbackScript
		/// <summary>
		/// Include the ScriptCallback.js file and client initialization code
		/// if WebForm_DoAsyncCallback or WebForm_DoSyncCallback callback functions
		/// is found in the page's HTML source
		/// </summary>
		/// <param name="writer">HtmlTextWriter</param>
		private void PrepareCallbackScript(HtmlTextWriter writer)
		{
			StringWriter sw = new StringWriter();
			HtmlTextWriter buffer = new HtmlTextWriter(sw);
			base.Render(buffer);
			string pageSource = sw.ToString();
			
			if (IsCallbackScriptRequired(pageSource))
				pageSource = InjectCallbackScript(pageSource);
			
			writer.Write(pageSource);
		}
		#endregion
		
		#region IsCallbackScriptRequired
		/// <summary>
		/// Determines whether the client callback script is required by
		/// inspecting the callback function signature in the page's HTML source.
		/// </summary>
		/// <param name="src">Page Source</param>
		/// <returns>True if the callback script is required; Otherwise, False.</returns>
		protected virtual bool IsCallbackScriptRequired(string src)
		{
			try
			{
				if (src != null && src != String.Empty)
				{
					if (src.IndexOf(ASYNCCALLBACK_KEY) != -1 || src.IndexOf(SYNCCALLBACK_KEY) != -1)
					{
						if (src.IndexOf(CALLBACKSCRIPT_KEY) == -1)
							return true;
					}
				}
				return false;
			}
			catch(Exception)
			{
				return false;
			}
		}
		#endregion
		
		#region InjectCallbackScript

		/// <summary>
		/// Inject the client callback script into the existing page source 
		/// </summary>
		/// <param name="src">Page source</param>
		/// <returns>Page source with the client callback script</returns>
		private string InjectCallbackScript(string src)
		{
			int index = -1;
			
			StringBuilder sb = new StringBuilder(src);
			
			#region Include the ScriptCallback.js callback script file
			index = src.IndexOf(HEAD_EndTag);	// Search for upper case </HEAD>
			if (index == -1)	// Not found, search for lower case </head>
				index = src.IndexOf(HEAD_EndTag.ToLower());
			
			if (index > 0)
			{
				sb.Insert(index - 1, String.Format("<script language=javascript src=\"{0}/Scripts/{1}\" type=\"text/javascript\"></script>", Request.ApplicationPath, CALLBACKSCRIPT_KEY));
				src = sb.ToString();
			}
			#endregion
			
			#region Inject script to initialize the callback function at client side
			index = src.IndexOf(FORM_EndTag);	// Search for upper case </FORM>
			if (index == - 1)	//	Not found, search for lower case </form>
				index = src.IndexOf(FORM_EndTag.ToLower());

			if (index > 0)
				sb.Insert(index - 1, String.Format("<script language=\"javascript\">WebForm_InitClientCallback('{0}');</script>", Request.RawUrl));
			#endregion

			return sb.ToString();
		}
		#endregion
		
		#region Properties
		protected bool IsCallback
		{
			get { return isCallback; }
		}
		#endregion
		
		#region HandleClientCallback

		/// <summary>
		/// Handles the client script callback request and invoke the corresponding control
		/// to response
		/// </summary>
		private void HandleClientCallback()
		{	
			string ctrlID	= Request.QueryString["__SCRIPTCALLBACKID"];
			string param	= Request.QueryString["__SCRIPTCALLBACKPARAM"];

			try
			{
				Response.Clear();
												
				Control c = FindControl(ctrlID);
				if (c != null)
				{
					IClientCallbackEventHandler pCB = null;
					
					//	Is this a HtmlForm or normal controls
					System.Web.UI.HtmlControls.HtmlForm form = c as System.Web.UI.HtmlControls.HtmlForm;
					if (form != null)
						//	Special handling for HtmlForm
						pCB = (IClientCallbackEventHandler)c.Page;
					else
						pCB = (IClientCallbackEventHandler)c;
					
					//	Is this Control implement the IClientCallbackEventHandler interface
					if (pCB != null)
					{
						Response.Write(pCB.RaiseClientCallbackEvent(param));
						Response.AppendHeader(CALLBACK_STATUSKEY, "200");	//	OK
					}
				}
				else
				{
					Response.AppendHeader(CALLBACK_STATUSKEY, "404");		//	Not Found
					Response.Write(String.Format("Unable to find the specified Control [{0}].", ctrlID)); 
				}
			}
			catch(InvalidCastException)
			{	
				Response.AppendHeader(CALLBACK_STATUSKEY, "501");			//	Not Implemented
				Response.Write(String.Format("The specified Control [{0}] does not implement the IClientCallbackEventHandler interface.", ctrlID)); 
			}
			catch(Exception ex)
			{
				Response.AppendHeader(CALLBACK_STATUSKEY, "500");			//	Internal Error
				Response.Write(ex.Message); 
			}
			finally
			{
				Response.Flush();
				Response.End();
			}
		}
		#endregion

		#region GetAsyncCallbackEventReference

		/// <summary>
		/// Obtains a reference to a client-side script function that causes, when invoked, 
		/// the server to callback asynchronously to the page. 
		/// This method also passes a parameter to the server control that performs the callback processing on the server. 
		/// </summary>
		/// <param name="control">The server control to process the callback. It must implement the IClientCallbackEventHandler</param>
		/// <param name="args">Argument to be passed to the server control that performs the callback processing on the server</param>
		/// <param name="cbHandler">Callback Handler (JavaScript function), invokes when callback operation completed successfully</param>
		/// <param name="context">Any DHTML reference or extra information to pass to the Callback or Error Handler</param>
		/// <param name="errHandler">Error Handler (JavaScript function), invokes when error occurred during the callback operation</param>
		/// <returns>Asynchronous callback JavaScript code</returns>
		public static string GetAsyncCallbackEventReference(
			System.Web.UI.Control control,
			string args,
			string cbHandler,
			string context,
			string errHandler)
		{
			if (control == null)
				throw new ArgumentNullException("control");
							
			if (cbHandler == null || cbHandler == String.Empty)
				throw new ArgumentException("Client Callback Handler is required.", "cbHandler");
			
			if (args == null || args == String.Empty)
				args = "null";
			
			if (context == null || context == String.Empty)
				context = "null";
			
			if (errHandler == null || errHandler == String.Empty)
				errHandler = "null";
			
			return String.Format("javascript:WebForm_DoAsyncCallback('{0}',{1},{2},{3},{4});", 
				control.ID,
				args, 
				cbHandler,
				context, 
				errHandler);
		}
		#endregion
		
		#region GetSyncCallbackEventReference

		/// <summary>
		/// Obtains a reference to a client-side script function that causes, when invoked, 
		/// the server to callback synchronously to the page. 
		/// This method also passes a parameter to the server control that performs the callback processing on the server. 
		/// </summary>
		/// <param name="control">The server control to process the callback. It must implement the IClientCallbackEventHandler</param>
		/// <param name="args">Argument to be passed to the server control that performs the callback processing on the server</param>
		/// <param name="cbHandler">Callback Handler (JavaScript function), invokes when callback operation completed successfully</param>
		/// <param name="context">Any DHTML reference or extra information to pass to the Callback or Error Handler</param>
		/// <param name="errHandler">Error Handler (JavaScript function), invokes when error occurred during the callback operation</param>
		/// <returns>Synchronous callback JavaScript code</returns>
		public static string GetSyncCallbackEventReference(
			System.Web.UI.Control control,
			string args,
			string cbHandler,
			string context,
			string errHandler)
		{
			if (control == null)
				throw new ArgumentNullException("control");
			
			if (cbHandler == null || cbHandler == String.Empty)
				throw new ArgumentException("A Callback Handler is required.", "cbHandler");
			
			if (args == null || args == String.Empty)
				args = "null";
			
			if (context == null || context == String.Empty)
				context = "null";
			
			if (errHandler == null || errHandler == String.Empty)
				errHandler = "null";
			
			return String.Format("javascript:WebForm_DoSyncCallback('{0}',{1},{2},{3},{4});", 
				control.ID,
				args, 
				cbHandler,
				context, 
				errHandler);
		}
		#endregion
				
		#region RegisterClientScriptFile
		
		/// <summary>
		/// Include the specified JavaScript file into the current page
		/// </summary>
		/// <param name="fileName">JavaScript File Name. For example, Helpers.js</param>
		public virtual void RegisterClientScriptFile(string fileName)
		{	
			if (fileName != null && fileName != String.Empty)
			{
				string key = String.Format("{0}", fileName.Replace('.', '_'));
				
				if (!Page.IsClientScriptBlockRegistered(key))
				{
					string script = String.Format("<script language=javascript src=\"{0}/Scripts/{1}\" type=\"text/javascript\"></script>", Request.ApplicationPath, fileName);
					Page.RegisterClientScriptBlock(key, script);
				}
			}
		}	
		
		#endregion		
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

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

A list of licenses authors might use can be found here


Written By
Singapore Singapore
Elvin Cheng is currently living in Woodlands, Singapore. He has been developing applications with the .NET Framework, using C# and ASP.NET since October 2002. Elvin specializes in building Real-time monitoring and tracking information system for Semi-conductor manufacturing industry. During his spare time, he enjoys reading books, watching movie and gym.

Comments and Discussions