using System; using System.Collections.Generic; using System.ComponentModel; using System.Reflection; using System.Runtime.CompilerServices; using ln.http.api.attributes; using ln.http.exceptions; using ln.http.router; using ln.json; using ln.json.mapping; using ln.type; namespace ln.http.api { public abstract class WebApiController : SimpleRouter { Dictionary methodCaches = new Dictionary(); public WebApiController() { Initialize(); } void Initialize() { foreach (MethodInfo methodInfo in GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) { foreach (EndpointAttribute endpointAttribute in methodInfo.GetCustomAttributes()) { if (endpointAttribute != null) { EndpointMethodCache methodCache = GetOrCreateMethodCache(endpointAttribute.Route); methodCache.Add(endpointAttribute.Method, methodInfo); } } } } HttpResponse MapRequest(HttpRequest request, MethodInfo methodInfo) { Func defaultResponseFactory; MapArguments(request, methodInfo, out object[] arguments, out defaultResponseFactory); object result = methodInfo.Invoke(this, arguments); if (result is HttpResponse httpResponse) return httpResponse; if (result is JSONValue jsonResult) return defaultResponseFactory() .ContentType("application/json") .Content(jsonResult); if (JSONMapper.DefaultMapper.Serialize(result, out JSONValue json)) return defaultResponseFactory() .ContentType("application/json") .Content(json.ToString()) ; return HttpResponse.InternalServerError().Content("Method result could not be serialized"); } void MapArguments(HttpRequest request, MethodInfo methodInfo,out object[] arguments, out Func defaultRepsonseFactory) { ParameterInfo[] parameterInfos = methodInfo.GetParameters(); arguments = new object[parameterInfos.Length]; JSONValue jsonContent = null; if (request.GetRequestHeader("content-type", "").Equals("application/json")) { jsonContent = JSONParser.Parse(request.ContentReader.ReadToEnd()); } for (int n=0;n() ?? ArgumentSourceAttribute.Default; if (typeof(HttpRequest).Equals(parameterInfo.ParameterType)) { arguments[n] = request; } else if (!FindArgumentByName(sourceAttribute, request, jsonContent, parameterInfo.Name, out object value)) { if (parameterInfo.HasDefaultValue) { arguments[n] = parameterInfo.DefaultValue; } else throw new BadRequestException(); } else { try { if (value is JSONValue jsonValue) { if (!JSONMapper.DefaultMapper.Deserialize(jsonValue, parameterInfo.ParameterType, out arguments[n])) throw new BadRequestException(); } else if (Cast.To(value, parameterInfo.ParameterType, out arguments[n])) { } else if (value is string text) { arguments[n] = TypeDescriptor.GetConverter(parameterInfo.ParameterType).ConvertFromInvariantString(text); } else { arguments[n] = Convert.ChangeType(value, parameterInfo.ParameterType); } } catch (FormatException) { throw new BadRequestException(); } } } switch (request.HttpMethod) { case HttpMethod.GET: if (typeof(void).Equals(methodInfo.ReturnType)) defaultRepsonseFactory = HttpResponse.NoContent; else defaultRepsonseFactory = HttpResponse.OK; break; case HttpMethod.POST: defaultRepsonseFactory = HttpResponse.Created; break; case HttpMethod.PUT: defaultRepsonseFactory = HttpResponse.OK; break; case HttpMethod.PATCH: defaultRepsonseFactory = HttpResponse.OK; break; case HttpMethod.DELETE: defaultRepsonseFactory = HttpResponse.NoContent; break; default: defaultRepsonseFactory = HttpResponse.OK; break; } } bool FindArgumentByName(ArgumentSourceAttribute sourceAttribute, HttpRequest request, JSONValue jsonContent, string parameterName, out object value) { parameterName = sourceAttribute.SourceName ?? parameterName; if (((sourceAttribute.Source & ArgumentSource.PARAMETER) == ArgumentSource.PARAMETER) && request.ContainsParameter(parameterName)) { value = request.GetParameter(parameterName); return true; } else if (((sourceAttribute.Source & ArgumentSource.CONTENT) == ArgumentSource.CONTENT) && (jsonContent is JSONObject jsonObject) && (jsonObject.ContainsKey(parameterName))) { value = jsonObject[parameterName]; return true; } else if (((sourceAttribute.Source & ArgumentSource.HEADER) == ArgumentSource.HEADER) && (request.RequestHeaders.Contains(parameterName))) { value = request.RequestHeaders.Get(parameterName); return true; } value = null; return false; } EndpointMethodCache GetOrCreateMethodCache(string route) { if (!methodCaches.TryGetValue(route, out EndpointMethodCache methodCache)) { methodCache = new EndpointMethodCache(); methodCaches.Add(route, methodCache); AddSimpleRoute(route, (request) => { MethodInfo methodInfo = methodCache[request.HttpMethod]; return MapRequest(request, methodInfo); }); } return methodCache; } class EndpointMethodCache { Dictionary cache = new Dictionary(); public EndpointMethodCache() { } public MethodInfo this[HttpMethod httpMethod] { get => cache[httpMethod]; set => cache[httpMethod] = value; } public void Add(HttpMethod httpMethod, MethodInfo methodInfo) => cache.Add(httpMethod, methodInfo); public void Remove(HttpMethod httpMethod, MethodInfo methodInfo) => cache.Remove(httpMethod); } } }