/*
 * My Ajax.NET
 *
 * Release 8
 *
 * See release history at the end of this file for changes.
 *
 * This code is intended to be simple enough so that anybody can
 * understand it and, therefore, feel good about including it in
 * their own projects and even modifying it. This is YOUR Ajax.NET.
 *
 * Originally written by Jason Diamond but donated to the Public
 * Domain which means anybody can use it for any reason. If you make
 * any modifications and want to contribute them to the "official"
 * release, please send them to <mailto:jason@diamond.name>.
 *
 * People who contributed code:
 *
 * Jason Diamond <http://jason.diamond.name/>
 * Rick Strahl <http://www.west-wind.com/>
 * Thomas F Kelly, Jr.
 * Chris Payne
 * Scott Ford
 *
 */

using System;
using System.Collections;
using System.Data;
using System.Globalization;
using System.Reflection;
using System.Runtime.Serialization;
using System.Text;
using System.Web;
using System.Web.UI;

namespace Ajax
{
	[AttributeUsage(AttributeTargets.Method)]
	public class MethodAttribute : Attribute
	{
		public MethodAttribute()
		{
			_includeControlValuesWithCallBack = true;
		}

		private string _clientSideName;

		public string ClientSideName
		{
			get { return _clientSideName; }
			set { _clientSideName = value; }
		}

		private bool _includeControlValuesWithCallBack;

		public bool IncludeControlValuesWithCallBack
		{
			get { return _includeControlValuesWithCallBack; }
			set { _includeControlValuesWithCallBack = value; }
		}
	}

	[Flags]
	public enum Debug
	{
		None         = 0,
		RequestText  = 1,
		ResponseText = 2,
		Errors       = 4,
		All          = 7
	}

	public class Manager
	{
		public static void RegisterLoginPage(Page page)
		{
			page.PreRender += new EventHandler(OnLoginPagePreRender);
		}

		private static void OnLoginPagePreRender(object sender, EventArgs e)
		{
			HttpContext context = HttpContext.Current;
			HttpRequest req = context.Request;
			string returnURL = req.QueryString["ReturnURL"];
			if (returnURL != null && returnURL.Length > 0)
			{
				returnURL = context.Server.UrlDecode(returnURL);
				if (returnURL.EndsWith("?Ajax_CallBack=true") ||
					returnURL.EndsWith("&Ajax_CallBack=true"))
				{
					HttpResponse resp = context.Response;
					WriteResult(resp, null, "LOGIN");
					resp.End();
				}
			}
		}

		public static void Register(Page page)
		{
			Register(page, page.GetType().FullName, false, Debug.None);
		}

		public static void Register(Page page, string prefix)
		{
			Register(page, prefix, false, Debug.None);
		}

		public static void Register(Page page, Debug debug)
		{
			Register(page, page.GetType().FullName, false, debug);
		}

		public static void Register(Page page, string prefix, Debug debug)
		{
			Register(page, prefix, false, debug);
		}

		public static void Register(Control control)
		{
			Register(control, control.GetType().FullName, true, Debug.None);
		}

		public static void Register(Control control, string prefix)
		{
			Register(control, prefix, true, Debug.None);
		}

		public static void Register(Control control, Debug debug)
		{
			Register(control, control.GetType().FullName, true, debug);
		}

		public static void Register(Control control, string prefix, bool requireID, Debug debug)
		{
			RegisterCount += 1;

			HttpContext context = HttpContext.Current;

			string url;
			string currentExecutionFilePath = context.Request.CurrentExecutionFilePath;
			string filePath = context.Request.FilePath;
			if (object.ReferenceEquals(currentExecutionFilePath, filePath))
			{
				url = filePath;
				int lastSlash = url.LastIndexOf('/');
				if (lastSlash != -1)
				{
					url = url.Substring(lastSlash + 1);
				}
			}
			else
			{
				Uri from = new Uri("file://foo" + filePath, true);
				Uri to = new Uri("file://foo" + currentExecutionFilePath, true);
				url = from.MakeRelative(to);
			}
			string queryString = context.Request.Url.Query;
			if (queryString != null && queryString.Length != 0)
			{
				url = url + queryString + "&Ajax_CallBack=true";
			}
			else
			{
				url += "?Ajax_CallBack=true";
			}

			string pageScript = @"
<script language=""javascript"" type=""text/javascript"">
function Ajax_GetXMLHttpRequest() {
	if (window.XMLHttpRequest) {
		return new XMLHttpRequest();
	} else {
		if (window.Ajax_XMLHttpRequestProgID) {
			return new ActiveXObject(window.Ajax_XMLHttpRequestProgID);
		} else {
			var progIDs = [""Msxml2.XMLHTTP.5.0"", ""Msxml2.XMLHTTP.4.0"", ""MSXML2.XMLHTTP.3.0"", ""MSXML2.XMLHTTP"", ""Microsoft.XMLHTTP""];
			for (var i = 0; i < progIDs.length; ++i) {
				var progID = progIDs[i];
				try {
					var x = new ActiveXObject(progID);
					window.Ajax_XMLHttpRequestProgID = progID;
					return x;
				} catch (e) {
				}
			}
		}
	}
	return null;
}
function Ajax_CallBack(type, id, method, args, clientCallBack, debugRequestText, debugResponseText, debugErrors, includeControlValuesWithCallBack) {
	var x = Ajax_GetXMLHttpRequest();
	var result = null;
	if (!x) {
		result = { ""value"":null, ""error"": ""NOXMLHTTP""};
		if (debugErrors) {
			alert(""error: "" + result.error);
		}
		if (clientCallBack) {
			clientCallBack(result);
		}
		return result;
	}
	var url = document.location.href.substring(0, document.location.href.length - document.location.hash.length);
	url = """ + url + @""";
	x.open(""POST"", url, clientCallBack ? true : false);
	x.setRequestHeader(""Content-Type"", ""application/x-www-form-urlencoded; charset=utf-8"");
	if (clientCallBack) {
		x.onreadystatechange = function() {
			if (x.readyState != 4) {
				return;
			}
			if (debugResponseText) {
				alert(x.responseText);
			}
			var result = eval(""("" + x.responseText + "")"");
			if (debugErrors && result.error) {
				alert(""error: "" + result.error);
			}
			clientCallBack(result);
		}
	}
	var encodedData = ""Ajax_CallBackType="" + type;
	if (id) {
		encodedData += ""&Ajax_CallBackID="" + id.split(""$"").join("":"");
	}
	encodedData += ""&Ajax_CallBackMethod="" + method;
	if (args) {
		for (var i in args) {
			encodedData += ""&Ajax_CallBackArgument"" + i + ""="" + encodeURIComponent(args[i]);
		}
	}
	if (includeControlValuesWithCallBack && document.forms.length > 0) {
		var form = document.forms[0];
		for (var i = 0; i < form.length; ++i) {
			var element = form.elements[i];
			if (element.name) {
				var elementValue = null;
				if (element.nodeName == ""INPUT"") {
					var inputType = element.getAttribute(""TYPE"").toUpperCase();
					if (inputType == ""TEXT"" || inputType == ""PASSWORD"" || inputType == ""HIDDEN"") {
						elementValue = element.value;
					} else if (inputType == ""CHECKBOX"" || inputType == ""RADIO"") {
						if (element.checked) {
							elementValue = element.value;
						}
					}
				} else if (element.nodeName == ""SELECT"") {
					elementValue = element.value;
				} else if (element.nodeName == ""TEXTAREA"") {
					elementValue = element.value;
				}
				if (elementValue) {
					encodedData += ""&"" + element.name + ""="" + encodeURIComponent(elementValue);
				}
			}
		}
	}
	if (debugRequestText) {
		alert(encodedData);
	}
	x.send(encodedData);
	if (!clientCallBack) {
		if (debugResponseText) {
			alert(x.responseText);
		}
		result = eval(""("" + x.responseText + "")"");
		if (debugErrors && result.error) {
			alert(""error: "" + result.error);
		}
	}
	delete x;
	return result;
}
</script>";
			control.Page.RegisterClientScriptBlock(typeof(Ajax.Manager).FullName, pageScript);
			Type type = control.GetType();
			StringBuilder controlScript = new StringBuilder();
			controlScript.Append("\n<script language=\"javascript\" type=\"text/javascript\">\n");
			string[] prefixParts = prefix.Split('.', '+');
			controlScript.AppendFormat("var {0} = {{\n", prefixParts[0]);
			for (int i = 1; i < prefixParts.Length; ++i)
			{
				controlScript.AppendFormat("\"{0}\": {{\n", prefixParts[i]);
			}
			int methodCount = 0;
			foreach (MethodInfo methodInfo in type.GetMethods(BindingFlags.Instance | BindingFlags.Public))
			{
				object[] attributes = methodInfo.GetCustomAttributes(typeof(Ajax.MethodAttribute), true);
				if (attributes != null && attributes.Length > 0)
				{
					MethodAttribute methodAttribute = attributes[0] as MethodAttribute;
					string clientSideName = methodAttribute.ClientSideName != null ? methodAttribute.ClientSideName : methodInfo.Name;
					++methodCount;
					controlScript.AppendFormat("\n\"{0}\": function(", clientSideName);
					if (requireID)
					{
						controlScript.AppendFormat("id, ");
					}
					foreach (ParameterInfo paramInfo in methodInfo.GetParameters())
					{
						controlScript.Append(paramInfo.Name + ", ");
					}
					controlScript.AppendFormat(
						"clientCallBack) {{\n\treturn Ajax_CallBack('{0}', {1}, '{2}', [",
						type.FullName,
						requireID ? "id" : "null",
						methodInfo.Name);
					int paramCount = 0;
					foreach (ParameterInfo paramInfo in methodInfo.GetParameters())
					{
						++paramCount;
						controlScript.Append(paramInfo.Name);
						controlScript.Append(",");
					}
					// If parameters were written, remove the
					// trailing comma.
					if (paramCount > 0)
					{
						--controlScript.Length;
					}
					controlScript.AppendFormat("], clientCallBack, {0}, {1}, {2}, {3});\n}}",
						(debug & Debug.RequestText) == Debug.RequestText ? "true" : "false",
						(debug & Debug.ResponseText) == Debug.ResponseText ? "true" : "false",
						(debug & Debug.Errors) == Debug.Errors ? "true" : "false",
						(methodAttribute.IncludeControlValuesWithCallBack ? "true" : "false"));

					controlScript.Append(",\n");
				}
			}
			// If no methods were found, you probably forget to add
			// [Ajax.Method] attributes to your methods or they weren't public.
			if (methodCount == 0)
			{
				throw new ApplicationException(string.Format("{0} does not contain any public methods with the Ajax.Method attribute.", type.FullName));
			}
			// Remove the trailing comma and newline.
			controlScript.Length -= 2;
			controlScript.Append("\n\n");
			for (int i = 0; i < prefixParts.Length; ++i)
			{
				controlScript.Append("}");
			}
			controlScript.Append(";\n</script>");
			control.Page.RegisterClientScriptBlock("Ajax.Manager:" + type.FullName, controlScript.ToString());
			control.PreRender += new EventHandler(OnPreRender);
		}

		public static int RegisterCount
		{
			get
			{
				return HttpContext.Current.Items.Contains("Ajax_RegisterCount")
					? (int)HttpContext.Current.Items["Ajax_RegisterCount"]
					: 0;
			}

			set
			{
				HttpContext.Current.Items["Ajax_RegisterCount"] = value;
			}
		}

		public static int CallBackAttemptCount
		{
			get
			{
				return HttpContext.Current.Items.Contains("Ajax_CallBackAttemptCount")
					? (int)HttpContext.Current.Items["Ajax_CallBackAttemptCount"]
					: 0;
			}

			set
			{
				HttpContext.Current.Items["Ajax_CallBackAttemptCount"] = value;
			}
		}

		public static string CallBackType
		{
			get
			{
				return HttpContext.Current.Request.Form["Ajax_CallBackType"];
			}
		}

		public static string CallBackID
		{
			get
			{
				return HttpContext.Current.Request.Form["Ajax_CallBackID"];
			}
		}

		public static string CallBackMethod
		{
			get
			{
				return HttpContext.Current.Request.Form["Ajax_CallBackMethod"];
			}
		}

		public static bool IsCallBack
		{
			get
			{
				return CallBackType != null;
			}
		}

		static void OnPreRender(object s, EventArgs e)
		{
			if (IsCallBack)
			{
				CallBackAttemptCount += 1;
				Control control = s as Control;
				MethodInfo methodInfo = null;
				if (control != null)
				{
					if (control.GetType().FullName == CallBackType)
					{
						if (control is Page || control.ClientID == CallBackID)
						{
							methodInfo = FindTargetMethod(control);
						}
					}
				}
				object val = null;
				string error = null;
				HttpResponse resp = HttpContext.Current.Response;
				if (methodInfo != null)
				{
					try
					{
						object[] parameters = ConvertParameters(methodInfo, HttpContext.Current.Request);
						val = InvokeMethod(control, methodInfo, parameters);
					}
					catch (Exception ex)
					{
						error = ex.Message;
					}
					WriteResult(resp, val, error);
					resp.End();
				}
				else if (CallBackAttemptCount == RegisterCount)
				{
					// If you're getting this error, it might be because you're calling
					// back with the wrong control ID or you forget to register your page
					// by calling Ajax.Manager.Register. You need to register your page
					// during both GET and POST requests so don't hide your calls to
					// Ajax.Manager.Register inside a "if (!IsPostBack)" block.
					error = "Call back method not found.";
					WriteResult(resp, val, error);
					resp.End();
				}
			}
		}

		static MethodInfo FindTargetMethod(Control control)
		{
			Type type = control.GetType();
			string methodName = CallBackMethod;
			MethodInfo methodInfo = type.GetMethod(methodName);
			if (methodInfo != null)
			{
				object[] methodAttributes = methodInfo.GetCustomAttributes(typeof(Ajax.MethodAttribute), true);
				if (methodAttributes.Length > 0)
				{
					return methodInfo;
				}
			}
			return null;
		}

		static object[] ConvertParameters(
			MethodInfo methodInfo,
			HttpRequest req)
		{
			object[] parameters = new object[methodInfo.GetParameters().Length];
			int i = 0;
			foreach (ParameterInfo paramInfo in methodInfo.GetParameters())
			{
				object param = null;
				string paramValue = req.Form["Ajax_CallBackArgument" + i];
				if (paramValue != null)
				{
					if (paramInfo.ParameterType.IsArray) 
					{
						Type type = paramInfo.ParameterType.GetElementType();
						string[] splits = paramValue.Split(',');
						Array array = Array.CreateInstance(type, splits.Length);
						for (int index = 0; index < splits.Length; index++) 
						{
							array.SetValue(Convert.ChangeType(splits[index], type), index);
						}
						param = array;
					}
					else
					{
						param = Convert.ChangeType(paramValue, paramInfo.ParameterType);
					}
				}
				parameters[i] = param;
				++i;
			}
			return parameters;
		}

		static object InvokeMethod(
			Control control,
			MethodInfo methodInfo,
			object[] parameters)
		{
			object val = null;
			try
			{
				val = methodInfo.Invoke(control, parameters);
			}
			catch (TargetInvocationException ex)
			{
				// TargetInvocationExceptions should have the actual
				// exception the method threw in its InnerException
				// property.
				if (ex.InnerException != null)
				{
					throw ex.InnerException;
				}
				else
				{
					throw ex;
				}
			}
			return val;
		}

		public static void WriteResult(
			HttpResponse resp,
			object val,
			string error)
		{
			resp.ContentType = "application/x-javascript";
			resp.Cache.SetCacheability(HttpCacheability.NoCache);
			StringBuilder sb = new StringBuilder();
			try
			{
				WriteValueAndError(sb, val, error);
			}
			catch (Exception ex)
			{
				// If an exception was thrown while formatting the
				// result value, we need to discard whatever was
				// written and start over with nothing but the error
				// message.
				sb.Length = 0;
				WriteValueAndError(sb, null, ex.Message);
			}
			resp.Write(sb.ToString());
		}

		static void WriteValueAndError(StringBuilder sb, object val, string error)
		{
			sb.Append("{\"value\":");
			WriteValue(sb, val);
			sb.Append(",\"error\":");
			WriteValue(sb, error);
			sb.Append("}");
		}

		static void WriteValue(StringBuilder sb, object val)
		{
			if (val == null || val == System.DBNull.Value)
			{
				sb.Append("null");
			}
			else if (val is string || val is Guid)
			{
				WriteString(sb, val.ToString());
			}
			else if (val is bool)
			{
				sb.Append(val.ToString().ToLower());
			}
			else if (val is double ||
				val is float ||
				val is long ||
				val is int ||
				val is short ||
				val is byte ||
				val is decimal)
			{
				sb.Append(val);
			}
			else if (val is DateTime)
			{
				sb.Append("new Date(\"");
				sb.Append(((DateTime)val).ToString("MMMM, d yyyy HH:mm:ss", new CultureInfo("en-US", false).DateTimeFormat));
				sb.Append("\")");
			}
			else if (val is DataSet)
			{
				WriteDataSet(sb, val as DataSet);
			}
			else if (val is DataTable)
			{
				WriteDataTable(sb, val as DataTable);
			}						
			else if (val is DataRow)
			{
				WriteDataRow(sb, val as DataRow);
			}
			else if (val is IEnumerable)
			{
				WriteEnumerable(sb, val as IEnumerable);
			}
			else
			{
				WriteSerializable(sb, val);
			}
		}

		static void WriteString(StringBuilder sb, string s)
		{
			sb.Append("\"");
			foreach (char c in s)
			{
				switch (c)
				{
					case '\"':
						sb.Append("\\\"");
						break;
					case '\\':
						sb.Append("\\\\");
						break;
					case '\b':
						sb.Append("\\b");
						break;
					case '\f':
						sb.Append("\\f");
						break;
					case '\n':
						sb.Append("\\n");
						break;
					case '\r':
						sb.Append("\\r");
						break;
					case '\t':
						sb.Append("\\t");
						break;
					default:
						int i = (int)c;
						if (i < 32 || i > 127)
						{
							sb.AppendFormat("\\u{0:X04}", i);
						}
						else
						{
							sb.Append(c);
						}
						break;
				}
			}
			sb.Append("\"");
		}

		static void WriteDataSet(StringBuilder sb, DataSet ds)
		{
			sb.Append("{\"Tables\":{");
			foreach (DataTable table in ds.Tables)
			{
				sb.AppendFormat("\"{0}\":", table.TableName);
				WriteDataTable(sb, table);
				sb.Append(",");
			}
			// Remove the trailing comma.
			if (ds.Tables.Count > 0) 
			{
				--sb.Length;
			}
			sb.Append("}}");
		}

		static void WriteDataTable(StringBuilder sb, DataTable table)
		{
			sb.Append("{\"Rows\":[");
			foreach (DataRow row in table.Rows)
			{
				WriteDataRow(sb, row);
				sb.Append(",");
			}
			// Remove the trailing comma.
			if (table.Rows.Count > 0) 
			{
				--sb.Length;
			}
			sb.Append("]}");
		}

		static void WriteDataRow(StringBuilder sb, DataRow row)
		{
			sb.Append("{");
			foreach (DataColumn column in row.Table.Columns)
			{
				sb.AppendFormat("\"{0}\":", column.ColumnName);
				WriteValue(sb, row[column]);
				sb.Append(",");
			}
			// Remove the trailing comma.
			if (row.Table.Columns.Count > 0) 
			{
				--sb.Length;
			}
			sb.Append("}");
		}

		static void WriteEnumerable(StringBuilder sb, IEnumerable e)
		{
			bool hasItems = false;
			sb.Append("[");
			foreach (object val in e)
			{
				WriteValue(sb, val);
				sb.Append(",");
				hasItems = true;
			}
			// Remove the trailing comma.
			if (hasItems)
			{
				--sb.Length;
			}
			sb.Append("]");
		}

		static void WriteSerializable(StringBuilder sb, object o)
		{
			MemberInfo[] members = o.GetType().GetMembers(BindingFlags.Instance | BindingFlags.Public);
			sb.Append("{");
			bool hasMembers = false;
			foreach (MemberInfo member in members)
			{
				bool hasValue = false;
				object val = null;
				if ((member.MemberType & MemberTypes.Field) == MemberTypes.Field)
				{
					FieldInfo field = (FieldInfo)member;
					val = field.GetValue(o);
					hasValue = true;
				}
				else if ((member.MemberType & MemberTypes.Property) == MemberTypes.Property)
				{
					PropertyInfo property = (PropertyInfo)member;
					if (property.CanRead && property.GetIndexParameters().Length == 0)
					{
						val = property.GetValue(o, null);
						hasValue = true;
					}
				}
				if (hasValue)
				{
					sb.Append("\"");
					sb.Append(member.Name);
					sb.Append("\":");
					WriteValue(sb, val);
					sb.Append(",");
					hasMembers = true;
				}
			}
			if (hasMembers)
			{
				--sb.Length;
			}
			sb.Append("}");
		}
	}
}

/*
 * Release History
 *
 * Release 1, 07/06/2005
 *   - Initial release.
 *
 * Release 2, 07/26/2005
 *   - Fixed bug which triggered the last server-side button's
 *     click event when calling back to the page as reported
 *     by Donald Hughes.
 *   - Set cacheability to NoCache for call back requests.
 *
 * Release 3, 07/29/2005
 *   - Merged in changes from Rick Strahl.
 *     - Added support for bool and DBNull.
 *     - Return the InnerException thrown by invoked methods.
 *   - Started using James Curran's technique for removing the
 *     trailing commas when outputting lists.
 *   - Factored out most of the code that was in OnPreRender to
 *     separate methods.
 *   - For security reasons, the code now checks to see if the call
 *     back target has the Ajax.Method on it before trying to invoke
 *     it.
 *
 * Release 4, 08/16/2005
 *   - Made error message more helpful when developers forget to add
 *     the Ajax.Method attribute any public methods.
 *   - Merged in some support for select elements from Thomas F Kelly, Jr.
 *   - Merged in Chris Payne's fixes for empty sets, tables, rows, and
 *     enumerables.
 *
 * Release 5, 08/18/2005
 *   - Controls (except Pages) that register for call backs are now
 *     required to pass in the control ID as the first parameter to their
 *     client-side call back functions. This is necessary to make call backs
 *     to specific instances when there are multiple controls of the same
 *     type on the same page. This problem was reported by Andrew Appell.
 *   - To support the above, the old Ajax_CallBackTarget parameter was
 *     split into three new parameters: Ajax_CallBackType, Ajax_CallBackID,
 *     and Ajax_CallBackMethod.
 *   - Added support for posting back values of textarea elements (for
 *     server-side TextBox controls with TextMode set to MultiLine).
 *   - Return values that don't have explicit support in Ajax.Manager
 *     are now returned as simple strings by calling their ToString method.
 *     This was inspired by Michael Stuart's request for adding support for
 *     returning Guids.
 *   - This release now includes a working example of a DataGrid-derived
 *     control that uses My Ajax.NET to do asynchronous call backs. Editing,
 *     updating, cancelling, and sorting should all work just like normal
 *     DataGrids but without the user-visible post backs. See
 *     AjaxTest\GridTest.aspx for an example.
 * 
 * Release 6, 08/25/2005
 *   - Fixed bug with trying to match up call backs to child controls as
 *     reported by Jim Matteson and Justin Keyes.
 *   - Added some code to return an error when a call back method couldn't be
 *     found instead of returning the normal HTML for the page.
 *   - Added patch by Scott Ford to support passing arrays from client
 *     to server. It still needs some work to make it handle all cases
 *     correctly.
 *   - We now clear the __EVENTTARGET hidden input field after fetching a new
 *     rendering of the Ajax.Grid. This should prevent the grid from misfiring
 *     events on the server when other (non-grid) call backs are made.
 *   - Added locale-independant DateTime formatting as suggested by
 *     Angel Machín.
 *   - Added language="javascript" and type="text/javascript" attributes to
 *     all of the script elements that get injected into the page as suggested
 *     by Chris Kennedy.
 *   - Tried to fix Justin Keyes' Server.Transfer problem. The call back URL
 *     is now the current request's CurrentExecutionFilePath instead of the
 *     URL the browser is pointed at.
 * 
 * Release 7, 09/06/2005
 *   - Added a IncludeControlValuesWithCallBack property to the Ajax.Method
 *     attribute. This was requested by Michael Retzler who has a page with
 *     hundreds of controls on the page which do not need to be posted back
 *     to the server for his call backs. The default value for this property
 *     is true which gives the exact same behavior as before.
 *   - Updated the Ajax_GetXMLHttpRequest function to try using different
 *     versions of the MSXML ProgID starting with the moste recent. The first
 *     ProgID that works is saved to avoid doing that work on subsequest
 *     invocations.
 *   - Added some code to compute the relative URL for the current page so
 *     that call backs work when cookieless session state is being used. This
 *     code was basically stolen from HtmlForm's private GetActionAttribute
 *     method and the internal UrlPath class' MakeRelative method. I love
 *     Reflector.
 *   - Fixed bug clearing the __EVENTTARGET hidden input field with Ajax.Grid
 *     when that field had to be dynamically created.
 *   - Added a more informative exception message whan Ajax.Grid is not
 *     contained by an HtmlForm.
 *   - Added a comment explaining why the "Call back method not found." error
 *     might be occurring as suggested by Michael Freidgeim.
 *   - Fixed bug updating wrong grid when multiple Ajax.Grid controls appear
 *     on the same page as found by Michael Freidgeim.
 *   - Added a charset parameter to the Content-Type header when posting back
 *     to the server to perform a call back. This is necessary for when the
 *     application has a requestEncoding in its web.config file that isn't
 *     UTF-8. This problem was reported by Akio Kasai.
 *   - Added missing support for returning decimal values as reported by Rick
 *     Rick Strahl.
 *   - Added support for returning serializable objects as suggested by
 *     just about everybody. It doesn't maintain object identity and it
 *     doesn't protect against circular references (yet)!
 *
 * Release 8, 09/14/2005
 *   - Made sure to include the query string part of the URL to post back to.
 *     This should help people who use DotNetNuke where there's always a
 *     query string parameters in the URLs.
 *   - Added a "Ajax_CallBack=true" query string parameter to the call back
 *     URL. This will trick browsers into thinking the call back URL is
 *     different than the page URL so should allow the browser to cache the
 *     page but not the call back response. This query string parameter is also
 *     used to detect if a call back was redirected to the login page when
 *     using Forms authentication.
 *   - Added a RegisterLoginPage method to Ajax.Manager. This is necessary
 *     when using Forms authentication and the user's authentication cookie
 *     expires. The login page will check for a URL in the ReturnURL query
 *     string parameter. If the ReturnURL parameter has a Ajax_CallBack=true
 *     parameter, that means this is a redirected call back and the server
 *     needs to return an error, "LOGIN", to the client. If the client
 *     detects that string in the error property, it can do whatever it needs
 *     to in order to get the client to log back in.
 *   - Added a new ClientSideName property to the Ajax.Method attribute that
 *     lets you specify an alternate client-side function name instead of
 *     using the server-side method name. This was suggested by Andrew
 *     Revinsky.
 *   - Removed the Ajax.MethodAttribute(bool) constructor since I suspect
 *     we're going to be adding more properties in the future to that
 *     attribute and I don't want to have to maintain multiple overloads
 *     that cover all the combinations of those properties.
 *   - Added some better checking to see if XMLHttpRequest is supported by
 *     the browser. If it's not, every attempt to perform a call back will
 *     return (or pass in to your asynchronous call back function) a result
 *     object with an error value of "NOXMLHTTP".
 *   - Removed the requirement to be serializable to return custom objects.
 *     Any type that's not explicitly supported now gets "serialized" (if
 *     possible).
 */
