Click here to Skip to main content
15,884,298 members
Articles / Web Development / ASP.NET

ASP.NET Advanced Generic Handler ASHX

Rate me:
Please Sign up or sign in to vote.
4.74/5 (49 votes)
9 Jun 2013CPOL5 min read 320.6K   10.2K   138  
Take your Generic Handlers to the next level...
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Web;
using System.Web.Script.Serialization;
using System.IO;
using System.ComponentModel;
using System.Text.RegularExpressions;

namespace App.Utilities.Web.Handlers
{
	public class AjaxCallSignature
	{
		public AjaxCallSignature(HttpContext context)
		{
			args = new Dictionary<string, object>();
			method = string.Empty;
			string nullKeyParameter = context.Request.QueryString[null];

			if (new List<string>() { "POST", "PUT", "DELETE" }.Contains(context.Request.RequestType.ToUpper()))
			{
				string[] requestParams = context.Request.Params.AllKeys;
				foreach (var item in requestParams)
				{
					if (item.ToLower() == "method")
					{
						method = context.Request.Params[item];
					}
					else if (item.ToLower().StartsWith("args["))
					{
						string key = item.Trim().TrimEnd(']').Substring(5);
						key = key.Trim().Replace("][", "+");

						string value = context.Request.Params[item];
						args.Add(key, value);
					}
					else
					{
						string key = item;
						string value = context.Request.Params[item];
						args.Add(key, value);
					}
				}
			}
			else if (context.Request.RequestType.ToUpper() == "GET")
			{
				// evaluate the data passed as json
				if (!string.IsNullOrEmpty(nullKeyParameter))
				{
					if (nullKeyParameter.ToLower() == "help")
					{
						method = "help";
						return;
					}
					else
					{
						object json = null;
						JavaScriptSerializer serializer = new JavaScriptSerializer();
						json = serializer.DeserializeObject(context.Request.QueryString[null]);

						try
						{
							Dictionary<string, Object> dict = (Dictionary<string, Object>)json;

							if (dict.ContainsKey("method"))
								method = dict["method"].ToString();
							else
								throw new Exception("Invalid BaseHandler call. MethodName parameter is mandatory in json object.");

							if (dict.ContainsKey("returntype"))
								returnType = dict["returntype"].ToString();

							if (dict.ContainsKey("args"))
								args = (Dictionary<string, Object>)dict["args"];
							else
								args = new Dictionary<string, object>();
						}
						catch
						{
							throw new InvalidCastException("Unable to cast json object to AjaxCallSignature");
						}
					}
				}

				// evaluate data passed as querystring params
				foreach (string key in context.Request.QueryString.Keys)
				{
					if (key == null)
						continue;

					if (key.ToLower() == "method")
					{
						if (string.IsNullOrEmpty(method))
						{
							method = context.Request.QueryString[key];
						}
						else
						{
							throw new Exception("Method name was already specified on the json data. Specify the method name only once, either on QueryString params or on the json data.");
						}
					}
					else if (key.ToLower() == "returntype")
					{
						returnType = context.Request.QueryString[key];
					}
					else if (key.ToLower().StartsWith("args["))
					{
						string _key = key.Trim().Substring(5).TrimEnd(']').Replace("][", "+");
						args.Add(_key, context.Request.QueryString[key]);
					}
					else
					{
						args.Add(key, context.Request.QueryString[key]);
					}
				}
			}
		}

		public string method { get; set; }
		public string returnType { get; set; }
		public Dictionary<string, object> args { get; set; }

		public object Invoke(BaseHandler handler, HttpContext context)
		{
			MethodInfo m = null;
			if (string.IsNullOrEmpty(method))
			{
				// call the request method
				// if no method is passed then well call the method by HTTP verb (GET, POST, DELETE, UPDATE)
				method = context.Request.RequestType.ToUpper();
			}
			m = handler.GetType().GetMethod(method);

			List<object> a = new List<object>();

			if (m == null)
			{
				if (method.ToLower() == "help")
				{
					m = handler.GetType().BaseType.GetMethod("Help");
				}
				else
				{
					throw new Exception(string.Format("Method {0} not found on Handler {1}.", method, this.GetType().ToString()));
				}
			}
			else
			{
				// evaluate the handler and method attributes against Http allowed verbs
				/*
				 The logic here is:
				 *	-> if no attribute is found means it allows every verb
				 *	-> is a method have verb attbibutes defined then it will ignore the ones on the class
				 *	-> verb attributes on the class are applied to all methods without verb attribues
				 */
				var handlerSupportedVerbs = handler.GetType().GetCustomAttributes(typeof(HttpVerbAttribute), true).Cast<HttpVerbAttribute>();
				var methodSupportedVerbs = m.GetCustomAttributes(typeof(HttpVerbAttribute), true).Cast<HttpVerbAttribute>();

				bool VerbAllowedOnMethod = (methodSupportedVerbs.Count() == 0);
				bool VerbAllowedOnHandler = (handlerSupportedVerbs.Count() == 0);
				if (methodSupportedVerbs.Count() > 0)
				{
					VerbAllowedOnMethod = methodSupportedVerbs.FirstOrDefault(x => x.HttpVerb == context.Request.RequestType.ToUpper()) != null;
				}
				else if (handlerSupportedVerbs.Count() > 0)
				{
					VerbAllowedOnHandler = handlerSupportedVerbs.FirstOrDefault(x => x.HttpVerb == context.Request.RequestType.ToUpper()) != null;
				}

				if (!VerbAllowedOnMethod || !VerbAllowedOnHandler)
				{
					throw new HttpVerbNotAllowedException();
				}

				// security validation: Search for RequireAuthenticationAttribute on the method
				//		value=true the user must be authenticated (only supports FromsAuthentication for now
				//		value=false invoke the method
				object[] attrs = m.GetCustomAttributes(typeof(RequireAuthenticationAttribute), true);
				if (attrs != null && attrs.Length > 0)
				{

					if (!context.Request.IsAuthenticated && ((RequireAuthenticationAttribute)attrs[0]).RequireAuthentication)
					{
						throw new InvalidOperationException("Method [" + m.Name + "] Requires authentication");
					}
				}
			}

			foreach (var param in m.GetParameters())
			{
				a.Add(ProcessProperty(param.Name, param.ParameterType, string.Empty));
			}

			// OnMethodInvoke -> Invoke -> AfterMethodInvoke
			OnMethodInvokeArgs cancelInvoke = new OnMethodInvokeArgs(m);
			handler.OnMethodInvoke(cancelInvoke);

			object invokeResult = null;
			if (!cancelInvoke.Cancel)
			{
				invokeResult = m.Invoke(handler, a.ToArray());
				handler.AfterMethodInvoke(invokeResult);
			}

			return invokeResult;
		}


		/// <summary>
		/// 
		/// </summary>
		/// <param name="propertyName"></param>
		/// <param name="propertyType"></param>
		/// <param name="parentNamespace">represents the full path of the parent node of the input parameter</param>
		/// <returns></returns>
		private object ProcessProperty(string propertyName, Type propertyType, string parentNamespace)
		{
			if (propertyType.IsArray || (propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(List<>)))
			{
				return HydrateArray(propertyName, propertyType, parentNamespace);
			}
			else if (propertyType.IsClass && !propertyType.Equals(typeof(String)))
			{
				return HydrateClass(propertyName, propertyType, parentNamespace);
			}
			else
			{
				return HydrateValue(propertyName, propertyType, parentNamespace);
			}
		}

		private object HydrateArray(string propertyName, Type propertyType, string parentNamespace)
		{
			Array result = null;
			Type elementType;
			
			if (propertyType.IsGenericType)
				elementType = propertyType.GetGenericArguments()[0];
			else
				elementType = propertyType.GetElementType();

			String propFQN = String.IsNullOrEmpty(parentNamespace) ? propertyName : parentNamespace + "+" + propertyName;

			if (elementType.IsValueType)
			{
			    TypeConverter conv = TypeDescriptor.GetConverter(elementType);
			    string[] values = args[propFQN + "+"].ToString().Split(new char[] { ',' });

				result = Array.CreateInstance(elementType, values.Length);

			    for (int i = 0; i < values.Length; i++)
			    {
			        result.SetValue(conv.ConvertFromString(values[i]), i);
			    }
			}
			else
			{
				// get the properties in the current nesting depth
				var objectProperties = args.Keys.ToList().FindAll(k => k.StartsWith(propFQN + "+"));

				// get the number of items in the array
				int max_index = 0;
				foreach (var p in objectProperties)
				{
					string idx = p.Remove(0, propFQN.Length + 1);
					idx = idx.Substring(0, idx.IndexOf('+'));
					int i = Convert.ToInt32(idx);
					if (i > max_index) max_index = i;
				}

				// create the instance of the array
				result = Array.CreateInstance(elementType, max_index + 1);
				for (int i = 0; i <= max_index; i++)
				{
					String nsPrefix = String.Format("{0}+{1}", propFQN, i.ToString());
					result.SetValue(ProcessProperty(propertyName + "+" + i.ToString(), result.GetType().GetElementType(), parentNamespace), i);
				}
			}

			if (propertyType.IsGenericType) 
			{
				return Activator.CreateInstance(propertyType, new object[] { result });
			}
 			else
				return result;
		}

		/// <summary>
		/// Hydrates CLR primitive types
		/// </summary>
		/// <param name="param"></param>
		/// <returns></returns>
		public object HydrateValue(string propertyName, Type propertyType, String parentNamespace)
		{
			String propFQN = string.IsNullOrEmpty(parentNamespace) ? propertyName : parentNamespace + "+" + propertyName;
			if (args.Keys.Contains(propFQN))
			{
				// its usual to pass an empty json string property but casting it to certain types will throw an exception
				if (string.IsNullOrEmpty(args[propFQN].ToString()) || args[propFQN].ToString() == "null" || args[propFQN].ToString() == "undefined")
				{
					// handle numerics. convert null or empty input values to 0
					if (propertyType.Equals(typeof(System.Int16)) || propertyType.Equals(typeof(System.Int32)) ||
						propertyType.Equals(typeof(System.Int64)) || propertyType.Equals(typeof(System.Decimal)) ||
						propertyType.Equals(typeof(System.Double)) || propertyType.Equals(typeof(System.Byte)))
					{
						args[propFQN] = 0;
					}
					else if (propertyType.Equals(typeof(System.Guid)))
					{
						args[propFQN] = new Guid();
					}
					else if (propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
					{
						args[propFQN] = null;
					}
				}

				// evaluate special types that are not directly casted from string
				TypeConverter conv = TypeDescriptor.GetConverter(propertyType);
				if (args[propFQN] == null || propertyType == args[propFQN].GetType())
				{
					return args[propFQN];
				}
				else
				{
					return conv.ConvertFromInvariantString(args[propFQN].ToString());
				}
			}
			else
			{
				return null;	// if there are missing arguments try passing null
			}
		}

		/// <summary>
		/// Hydrates complex types
		/// </summary>
		/// <param name="param"></param>
		/// <returns></returns>
		public object HydrateClass(string propertyName, Type propertyType, string parentNamespace)
		{
			var argumentObject = Activator.CreateInstance(propertyType);

			// search for properties on the current namespace
			string nsPrefix = string.IsNullOrEmpty(parentNamespace) ? propertyName : parentNamespace + "+" + propertyName;

			var objectProperties = args.Keys.ToList().FindAll(k => k.StartsWith(nsPrefix));

			// loop through them 
			foreach (var p in objectProperties)
			{
				String propName = p.Remove(0, nsPrefix.Length + 1).Split('+')[0];

				argumentObject.GetType()
					.GetProperty(propName)
						.SetValue(argumentObject, ProcessProperty(propName, argumentObject.GetType().GetProperty(propName).PropertyType, nsPrefix), null);
			}

			return argumentObject;
		}

	}
}

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 Code Project Open License (CPOL)


Written By
Architect
Switzerland Switzerland
Senior IT Consultant working in Switzerland as Senior Software Engineer.

Find more at on my blog.

Comments and Discussions