Click here to Skip to main content
15,892,005 members
Articles / Web Development / HTML

Magic AJAX: Applying AJAX to your existing Web Pages

Rate me:
Please Sign up or sign in to vote.
4.82/5 (72 votes)
28 May 2007MIT12 min read 978.1K   2.7K   251  
How to apply AJAX technologies to your web pages without replacing ASP.NET controls and/or writing JavaScript code.
#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.Collections;
using System.Web;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;

namespace MagicAjax
{
	/// <summary>
	/// Helper methods intented to be used internally be the framework.
	/// </summary>
	internal class Util
	{
		public const string ScriptPattern = "<script.[^>]*>";
		public const string FormElementPattern = @"<(?<tag>input|textarea|select)\s+(?<attribute>(?<attrname>[-\w]+)=(""(?<attrvalue>.*?)""\s*|'(?<attrvalue>.*?)'\s*|(?<attrvalue>[-\w]+)\s*))*.*?(/>|>((?<inner>.*?)</\k'tag'>)?)";
		public const string StyleElementPattern = @"<(?<tag>style|link)\s*(?<attribute>(?<attrname>[-\w]+)=((""|')(?<attrvalue>.*?)(""|')\s*|(?<attrvalue>[-\w]+)\s*))*.*?(/>|>(?<inner>.*?)</\k'tag'>)";

		#region Global Regular Expressions
		private static RegexOptions _options = RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.ExplicitCapture;
		public static Regex FormElementRegEx = new Regex(Util.FormElementPattern, _options);
		public static Regex FormElementOptionsRegEx = new Regex("<option\\s((?<attrname>[-\\w]+)=\"(?<attrvalue>.*?)\"\\s?)*\\s*>.*?</option>", _options);
		public static Regex FormElementOptionSelectedRegEx = new Regex(@"<option.*?(?<selected>selected=(""|'|)selected(""|'|)(\s+|(?=>))).*?>", _options);
		public static Regex ScriptPatternRegEx = new Regex(Util.ScriptPattern, RegexOptions.IgnoreCase);
		public static Regex ScriptTagsRegEx = new Regex("<script\\s((?<attrname>[-\\w]+)=[\"'](?<attrvalue>.*?)[\"']\\s?)*\\s*>(?<script>.*?)</script>", _options);
		public static Regex HeaderStyleTagsRegEx = new Regex(StyleElementPattern, _options);
		#endregion

		/// <summary>
		///	Gets the ClientID of the given Page's Form.
		/// </summary>
		/// <param name="page"></param>
		/// <returns></returns>
		public static string GetPageFormID(Page page)
		{
			string id = null;
#if NET_2_0
			if (page.Form != null)
				id = page.Form.ClientID;
#else
			foreach (Control con in page.Controls)
			{
				if (con is HtmlForm)
				{
					id = con.ClientID;
					break;
				}
			}
#endif
			return id;
		}

		/// <summary>
		/// Add 'defer' attribute to script tags excluding external script files.
		/// </summary>
		/// <remarks>
		/// 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.
		/// </remarks>
		/// <param name="html"></param>
		/// <returns></returns>
		public static string AddDeferToScriptTags (string html)
		{
			StringBuilder sb = new StringBuilder(html);

			Regex regEx = Util.ScriptPatternRegEx;
			MatchCollection scriptMatches = regEx.Matches(html);
			//MatchCollection scriptMatches = Regex.Matches(html, ScriptPattern, RegexOptions.IgnoreCase);

			int diff = 0;
			for (int i=0; i < scriptMatches.Count; i++)
			{
				Match match = scriptMatches[i];
				string tag = match.Value.ToLower(System.Globalization.CultureInfo.InvariantCulture);

				if (tag.IndexOf(" src=") == -1)
				{
					// It's script block, add 'defer' attribute
					tag = tag.Insert(tag.Length - 1, " defer");
				}

				if ( tag.Length != match.Value.Length )
				{
					int sbIndex = match.Index + diff;
					sb.Replace(match.Value, tag, sbIndex, match.Value.Length);
					diff += tag.Length - match.Value.Length;
				}
			}

			return sb.ToString();
		}

		/// <summary>
		/// Create a fingerprint string of an input html
		/// </summary>
		/// <param name="str"></param>
		/// <returns></returns>
		public static string GetFingerprint(string input)
		{
            input = GetHtmlWithClearedFormValues(input);

			MagicAjax.Configuration.OutputCompareMode compareMode = MagicAjaxContext.Current.Configuration.CompareMode;
			switch (compareMode)
			{
				case MagicAjax.Configuration.OutputCompareMode.FullHtml:
					return input;
				case MagicAjax.Configuration.OutputCompareMode.HashCode:
					return input.GetHashCode().ToString("X2");
				case MagicAjax.Configuration.OutputCompareMode.MD5:
					byte[] inputBytes = UnicodeEncoding.Default.GetBytes(input);
					byte[] hashedBytes = new MD5CryptoServiceProvider().ComputeHash(inputBytes);
					return Convert.ToBase64String(hashedBytes);
				default:
					return input;
			}
		}

		/// <summary>
		/// Parses all form input controls from the html, and clears their values.
		/// The return html is used to get a fingerprint that is not dependent on the
		/// actual value of a form input control.
		/// </summary>
		/// <param name="html"></param>
		/// <returns></returns>
		public static string GetHtmlWithClearedFormValues (string html)
		{
			StringBuilder strbuild = new StringBuilder(html);
			Regex regEx = Util.FormElementRegEx;
			MatchCollection matches = regEx.Matches(html);
			int diff = 0;
			for (int i=0; i<matches.Count; i++)
			{
				Match match = matches[i];
				CaptureCollection attributes = match.Groups["attribute"].Captures;
				CaptureCollection attrnames = match.Groups["attrname"].Captures;
				CaptureCollection attrvalues = match.Groups["attrvalue"].Captures;

				Hashtable attrCaptures = new Hashtable(attributes.Count);
				Hashtable attrNameValues = new Hashtable(attributes.Count);

				for (int j=0; j < attributes.Count; j++)
				{
					string attrname = attrnames[j].Value.ToLower(System.Globalization.CultureInfo.InvariantCulture);
					attrNameValues[attrname] = attrvalues[j].Value;
					attrCaptures[attrname] = attributes[j];
				}

				// If the form element has the MagicAjax 'ExcludeFromPost' attribute
				// set to 'true', ignore it. We will need its value for the fingerprint.
				if ( attrNameValues.ContainsKey("excludefrompost")
					&& (attrNameValues["excludefrompost"] as String).ToLower(System.Globalization.CultureInfo.InvariantCulture) == "true")
					continue;

				string tag = match.Groups["tag"].Value.ToLower(System.Globalization.CultureInfo.InvariantCulture);
				string name = (string)attrNameValues["name"];
				string clientID = (string)attrNameValues["id"];

				if (name == null || clientID == null)
					continue;

				switch (tag)
				{
						#region <input> tags

					case "input":
						string type = (string)attrNameValues["type"];
						if (type != null)
						{
							switch (type)
							{
								case "text":
									if (attrNameValues.ContainsKey("value"))
									{
										Capture attr = attrCaptures["value"] as Capture;
										int sbIndex = attr.Index - diff;
										strbuild.Remove (sbIndex, attr.Length);
										diff += attr.Length;
									}
									break;
								case "checkbox":
								case "radio":
									if (attrNameValues.ContainsKey("checked"))
									{
										Capture attr = attrCaptures["checked"] as Capture;
										int sbIndex = attr.Index - diff;
										strbuild.Remove (sbIndex, attr.Length);
										diff += attr.Length;
									}
									break;
							}
						}
						break;

						#endregion

						#region <textarea> tags

					case "textarea":
						Group inner = match.Groups["inner"];
						if (inner.Success)
						{
							int sbIndex = inner.Index - diff;
							strbuild.Remove (sbIndex, inner.Length);
							diff += inner.Length;
						}
						break;

						#endregion

						#region <select> tags

					case "select":
						Group selInner = match.Groups["inner"];
						Regex regExOpt = Util.FormElementOptionSelectedRegEx;

						//now remove the 'selected' attributes within this <select> tag
						MatchCollection matchesSelected = regExOpt.Matches(selInner.Value);
						for (int j = 0; j < matchesSelected.Count; j++)
						{
							Group selected = matchesSelected[j].Groups["selected"];
							int sbIndex = selInner.Index + selected.Index - diff;
							strbuild.Remove (sbIndex, selected.Length);
							diff += selected.Length;
						}
						break;
						#endregion
				}
			}

			return strbuild.ToString();
		}

		/// <summary>
		/// Resolves relative url's (starting with "~"). 
		/// Use this when Control.ResolveUrl() is not available.
		/// </summary>
		/// <param name="url"></param>
		/// <returns></returns>
		public static string ResolveUrl(string url)
		{
			if (url != null && url.StartsWith("~") && HttpContext.Current != null)
			{
				string appPath = HttpContext.Current.Request.ApplicationPath.Length > 1 ? HttpContext.Current.Request.ApplicationPath : string.Empty;
				url = appPath + url.Substring(1);
			}
			return url;
		}

		/// <summary>
		/// Call a private method from the given object.
		/// </summary>
		/// <param name="instance"></param>
		/// <param name="type"></param>
		/// <param name="methodName"></param>
		/// <param name="parameters"></param>
		/// <returns></returns>
		public static object CallPrivateMethod(object instance, Type type, string methodName, params object[] parameters)
		{
			return type.GetMethod(methodName, System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic, null, GetTypesOfParameters(parameters), null).Invoke(instance, parameters);
		}

		public static void SetPrivateField (object instance, Type type, string fieldName, object value)
		{
			type.GetField(fieldName, System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic).SetValue(instance, value);
		}

		public static object GetPrivateField (object instance, Type type, string fieldName)
		{
			return type.GetField(fieldName, System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic).GetValue(instance);
		}

		/// <summary>
		/// Copies all fields (private and public) from the source object to
		/// the destination object thereby performing shallow copy
		/// </summary>
		/// <param name="destination">object to copy fields on</param>
		/// <param name="source">object to copy fields from</param>
		public static void ForceShallowCopyOnObject(object destination, object source)
		{
			Type dstType = destination.GetType();
			Type srcType = source.GetType();
			// get all fields of source type
			// to get all private fields loop over all base classes.
			for (Type t = srcType; t != typeof(Object); t = t.BaseType)
			{
				System.Reflection.FieldInfo[] srcFields = t.GetFields(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public);
				foreach (System.Reflection.FieldInfo fld in srcFields)
				{
					string fldName = fld.Name;
					object fldValue = fld.GetValue(source);
					System.Reflection.FieldInfo dstFld = t.GetField(fldName, System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public);
					dstFld.SetValue(destination, fldValue);
				}
			}
		}

		/// <summary>
		/// Gets all child controls of a specific type.
		/// </summary>
		/// <param name="parentControl">Control to search</param>
		/// <param name="searchType">The type to search for</param>
		/// <param name="notRecursiveType">If not null, the child controls of this type are not searched</param>
		/// <param name="onlyVisible">if true gets only the visible controls</param>
		/// <returns></returns>
		public static ArrayList GetChildControlsOfType (Control parentControl, Type searchType, Type notRecursiveType, bool onlyVisible)
		{
			ArrayList list = new ArrayList();

			for (int i=0; i < parentControl.Controls.Count; i++)
			{
				Control con = parentControl.Controls[i];
				if (onlyVisible && !con.Visible)
					continue;

				if ( searchType.IsInstanceOfType(con) )
					list.Add (con);

				if ( notRecursiveType == null || !notRecursiveType.IsInstanceOfType(con) )
					list.AddRange (GetChildControlsOfType(con, searchType, notRecursiveType, onlyVisible));
			}

			return list;
		}

		/// <summary>
		/// Returns an array of types from the input object array
		/// </summary>
		/// <param name="parameters"></param>
		/// <returns></returns>
		private static Type[] GetTypesOfParameters(object[] parameters)
		{
			if (parameters != null)
			{
				Type[] types = new Type[parameters.Length];
				for (int i = 0; i < parameters.Length; i++)
				{
					types[i] = parameters[i].GetType();
				}
				return types;
			}
			return null;
		}

		private Util()
		{
		}
	}
}

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


Written By
Web Developer
Greece Greece
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions