using System;
using System.Collections.Generic;
using System.Text;
using System.Web;
using System.Collections;
using System.Reflection;
using System.Xml;
namespace PoC.Web.Services
{
//[Flags]
public enum HttpVerb
{
Get,
Post,
Put,
Delete
}
/// <summary>
/// The main handler
/// </summary>
public class ReSTServiceHandler : IHttpHandler
{
//Will
IDictionary<string, KeyValuePair<Type, Dictionary<string, System.Reflection.MethodInfo > > > typeReference;
static object syncRoot = new object();
public ReSTServiceHandler()
{
typeReference = new Dictionary<string, KeyValuePair<Type, Dictionary<string,System.Reflection.MethodInfo > > >();
SetupReference();
}
private void SetupReference()
{
//This isn't gng to work :(
//System.Reflection.Assembly[] loadedAssm = System.AppDomain.CurrentDomain.GetAssemblies();
//Get codebase and list all dlls in bin folder.... ??
string[] allASMs = System.IO.Directory.GetFiles(System.IO.Path.GetDirectoryName(this.GetType().Assembly.CodeBase.Replace("file:///", string.Empty)), "*.dll");
foreach (string aASM in allASMs)
{
//ignore this assembly
if (string.Compare(System.IO.Path.GetFileName(aASM), System.IO.Path.GetFileName(this.GetType().Assembly.CodeBase.Replace("file:///", string.Empty)), StringComparison.OrdinalIgnoreCase) == 0) continue;
Assembly assm = Assembly.LoadFrom(aASM);
foreach (Type type in assm.GetTypes())
{
if (typeof(IReSTService).IsAssignableFrom(type))
{
//We got one
//Get All "ReSTMethods"
Dictionary<string, System.Reflection.MethodInfo> supportedMethods = new Dictionary<string, System.Reflection.MethodInfo>();
System.Reflection.MethodInfo[] methods = type.GetMethods(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
foreach (System.Reflection.MethodInfo mInfo in methods)
{
if (mInfo.GetCustomAttributes(typeof(ReSTMethod), false).Length > 0)
{
//No overloading supported
supportedMethods[mInfo.Name] = mInfo;
}
}
//Not supporting same typenames in different namespaces.
typeReference.Add(type.Name/*.ToLower()*/, new KeyValuePair<Type, Dictionary<string, System.Reflection.MethodInfo>>(type, supportedMethods));
}
}
}
}
private KeyValuePair<Type, Dictionary<string,System.Reflection.MethodInfo> > GetTypeReference(string className, bool throwOnError)
{
if (!typeReference.ContainsKey(className/*.ToLower()*/))
{
/*Type t = DiscoverType(className);
if (t == null) */
if (throwOnError) throw new InternalErrorException(503, "Service Not Available");
return new KeyValuePair<Type,Dictionary<string,MethodInfo>>(null,null);
/*lock (syncRoot)
{
if (!typeReference.ContainsKey(className))
{
typeReference.Add(className, t);
}
}*/
}
return typeReference[className];
}
#region IHttpHandler Members
public bool IsReusable
{
get { /*We need Singleton*/return true; }
}
public void ProcessRequest(HttpContext context)
{
try
{
string[] path_items = context.Request.Url.AbsolutePath.Split('/');
if (context.Request.RawUrl.EndsWith("?ReSTHelp"))
{
ServeHelp(context);
return;
}
string typeName,functionName;
KeyValuePair<Type, Dictionary<string, MethodInfo>> typeRef = GetTypeName(context.Request.Url.AbsolutePath, out typeName, out functionName);
if (typeRef.Key != null)//!string.IsNullOrEmpty(typeName))
{
HttpVerb verb = (HttpVerb)Enum.Parse(typeof(HttpVerb), context.Request.HttpMethod, true);
//string functionName = path_items[path_items.Length - 1];
//string className = path_items[path_items.Length - 2];
//Type typeRef = GetTypeReference(className);
//object[] param_arr = GetParameters(context);
//Utilities.ReflectionUtil.FindMethod(typeRef, functionName, param_arr, verb);
object returnObject = null;
bool shouldSerializeXml;
returnObject = ServiceRequest(context, verb, functionName, ref typeRef, out shouldSerializeXml);
WriteResponse(shouldSerializeXml, returnObject, context);
//WriteResponse(method, returnObject, context);
}
else
{
throw new InternalErrorException(404, "Invalid Path");
}
}
catch (InternalErrorException iex)
{
context.Response.StatusCode = iex.ErrorCode;
context.Response.Write(string.Format("{0}{1}", Environment.NewLine, iex.Message));
}
catch (Exception ex)
{
//Log error
//log.Error("An exception occurred while processing request for {0} DUMP:{1}
context.Response.StatusCode = 500;
context.Response.Write(string.Format("An error occurred. {0}Exception:{0}{1}", Environment.NewLine, ex.ToString()));
}
}
private KeyValuePair<Type, Dictionary<string,MethodInfo>> GetTypeName(string path, out string typeName, out string functionName)
{
KeyValuePair<Type, Dictionary<string, MethodInfo>> typeRef = new KeyValuePair<Type,Dictionary<string,MethodInfo>>(null,null);
typeName = functionName = string.Empty;
foreach (string entity in path.Split('/'))
{
if (string.IsNullOrEmpty(typeName)) { if ((typeRef = GetTypeReference(entity, false)).Key != null) typeName = entity; }
else { functionName = entity; break; }
}
return typeRef;
//return typeName;
}
private object ServiceRequest(HttpContext context, HttpVerb verb, string functionName, ref KeyValuePair<Type, Dictionary<string, MethodInfo>> typeRef, out bool shouldSerializeXml)
{
object returnObject = null;
System.Reflection.MethodInfo method = null;
if (typeof(IReSTFulService).IsAssignableFrom(typeRef.Key))
{
IReSTFulService svcObject = (IReSTFulService)typeRef.Key.GetConstructor(System.Type.EmptyTypes).Invoke(null);
returnObject = ServiceReSTFullCall(verb, functionName, returnObject, svcObject, context);
shouldSerializeXml = true;
}
else if (!string.IsNullOrEmpty(functionName) && typeRef.Value.ContainsKey(functionName))
{
method = typeRef.Value[functionName];
if (((ReSTMethod)(method.GetCustomAttributes(typeof(ReSTMethod), false)[0])).Verb != verb)
{
throw new InternalErrorException(503, "Method not supported");
}
returnObject = Call(typeRef.Key.GetConstructor(System.Type.EmptyTypes).Invoke(null), method, context);
shouldSerializeXml = method.GetCustomAttributes(typeof(ReSTXmlResponse), false).Length > 0;
}
else
{
/*if (method == null) */
throw new InternalErrorException(503, "Method not supported");
}
return returnObject;
}
private static object ServiceReSTFullCall(
HttpVerb verb,
string functionName,
object returnObject,
IReSTFulService svcObject,
HttpContext context
)
{
switch (verb)
{
case HttpVerb.Get:
returnObject = svcObject.GetCall(functionName, context);
break;
case HttpVerb.Put:
returnObject = svcObject.PutCall(functionName, context);
break;
case HttpVerb.Post:
returnObject = svcObject.PostCall(functionName, context);
break;
case HttpVerb.Delete:
returnObject = svcObject.DeleteCall(functionName, context);
break;
default:
throw new InternalErrorException(503, "Method not supported");
}
return returnObject;
}
private void ServeHelp(HttpContext context)
{
context.Response.ContentType = "text/html";
//string typeName, functionName;
//KeyValuePair<> GetTypeName(context.Request.Url.AbsolutePath, out typeName, out functionName);
StringBuilder sb = new StringBuilder();
sb.Append("<html><body><H1>HELP</H1><div style=\"font-size:0.7em\"><ul style=\"list-style-type:none;padding:5px 5px 5px 5px;\">");
foreach (KeyValuePair<string, KeyValuePair<Type, Dictionary<string, MethodInfo>>> pair in typeReference)
{
sb.AppendFormat("<li>{0}", pair.Key);
sb.Append("<br/><ul style=\"list-style-type:none;background-Color:lightgray;padding:5px 5px 5px 5px;\">");
foreach (KeyValuePair<string, MethodInfo> methodsDef in pair.Value.Value)
{
sb.AppendFormat("<li><b>{0}</b>", methodsDef.Value.Name);
if (methodsDef.Value.GetParameters().Length > 0)
{
sb.Append("<br/><ul style=\"list-style-type:none;padding:5px 5px 5px 5px;\">");
foreach (ParameterInfo pi in methodsDef.Value.GetParameters())
{
sb.AppendFormat("<li>- <b>{0}</b> (typeof - {1})</li>",pi.Name, pi.ParameterType.Name);
}
sb.Append("</ul>");
}
sb.Append("</li>");
}
sb.Append("</ul></li>");
}
sb.Append("</ul></div></body></html>");
context.Response.Write(sb.ToString());
//context.Response.Write(<H2>COMING SOON</H2>
}
private void WriteResponse(bool shouldSerializeXml, object returnObject, HttpContext context)
{
if (returnObject == null)
{
context.Response.Write(string.Empty);
}
else if (returnObject is System.Xml.XmlNode)
{
context.Response.Write((returnObject as System.Xml.XmlNode).OuterXml);
context.Response.ContentType = "text/xml";
}
else if (returnObject is IDictionary)
{
throw new InternalErrorException(503, "Dictionary cannot be serialized");
}
else if (shouldSerializeXml)
{
context.Response.Write(SerializeToXml(returnObject));
context.Response.ContentType = "text/xml";
}
else
{
context.Response.Write(returnObject.ToString());
}
}
private void WriteResponse(System.Reflection.MethodInfo method, object o, HttpContext context)
{
if (o is System.Xml.XmlNode)
{
context.Response.Write((o as System.Xml.XmlNode).OuterXml);
context.Response.ContentType = "text/xml";
}
else if (o is IDictionary)
{
throw new InternalErrorException(503, "Dictionary cannot be serialized");
}
//else if (returnObject.GetType().IsValueType)
//{
// context.Response.Write(returnObject.ToString());
//}
else
{
//No support to array yet
if (method.GetCustomAttributes(typeof(ReSTXmlResponse), false).Length > 0)
{
context.Response.Write(SerializeToXml(o));
context.Response.ContentType = "text/xml";
}
else
{
context.Response.Write(o.ToString());
}
}
}
private string SerializeToXml(object o)
{
System.Xml.Serialization.XmlSerializer serializer = new System.Xml.Serialization.XmlSerializer(o.GetType());
System.IO.MemoryStream ms = new System.IO.MemoryStream();
serializer.Serialize(ms, o);
ms.Position = 0;
System.Xml.XmlDocument dom = new System.Xml.XmlDocument();
dom.LoadXml(System.Text.ASCIIEncoding.ASCII.GetString(ms.GetBuffer()));
if (dom.DocumentElement == null) return string.Empty;
return dom.DocumentElement.OuterXml;
}
private object Call(object context, System.Reflection.MethodInfo method, HttpContext httpContext)
{
System.Reflection.ParameterInfo[] parameters = method.GetParameters();
if (parameters.Length == 0) return method.Invoke(context, null);
List<object> paramArgs = new List<object>();
foreach(ParameterInfo pi in parameters)
{
string qofParam = httpContext.Request[pi.Name];
if (qofParam == null) paramArgs.Add(null);
else
{
paramArgs.Add(Utilities.TypeConversion.ConvertType(pi.Name, qofParam, pi.ParameterType));
}
}
/*
if (parameters.Length ==0 && param_arr.Length!=0) throw new InvalidOperationException("Invalid arguments");
if (parameters.Length > param_arr.Length)
{
object[] resizeArgs = new object[parameters.Length];
System.Array.Copy(param_arr, resizeArgs, param_arr.Length);
param_arr = resizeArgs;
}
List<object> newParamList = new List<object>();
for (int idx = 0; idx < parameters.Length; ++idx)
{
object returnObject = param_arr[idx];
if (returnObject == null) newParamList.Add(null);
else
{
newParamList.Add(Utilities.TypeConversion.ConvertType(parameters[idx].Name, returnObject, parameters[idx].ParameterType));
}
}
*/
return method.Invoke(context, paramArgs.ToArray());//newParamList.ToArray());
}
#endregion
private object[] GetParameters(HttpContext context)
{
List<object> args = new List<object>();
foreach (string key in context.Request.QueryString.AllKeys)
{
args.Add(context.Request.QueryString[key]);
}
foreach (string key in context.Request.Form.AllKeys)
{
args.Add(context.Request.Form[key]);
}
return args.ToArray();
}
}
}