Click here to Skip to main content
Click here to Skip to main content
Articles » Web Development » Ajax » General » Downloads
 
Add your own
alternative version

Magic AJAX: Applying AJAX to your existing Web Pages

, 28 May 2007
How to apply AJAX technologies to your web pages without replacing ASP.NET controls and/or writing JavaScript code.
magicajax-030-net11.zip
magicajax
Examples
ExampleSite (.NET 2.0 only)
Web.sitemap
webparts
images
first.GIF
last.GIF
next.gif
pow_by_aspnet2.0.gif
prev.GIF
examples
App_Data
App_Code
Docs
Core
script
bin
MagicAjax.dll
magicajax-030-net20.zip
MagicAjax.dll
Web.sitemap
first.GIF
last.GIF
next.gif
pow_by_aspnet2.0.gif
prev.GIF
magicajax-030-source.zip
Web.sitemap
CVS
Root
Repository
Entries.Old
Entries
Entries.Extra.Old
Entries.Extra
first.GIF
last.GIF
next.gif
pow_by_aspnet2.0.gif
prev.GIF
CVS
Root
Repository
Entries.Old
Entries
Entries.Extra.Old
Entries.Extra
CVS
Root
Repository
Entries.Old
Entries
Entries.Extra.Old
Entries.Extra
CVS
Root
Repository
Entries.Old
Entries
Entries.Extra.Old
Entries.Extra
CVS
Root
Repository
Entries.Old
Entries
Entries.Extra.Old
Entries.Extra
CVS
Root
Repository
Entries
Entries.Extra
Entries.Old
Entries.Extra.Old
CVS
Root
Repository
Entries.Old
Entries.Extra.Old
Entries
Entries.Extra
CVS
Root
Repository
Entries
Entries.Extra
Entries.Old
Entries.Extra.Old
MagicAjax.snk
CVS
Root
Repository
Entries.Old
Entries.Extra.Old
Entries
Entries.Extra
UI
Design
CVS
Root
Repository
Entries.Old
Entries.Extra.Old
Entries
Entries.Extra
Controls
ClientEventControls
CVS
Root
Repository
Entries.Old
Entries
Entries.Extra.Old
Entries.Extra
CVS
Root
Repository
Entries.Extra
Entries.Old
Entries
Entries.Extra.Old
CVS
Root
Repository
Entries.Old
Entries.Extra.Old
Entries
Entries.Extra
Interfaces
CVS
Root
Repository
Entries.Old
Entries.Extra.Old
Entries
Entries.Extra
Configuration
CVS
Root
Repository
Entries.Old
Entries.Extra.Old
Entries
Entries.Extra
CVS
Root
Repository
Entries
Entries.Extra
Entries.Old
Entries.Extra.Old
CVS
Root
Repository
Entries.Old
Entries.Extra.Old
Entries
Entries.Extra
magicajax.zip
AJAXTest
AJAXTest.csproj.webinfo
Global.asax
Ajax
Ajax.csproj.user
Controls
Design
script
#region LGPL License
/*
MagicAjax.NET Framework
Copyright (C) 2005  MagicAjax Project Team

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#endregion

using System;
using System.Configuration;
using System.Collections;
using System.Collections.Specialized;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;

using MagicAjax.Configuration;
using MagicAjax.UI;
using MagicAjax.UI.Controls;

namespace MagicAjax
{
	/// <summary>
	/// It handles an AjaxCall request from the client and sends the appropriate javascript.
	/// </summary>
	/// <remarks>
	/// MagicAjaxModule handles the AcquireRequestState event of the HttpApplication, when
	/// the request has been authorized and the session state has been restored.
	/// 
	/// To use MagicAjaxModule for your application you must put
	/// 
	///		<httpModules>
	///			<add name="MagicAjaxModule" type="MagicAjax.MagicAjaxModule, MagicAjax" /> 
	///		</httpModules>
	///	
	/// in the system.web section of the web.config file.
	/// </remarks>
	public class MagicAjaxModule : IHttpModule
	{
		private bool _threadAbortExceptionThrown;
		private bool _processedAjaxCall;
		private PageFilter _filter;
		private HttpRequest _request;
		private HttpResponse _response;
		private MagicAjaxContext _magicAjaxContext;

		#region IHttpModule implementation
		/// <summary>
		/// Member of the IHttpModule interface
		/// </summary>
		/// <param name="context"></param>
		public virtual void Init(HttpApplication application)
		{
			application.AcquireRequestState += new EventHandler(Application_AcquireRequestState);
			application.BeginRequest += new EventHandler(Application_BeginRequest);
			application.EndRequest += new EventHandler(Application_EndRequest);
		}

		/// <summary>
		/// Member of the IHttpModule interface
		/// </summary>
		public virtual void Dispose()
		{
		}
		#endregion

		#region Public Static Methods

		/// <summary>
		/// Enables AJAX on a Page.
		/// </summary>
		/// <remarks>
		/// This method is called by controls that inherit from AjaxControl.
		/// Depending on the configuration options of MagicAjax, the page object may
		/// be stored so that MagicAjaxModule can retrieve it at an AjaxCall.
		/// </remarks>
		public static void EnableAjaxOnPage(Page page)
		{
			MagicAjaxContext magicAjaxContext = MagicAjaxContext.Current;
			if (page == null || magicAjaxContext.IsAjaxCallForPage(page)) return;

			// Enable AJAX only MagicAjax-supported browsers.
			// Other browsers will get a plain postback page.
			if (!magicAjaxContext.IsBrowserSupported)
				return;

			string STARTUP_SCRIPT_FORMAT = @"
			<script type=""text/javascript"">
			    if (typeof(AJAXCbo) == 'undefined')
					alert(""Unable to find script library '{0}/{1}'. Copy the file to the required location, or change the 'scriptPath' setting at magicAjax section of web.config."");
				else
					AJAXCbo.HookAjaxCall({2},{3},{4},{5});
			</script>";

			if (!page.IsClientScriptBlockRegistered("AJAXCALL_FOR_MAGICAJAX"))
			{
				// Provides the location of the script file.
				string location = magicAjaxContext.Configuration.ScriptPath;
				string includeScript = null;

				// If script location is null, use embedded AjaxCallObject.js file
				if (location == null)
				{
#if NET_2_0
					// Use the webresource url for AjaxCallObject.js (only for default Page HttpHandler)
					if (HttpContext.Current.Handler is Page)
					{
						includeScript = String.Format("<script type=\"text/javascript\" src=\"{0}\"></script>", page.ClientScript.GetWebResourceUrl(typeof(MagicAjaxModule), "MagicAjax.script.AjaxCallObject.js"));
					}
#endif
					if (includeScript == null)
					{
						// src-request to "AjaxCallObject.js.aspx" will be handled by Application_BeginRequest, which returns the embedded AjaxCallObject.js script
						includeScript = String.Format("<script type=\"text/javascript\" src=\"{0}/{1}\"></script>", MagicAjaxContext.Current.MagicAjaxVersion, "AjaxCallObject.js.aspx");
					}
				}
				else
				{
					// Point to external script source file
					includeScript = String.Format("<script type=\"text/javascript\" src=\"{0}/{1}\"></script>", location, "AjaxCallObject.js");
				}
				page.RegisterClientScriptBlock("AJAXCALL_FOR_MAGICAJAX", includeScript);

				bool pageIsStored = (magicAjaxContext.Configuration.PageStore.Mode != PageStoreMode.NoStore);

				if (pageIsStored)
				{
					// Store the page object
#if NET_2_0
					throw new MagicAjaxException("Session/Cache page storing modes are not supported for .NET 2.0 framework.");
#endif
					if (!page.Request.Browser.Crawler && page.Request.Browser.JavaScript)
					{
						string key = StorePage(page);
						page.RegisterHiddenField(PageKeyFieldName, key);
					}
				}

				// Save to page any changes that may occured to Configuration
				string configState = magicAjaxContext.Configuration.GetState();
				if (configState != null)
					page.RegisterHiddenField("__MAGICAJAX_CONFIG", configState);

				bool unloadStoredPage = magicAjaxContext.Configuration.PageStore.UnloadStoredPage;
				string formID = Util.GetPageFormID(page);
				page.RegisterStartupScript("AJAXCALL_HOOK", String.Format(STARTUP_SCRIPT_FORMAT, location, "AjaxCallObject.js", pageIsStored.ToString().ToLower(System.Globalization.CultureInfo.InvariantCulture), unloadStoredPage.ToString().ToLower(System.Globalization.CultureInfo.InvariantCulture), magicAjaxContext.Configuration.Tracing.ToString().ToLower(System.Globalization.CultureInfo.InvariantCulture), AjaxCallHelper.EncodeString(formID)));
			}
		}

#if NET_2_0
		/// <summary>
		/// Adds WebPart client scriptfile to the page.
		/// This scriptfile adds functions to reset the WebPartManager object on the client after a callback
		/// </summary>
		/// <param name="page"></param>
		public static void AddWebPartClientFunctions(Page page)
		{
			if (page == null || MagicAjaxContext.Current.IsAjaxCallForPage(page)) return;

			if (!HttpContext.Current.Items.Contains("AJAX_WEBPARTCLIENTSCRIPT_REGISTERED"))
			{
				// if this is a WebPart page, and IE browser, add our 'WebParts.js' script
				System.Web.UI.WebControls.WebParts.WebPartManager wpm = System.Web.UI.WebControls.WebParts.WebPartManager.GetCurrentWebPartManager(page);
				if (wpm != null && wpm.EnableClientScript)
				{
					HttpBrowserCapabilities capabilities = page.Request.Browser;
					if (capabilities.Win32 && (capabilities.MSDomVersion.CompareTo(new Version(5, 5)) >= 0))
					{
						string location = MagicAjaxContext.Current.Configuration.ScriptPath;
						if (location == null)
						{
							// Use embedded clientscipt resource
							page.ClientScript.RegisterClientScriptResource(typeof(MagicAjaxModule), "MagicAjax.script.WebParts.js");
						}
						else
						{
							// Point to external script source file
							string includeScript = String.Format("<script type=\"text/javascript\" src=\"{0}/{1}\"></script>", location, "WebParts.js");
							page.RegisterClientScriptBlock("AJAX_WEBPARTCLIENTSCRIPT_REGISTERED", includeScript);
						}

						string AJAX_WEBPARTCLIENTSCRIPT_CHECK_FORMAT = @"
						<script language='javascript'>
							 if (typeof(WebPartManager_Cleanup) == 'undefined')
								alert(""Unable to find script library '{0}/{1}'. Copy the file to the required location, or change the 'scriptPath' setting at magicAjax section of web.config."");
						</script>";

						page.RegisterStartupScript("AJAX_WEBPARTCLIENTSCRIPT_CHECK", string.Format(AJAX_WEBPARTCLIENTSCRIPT_CHECK_FORMAT, location, "WebParts.js"));
					}
				}
				HttpContext.Current.Items.Add("AJAX_WEBPARTCLIENTSCRIPT_REGISTERED", true);
			}
		}
#endif
		#endregion

		#region Protected Static Methods

		protected static string PageKeyFieldName
		{
			get { return "__AJAX_PAGEKEY"; }
		}

		protected static string StorePage(Page page)
		{
			string key = GetStoredPagesKey(page);
			int pageInfoIndex;

			bool saveStoredPagesList = false;
			ArrayList storedPages = GetStoredPagesList(key);
			if (storedPages == null)
			{
				storedPages = new ArrayList();
				saveStoredPagesList = true;
			}

			StoredPageInfo pageInfo = new StoredPageInfo(page);

			if (storedPages.Count < MagicAjaxContext.Current.Configuration.PageStore.MaxConcurrentPages)
			{
				pageInfoIndex = storedPages.Add(pageInfo);
			}
			else
			{
				// Find an empty slot
				pageInfoIndex = -1;
				for (int i = 0; i < storedPages.Count; i++)
				{
					if (storedPages[i] == null)
					{
						pageInfoIndex = i;
						break;
					}
				}

				if (pageInfoIndex == -1)
				{
					// Empty slot not found. Replace the page that wasn't accessed for a while
					DateTime oldest = DateTime.Now;
					for (int i = 0; i < storedPages.Count; i++)
					{
						StoredPageInfo storedInfo = (StoredPageInfo)storedPages[i];
						if (storedInfo.LastAccess < oldest)
						{
							oldest = storedInfo.LastAccess;
							pageInfoIndex = i;
						}
					}

					if (MagicAjaxContext.Current.Configuration.PageStore.MaxPagesLimitAlert)
					{
						page.RegisterStartupScript("__AJAX_PAGENOTSTOREDWARNING",
							"<script language='javascript'>alert('MagicAjax: You reached maximum concurrent pages. A stored page was replaced by this one.');</script>");
					}
				}

				storedPages[pageInfoIndex] = pageInfo;
			}

			if (saveStoredPagesList)
			{
				switch (MagicAjaxContext.Current.Configuration.PageStore.Mode)
				{
					case PageStoreMode.Session:
						HttpContext.Current.Session[key] = storedPages;
						break;
					case PageStoreMode.Cache:
						HttpContext.Current.Cache.Add(key, storedPages, null, System.Web.Caching.Cache.NoAbsoluteExpiration,
							TimeSpan.FromMinutes(MagicAjaxContext.Current.Configuration.PageStore.CacheTimeout), System.Web.Caching.CacheItemPriority.Default, null);
						break;
					default:
						throw new ConfigurationException(String.Format("MagicAjax configuration: The pageStore mode '{0}' is not supported for storing the page.", MagicAjaxContext.Current.Configuration.PageStore.Mode));
				}
			}

			return String.Format("{0}!{1}", key, pageInfoIndex);
		}

		protected static StoredPageInfo GetStoredPageInfo(string pageKey)
		{
			int chari = pageKey.LastIndexOf('!');
			if (chari == -1)
				throw new MagicAjaxException("PageKey is invalid.");

			string storedPagesKey = pageKey.Substring(0, chari);
			int pageInfoIndex;
			try
			{
				pageInfoIndex = Int32.Parse(pageKey.Substring(chari + 1));
			}
			catch
			{
				throw new MagicAjaxException("PageKey is invalid.");
			}

			ArrayList storedPages = GetStoredPagesList(storedPagesKey);
			if (storedPages == null)
				return null;

			return (StoredPageInfo)storedPages[pageInfoIndex];
		}

		protected static void RemoveStoredPageInfo(string pageKey)
		{
			int chari = pageKey.LastIndexOf('!');
			if (chari == -1)
				throw new MagicAjaxException("PageKey is invalid.");

			string storedPagesKey = pageKey.Substring(0, chari);
			int pageInfoIndex;
			try
			{
				pageInfoIndex = Int32.Parse(pageKey.Substring(chari + 1));
			}
			catch
			{
				throw new MagicAjaxException("PageKey is invalid.");
			}

			ArrayList storedPages = GetStoredPagesList(storedPagesKey);
			if (storedPages == null)
				return;

			StoredPageInfo storedInfo = (StoredPageInfo)storedPages[pageInfoIndex];
			// Firefox requests the page first and then sends the unload page request,
			// so check to see if the page we are going to remove is just created.
			if (storedInfo.AjaxCallsCount == 0 && DateTime.Now - storedInfo.LastAccess < TimeSpan.FromSeconds(5))
				return;

			storedPages[pageInfoIndex] = null;
		}

		protected static ArrayList GetStoredPagesList(string key)
		{
			if (HttpContext.Current == null)
				return null;

			switch (MagicAjaxContext.Current.Configuration.PageStore.Mode)
			{
				case PageStoreMode.Session:
					if (HttpContext.Current.Session != null)
						return (ArrayList)HttpContext.Current.Session[key];
					else
						return null;

				case PageStoreMode.Cache:
					if (HttpContext.Current.Cache != null)
						return (ArrayList)HttpContext.Current.Cache[key];
					else
						return null;

				default:
					throw new ConfigurationException(String.Format("MagicAjax configuration: The pageStore mode '{0}' is not supported for storing the page.", MagicAjaxContext.Current.Configuration.PageStore.Mode));
			}
		}

		protected static string GetStoredPagesKey(Page page)
		{
			switch (MagicAjaxContext.Current.Configuration.PageStore.Mode)
			{
				case PageStoreMode.Session:
					return String.Format("__AJAX_{0}_{1}", page.Request.FilePath, page.GetTypeHashCode().ToString("X2"));

				case PageStoreMode.Cache:
					return String.Format("__AJAX_{0}_{1}_{2}", HttpContext.Current.Session.SessionID, page.Request.FilePath, page.GetTypeHashCode().ToString("X2"));

				default:
					throw new ConfigurationException(String.Format("MagicAjax configuration: The pageStore mode '{0}' is not supported for storing the page.", MagicAjaxContext.Current.Configuration.PageStore.Mode));
			}
		}
		#endregion

		#region Protected methods
		/// <summary>
		/// Handles the BeginRequest event of the HttpApplication
		/// Currently only used to return the embedded "AjaxCallObject.js" script
		/// on requests to "AjaxCallObject.js.aspx"
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		protected void Application_BeginRequest(object sender, EventArgs e)
		{
			HttpContext context = ((HttpApplication)sender).Context;

			if (context.Request.Url.AbsolutePath.EndsWith(".aspx"))
			{
				// Check if the request is for the embedded AjaxCallObject.js script
				if (context.Request.RawUrl.EndsWith("AjaxCallObject.js.aspx"))
				{
					context.Response.ContentType = "text/javascript";
					object cachedAjaxCallObjectJs = context.Cache["__CACHED_AJAXCALLOBJECT_JS"];
					if (cachedAjaxCallObjectJs == null)
					{
						//read and output the embedded AjaxCallObject.js file from the manifest
						using (System.IO.StreamReader reader = new System.IO.StreamReader(typeof(MagicAjaxModule).Assembly.GetManifestResourceStream("MagicAjax.script.AjaxCallObject.js")))
						{
							cachedAjaxCallObjectJs = reader.ReadToEnd();
						}
						context.Cache.Insert("__CACHED_AJAXCALLOBJECT_JS", cachedAjaxCallObjectJs);
					}
					context.Response.Write(cachedAjaxCallObjectJs);
					context.Response.Cache.SetExpires(DateTime.Now.AddYears(1));
					context.Response.End();
				}
				else
				{
					// Init private fields
					_threadAbortExceptionThrown = false;
					_processedAjaxCall = false;
					_request = context.Request;
					_response = context.Response;

					// Create a new context and add it to the items collection for later retrieval
					// by MagicAjaxContext.Current
					_magicAjaxContext = new MagicAjaxContext();
					HttpContext.Current.Items.Add(MagicAjaxContext.ContextKey, _magicAjaxContext);
				}
			}
		}

		/// <summary>
		/// Handles the AcquireRequestState event of the HttpApplication
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		protected void Application_AcquireRequestState(object sender, EventArgs e)
		{
			HttpApplication application = (HttpApplication)sender;

			if (_request == null || !_request.Url.AbsolutePath.EndsWith(".aspx"))
			{
				return; //if this wasn't a .aspx request, don't process
			}

			string pageKey = _request.QueryString["__AJAX_PAGEUNLOAD"];
			if (pageKey != null)
			{
				RemoveStoredPageInfo(pageKey);
				// Don't let the browser cache it or the next page unload request
				// will not reach the server.
				_response.Cache.SetNoStore();
				application.CompleteRequest();
			}

			// Continue only if it is a postback or an AjaxCall
			if ("GET" == _request.HttpMethod)
				return;

			Page currentPage = HttpContext.Current.Handler as Page;

#if NET_2_0
			if (currentPage != null)
			{
				// For ASP.NET 2.0, disable event validation on the page 
				currentPage.EnableEventValidation = false;
				//TODO: check if there is another way to avoid the event-validation exceptions in the controls
			}
#endif

			_magicAjaxContext.IsAjaxCall = (_request.Form["__AJAXCALL"] != null);
			string configState = _request.Form["__MAGICAJAX_CONFIG"];
			if (configState != null)
			{
				_magicAjaxContext.Configuration.LoadState(configState);
				if (_magicAjaxContext.IsAjaxCall)
					_magicAjaxContext.Configuration.IsLocked = true;
			}

			pageKey = _request.Form[PageKeyFieldName];

			if (pageKey == null)
			{
				// The page is not stored

				if (_magicAjaxContext.IsAjaxCall)
				{
					bool viewStateIsExcluded = (_request.Form["__VIEWSTATE"] == null);

					//insert output filter
					_filter = new PageFilter(_response.Filter);
					_response.Filter = _filter;

					AjaxCallHelper.Init();

					try
					{
						if (viewStateIsExcluded && currentPage != null)
						{
							// ViewState is excluded from the post data. Disable it on
							// the page since its value will not be sent to client.
							currentPage.EnableViewState = false;
						}

						HttpContext.Current.Handler.ProcessRequest(HttpContext.Current);
					}
					catch (System.Threading.ThreadAbortException)
					{
						// ThreadAbortException is for Server.Transfer, Response.Redirect and AjaxCallHelper.End
						_threadAbortExceptionThrown = true;
						throw;
					}

#if NET_2_0
					// In ASP.NET 2.0, no ThreadAbortException is thrown when a Response.Redirect is 
					// done in HttpContext.Current.Handler.ProcessRequest. We solved this by checking 
					// if the request is being redirected, and output the redirecting javascript.
					// Note: Server.Transfer for ASP.NET 2.0 isn't supported by MagicAjax yet.
					if (_response.IsRequestBeingRedirected)
					{
						AjaxCallHelper.Redirect(_response.RedirectLocation);
					}
#endif
					_processedAjaxCall = true;
					_response.End();
				}
			}
			else
			{
				// The page is stored

				_magicAjaxContext.StoredPageInfo = GetStoredPageInfo(pageKey);
				if (_magicAjaxContext.StoredPageInfo == null)
				{
					if (_magicAjaxContext.IsAjaxCall)
					{
						// Stored Page wasn't found, could be that the session expired or
						// the client returned to an unloaded page.
						// Have the browser do a refresh.
						// It will stop the current execution and will throw Application_EndRequest event
						AjaxCallHelper.Redirect(_request.RawUrl);
					}
					else
					{
						return;
					}
				}

				if (!_magicAjaxContext.IsAjaxCall)
				{
					// PostBack. Do some really hacky stuff to get the real viewstate.

					string vs = GetViewStateOfPage(_magicAjaxContext.StoredPageInfo.Page);
					if (vs != null)
					{
						Util.SetPrivateField(_request.Form, typeof(NameObjectCollectionBase), "_readOnly", false);
						_request.Form["__VIEWSTATE"] = vs;
						Util.SetPrivateField(_request.Form, typeof(NameObjectCollectionBase), "_readOnly", true);
					}
				}
				else
				{
					// AjaxCall
					_filter = new PageFilter(_response.Filter);
					_response.Filter = _filter;

					if (_magicAjaxContext.StoredPageInfo.AjaxCallsCount == 0)
					{
						// For bypassing the VerifyRenderingInServerForm check
						Util.SetPrivateField(_magicAjaxContext.StoredPageInfo.Page, typeof(Page), "_inOnFormRender", true);

						if (_magicAjaxContext.StoredPageInfo.Page.Validators.Count == 0)
						{
							// Restore the validators
							ArrayList validators = Util.GetChildControlsOfType(_magicAjaxContext.StoredPageInfo.Page, typeof(IValidator), null, false);
							foreach (IValidator valid in validators)
								_magicAjaxContext.StoredPageInfo.Page.Validators.Add(valid);
						}
					}

					_magicAjaxContext.StoredPageInfo.AjaxCallsCount++;
					_magicAjaxContext.StoredPageInfo.LastAccess = DateTime.Now;

					AjaxCallHelper.Init();

					try
					{
						ProcessAjaxCall(_magicAjaxContext.StoredPageInfo.Page);
					}
					catch (System.Threading.ThreadAbortException)
					{
						// ThreadAbortException is for Server.Transfer, Response.Redirect and AjaxCallHelper.End
						_threadAbortExceptionThrown = true;
						throw;
					}

					_processedAjaxCall = true;
					_response.End();
				}
			}
		}

		/// <summary>
		/// Handles the EndRequest event of the Application
		/// </summary>
		/// <remarks>
		/// During an AjaxCall it checks if the request was ended because of a
		/// Response.Redirect or a Server.Transfer and sends the appropriate javascript
		/// for each case.
		/// </remarks>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		protected void Application_EndRequest(object sender, EventArgs e)
		{
			if (_request == null)
				return;

			try
			{
				if (_magicAjaxContext.IsAjaxCall)
				{
					if (_processedAjaxCall)
					{
						// If the ViewState wasn't excluded from the post data, retrieve
						// it and send it to client.
						if (_magicAjaxContext.IsPageNoStoreMode && _request.Form["__VIEWSTATE"] != null)
						{
							string vsValue = _filter.GetViewStateFieldValue();
							if (vsValue != null && _request.Form["__VIEWSTATE"] != vsValue)
							{
								AjaxCallHelper.WriteSetFieldScript("__VIEWSTATE", vsValue);
							}
						}

#if NET_2_0
						Page currentPage = HttpContext.Current.Handler as Page;

						// Check if this request is to add/remove/replace a WebPart
						// If so, send back updated WebPartManager drag&drop javascript (IE only)
						if (!string.IsNullOrEmpty(_request.Form["__WPPS"]) && currentPage != null)
						{
							string wpmValue = _filter.GetWebPartManagerScriptValue(currentPage.Form.ClientID);
							if (wpmValue != null)
							{
								// Send script to cleanup current clientside WebPartManager object
								AjaxCallHelper.Write("if (typeof(WebPartManager_Cleanup) == 'function') { WebPartManager_Cleanup(); }");

								// Send script to setup webpartmanager drag&drop + webpartmenu's
								AjaxCallHelper.Write(wpmValue);
							}
						}
#endif

						AjaxCallHelper.End();

					}
					else if (_threadAbortExceptionThrown && !_magicAjaxContext.CompletedAjaxCall)
					{
						// There was a Response.Redirect or Server.Transfer during AjaxCall

						if (_response.RedirectLocation != null)
						{
							// Handle Response.Redirect
							AjaxCallHelper.Redirect(_response.RedirectLocation);
						}
						else
						{
							// Handle Server.Transfer
							AjaxCallHelper.Init();
							string html = _filter.GetHtmlPage();
							if (_request.Browser != null && _request.Browser.Browser == "IE")
							{
								// IE has a weird bug and when document.write is called, it
								// executes the scripts of the html BEFORE the external script files.
								// So add 'defer' attribute to the scripts so that external script files
								// are executed first.
								html = Util.AddDeferToScriptTags(html);

							}
							AjaxCallHelper.WriteSetHtmlOfPageScript(html);

							AjaxCallHelper.End();
						}
					}
				}
			}
			finally
			{
				// Clear private fields
				_request = null;
				_response = null;
				_magicAjaxContext = null;
				_filter = null;
			}
		}

		/// <summary>
		/// Raises the AjaxCall events on the Page and the child controls,
		/// and invokes the IScriptWriter controls.
		/// </summary>
		/// <param name="page"></param>
		protected void ProcessAjaxCall(Page page)
		{
			// Required for Page.Request/Page.Response to be valid
			Util.CallPrivateMethod(page, typeof(Page), "SetIntrinsics", HttpContext.Current);

			ArrayList postDataChangedControls;
			postDataChangedControls = LoadFormDataOnChildren(page);

			RaiseAjaxCallStartEvent(page);

			foreach (IPostBackDataHandler handler in postDataChangedControls)
				handler.RaisePostDataChangedEvent();

			if (_magicAjaxContext.AjaxCallType == AjaxCallType.Control)
			{
				string target = _request.Form["__EVENTTARGET"];
				string argument = _request.Form["__EVENTARGUMENT"];
				RaisePostBackEventInChild(page, target, argument);
			}

			RaisePreWriteScriptEvent(page);

			InvokeScriptWriters(page);

			RaiseAjaxCallEndEvent(page);
		}

		/// <summary>
		/// Raises AjaxCall event on the supplied control and its children.
		/// </summary>
		/// <remarks>
		/// This is a recursive method. It goes through the control collection tree
		/// and raises the AjaxCall event on all the controls that implement the
		/// IAjaxCallEventHandler interface.
		/// </remarks>
		/// <param name="control"></param>
		protected void RaiseAjaxCallStartEvent(Control control)
		{
			if (control is IAjaxCallEventHandler)
				((IAjaxCallEventHandler)control).RaiseAjaxCallStartEvent();

			for (int i = 0; i < control.Controls.Count; i++)
				RaiseAjaxCallStartEvent(control.Controls[i]);
		}

		/// <summary>
		/// Raises AjaxCallEnd event on the supplied control and its children.
		/// </summary>
		/// <remarks>
		/// This is a recursive method. It goes through the control collection tree
		/// and raises the AjaxCallEnd event on all the controls that implement the
		/// IAjaxCallEventHandler interface.
		/// </remarks>
		/// <param name="control"></param>
		protected void RaiseAjaxCallEndEvent(Control control)
		{
			if (control is IAjaxCallEventHandler)
				((IAjaxCallEventHandler)control).RaiseAjaxCallEndEvent();

			for (int i = 0; i < control.Controls.Count; i++)
				RaiseAjaxCallEndEvent(control.Controls[i]);
		}

		/// <summary>
		/// Raises the PreWriteScript event on the supplied control and its children.
		/// </summary>
		/// <remarks>
		/// This is a recursive method. It goes through the control collection tree
		/// and raises the PreWriteScript event on all the controls of the
		/// RenderedByScriptControl class.
		/// </remarks>
		/// <param name="control"></param>
		protected void RaisePreWriteScriptEvent(Control control)
		{
			if (control is IPreWriteScriptEventHandler)
				((IPreWriteScriptEventHandler)control).RaisePreWriteScriptEvent();

			for (int i = 0; i < control.Controls.Count; i++)
				RaisePreWriteScriptEvent(control.Controls[i]);
		}

		/// <summary>
		/// It invokes the WriteScript method on the supplied control and its children.
		/// </summary>
		/// <remarks>
		/// This is a recursive method. It goes through the control collection tree
		/// and invokes the WriteScript method on all the controls of the
		/// RenderedByScriptControl class.
		/// </remarks>
		/// <param name="control"></param>
		protected void InvokeScriptWriters(Control control)
		{
			if (control is IScriptWriter)
				((IScriptWriter)control).WriteScript();

			for (int i = 0; i < control.Controls.Count; i++)
				InvokeScriptWriters(control.Controls[i]);
		}

		/// <summary>
		/// Loads form post data on controls during an AjaxCall.
		/// </summary>
		protected ArrayList LoadFormDataOnChildren(Control control)
		{
			ArrayList list = new ArrayList();

			for (int i = 0; i < control.Controls.Count; i++)
			{
				Control con = control.Controls[i];

				if (con.Visible)
				{
					if (con is IPostBackDataHandler)
					{
						if (con is WebControl
							&& ((WebControl)con).Attributes["ExcludeFromPost"] != null
							&& ((WebControl)con).Attributes["ExcludeFromPost"].ToLower(System.Globalization.CultureInfo.InvariantCulture) == "true")
							continue;

						IPostBackDataHandler handler = (IPostBackDataHandler)con;

						if (con is RadioButtonList)
						{
							// For some strange reason, if the RadioButtonList selection
							// changes, the SelectedIndexChanged event is invoked every time
							// an AjaxCall occurs. So, do a manual check.
							RadioButtonList rbList = (RadioButtonList)con;
							if (_request.Form[rbList.UniqueID] != rbList.SelectedValue)
							{
								if (handler.LoadPostData(con.UniqueID, _request.Form))
									list.Add(con);
							}
						}
						else if (con is CheckBoxList)
						{
							CheckBoxList cbList = (CheckBoxList)con;
							bool changed = false;
							for (int listItem = 0; listItem < cbList.Items.Count; listItem++)
							{
								bool oldSelected = cbList.Items[listItem].Selected;
								handler.LoadPostData(String.Format("{0}:{1}", con.UniqueID, listItem), _request.Form);
								if (oldSelected != cbList.Items[listItem].Selected)
									changed = true;
							}

							if (changed)
								list.Add(con);
						}
						else
						{
							if (handler.LoadPostData(con.UniqueID, _request.Form))
							{
								list.Add(con);
							}
						}
					}

					list.AddRange(LoadFormDataOnChildren(con));
				}
			}

			return list;
		}

		/// <summary>
		/// Raises a PostBack event of a control during an AjaxCall.
		/// </summary>
		/// <param name="eventTarget"></param>
		/// <param name="eventArgument"></param>
		protected void RaisePostBackEventInChild(Page page, string eventTarget, string eventArgument)
		{
			Control eventcontrol = page.FindControl(eventTarget);

			if (eventcontrol is IPostBackEventHandler)
				((IPostBackEventHandler)eventcontrol).RaisePostBackEvent(eventArgument);
		}

		/// <summary>
		/// Hacky way to get the ViewState field value of a page.
		/// </summary>
		/// <param name="page"></param>
		/// <returns></returns>
		protected string GetViewStateOfPage(Page page)
		{
#if NET_2_0
			// TODO: Write the hacky way to get ViewState for NET 2.0 too.
			return null;
#else
			System.Text.StringBuilder sb = new System.Text.StringBuilder();
			HtmlTextWriter writer = new HtmlTextWriter(new System.IO.StringWriter(sb));

			Util.SetPrivateField(page, typeof(Page), "_fOnFormRenderCalled", false);
			Util.CallPrivateMethod(page, typeof(Page), "SavePageViewState");
			Util.CallPrivateMethod(page, typeof(Page), "OnFormRender", writer, String.Empty);

			string html = sb.ToString();
			int si = html.IndexOf("value=\"") + 7;
			int ei = html.IndexOf('\"', si);
			return html.Substring(si, ei - si);
#endif
		}
		#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, along with any associated source code and files, is licensed under The MIT License

About the Author

Argiris Kirtzidis
Web Developer
Greece Greece
No Biography provided

| Advertise | Privacy | Mobile
Web03 | 2.8.140721.1 | Last Updated 28 May 2007
Article Copyright 2005 by Argiris Kirtzidis
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid