ln.http.api/ln.http.api/WebApiController.cs

203 lines
7.7 KiB
C#

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<string,EndpointMethodCache> methodCaches = new Dictionary<string, EndpointMethodCache>();
public WebApiController()
{
Initialize();
}
void Initialize()
{
foreach (MethodInfo methodInfo in GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
{
foreach (EndpointAttribute endpointAttribute in methodInfo.GetCustomAttributes<EndpointAttribute>())
{
if (endpointAttribute != null)
{
EndpointMethodCache methodCache = GetOrCreateMethodCache(endpointAttribute.Route);
methodCache.Add(endpointAttribute.Method, methodInfo);
}
}
}
}
HttpResponse MapRequest(HttpRequest request, MethodInfo methodInfo)
{
Func<HttpResponse> 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<HttpResponse> 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<arguments.Length;n++)
{
ParameterInfo parameterInfo = parameterInfos[n];
ArgumentSourceAttribute sourceAttribute = parameterInfo.GetCustomAttribute<ArgumentSourceAttribute>() ?? 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<HttpMethod,MethodInfo> cache = new Dictionary<HttpMethod, MethodInfo>();
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);
}
}
}