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