Click here to Skip to main content
15,885,216 members
Articles / Web Development / IIS

Build ReST based Web Services in .NET/C#

Rate me:
Please Sign up or sign in to vote.
5.00/5 (12 votes)
12 Jul 2009CPOL2 min read 122.5K   5.5K   55  
A ReST based Web Service for C#.
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();
        }


    }

    
}

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
United States United States
Loves coding...

Comments and Discussions