203 lines
7.7 KiB
C#
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} |