From 2cf6cd4a7d447b289dff76c7041224a211f0795a Mon Sep 17 00:00:00 2001 From: GHXX Date: Thu, 25 Jul 2024 07:30:35 +0200 Subject: [PATCH] make stuff nonstatic --- SimpleHttpServer/HttpServer.cs | 25 +++++++++++++------ .../Types/BaseEndpointCheckAttribute.cs | 21 ++++++++++++---- .../Types/EndpointInvocationInfo.cs | 9 +++++-- 3 files changed, 40 insertions(+), 15 deletions(-) diff --git a/SimpleHttpServer/HttpServer.cs b/SimpleHttpServer/HttpServer.cs index 02c126e..3bffdc5 100644 --- a/SimpleHttpServer/HttpServer.cs +++ b/SimpleHttpServer/HttpServer.cs @@ -86,18 +86,24 @@ public sealed class HttpServer { private readonly Dictionary<(string path, string rType), EndpointInvocationInfo> simpleEndpointMethodInfos = new(); private static readonly Type[] expectedEndpointParameterTypes = new[] { typeof(RequestContext) }; - public void RegisterEndpointsFromType() { + + public void RegisterEndpointsFromType(Func? instanceFactory = null) where T : class { // T cannot be static, as generic args must be nonstatic if (stringToTypeParameterConverters.Count == 0) RegisterDefaultConverters(); var t = typeof(T); - foreach (var (mi, attrib) in t.GetMethods() + var mis = t.GetMethods() .ToDictionary(x => x, x => x.GetCustomAttributes()) - .Where(x => x.Value.Any()).ToDictionary(x => x.Key, x => x.Value.Single())) { + .Where(x => x.Value.Any()).ToDictionary(x => x.Key, x => x.Value.Single()); + + var isStatic = mis.All(x => x.Key.IsStatic); // if all are static then there is no point in having a constructor as no instance data is accessible, but we allow passing a factory anyway + Assert(isStatic || (instanceFactory != null), $"You must provide an instance factory if any methods of the given type ({typeof(T).FullName}) are non-static"); + T? classInstance = instanceFactory?.Invoke(); + foreach (var (mi, attrib) in mis) { string GetFancyMethodName() => mi.DeclaringType!.FullName + "#" + mi.Name; - Assert(mi.IsStatic, $"Method tagged with HttpEndpointAttribute must be static! ({GetFancyMethodName()})"); + //Assert(mi.IsStatic, $"Method tagged with HttpEndpointAttribute must be static! ({GetFancyMethodName()})"); Assert(mi.IsPublic, $"Method tagged with HttpEndpointAttribute must be public! ({GetFancyMethodName()})"); var methodParams = mi.GetParameters(); @@ -126,8 +132,11 @@ public sealed class HttpServer { } // stores the check attributes that are defined on the method and on the containing class - var requiredChecks = mi.GetCustomAttributes(true).Concat(mi.DeclaringType?.GetCustomAttributes(true) ?? Enumerable.Empty()) - .Where(a => a.GetType().IsAssignableTo(typeof(BaseEndpointCheckAttribute))).Cast().ToArray(); + var requiredChecks = mi.GetCustomAttributes(true).Concat(mi.DeclaringType?.GetCustomAttributes(true) ?? Enumerable.Empty()) + .Where(a => a.GetType().IsAssignableTo(typeof(InternalEndpointCheckAttribute))).Cast().ToArray(); + + foreach (var requiredCheck in requiredChecks) + requiredCheck.SetInstance(classInstance); foreach (var location in attrib.Locations) { var normLocation = NormalizeUrlPath(location); @@ -139,7 +148,7 @@ public sealed class HttpServer { var reqMethod = Enum.GetName(attrib.RequestMethod) ?? throw new ArgumentException("Request method was undefined"); mainLogger.Information($"Registered endpoint: '{reqMethod} {normLocation}'"); - simpleEndpointMethodInfos.Add((normLocation, reqMethod), new EndpointInvocationInfo(mi, qparams, requiredChecks)); + simpleEndpointMethodInfos.Add((normLocation, reqMethod), new EndpointInvocationInfo(mi, qparams, requiredChecks, classInstance)); } } } @@ -265,7 +274,7 @@ public sealed class HttpServer { convertedQParamValues[0] = rc; rc.ParsedParameters = parsedQParams.AsReadOnly(); - await (Task) (mi.Invoke(null, convertedQParamValues) ?? throw new NullReferenceException("Website func returned null unexpectedly")); + await (Task) (mi.Invoke(endpointInvocationInfo.typeInstanceReference, convertedQParamValues) ?? throw new NullReferenceException("Website func returned null unexpectedly")); } else { if (requestMethod == "GET") foreach (var (k, v) in staticServePaths) { diff --git a/SimpleHttpServer/Types/BaseEndpointCheckAttribute.cs b/SimpleHttpServer/Types/BaseEndpointCheckAttribute.cs index 84fe930..c4823f9 100644 --- a/SimpleHttpServer/Types/BaseEndpointCheckAttribute.cs +++ b/SimpleHttpServer/Types/BaseEndpointCheckAttribute.cs @@ -2,14 +2,25 @@ namespace SimpleHttpServer.Types; -[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = true)] -public abstract class BaseEndpointCheckAttribute : Attribute { - - public BaseEndpointCheckAttribute() { } - +public abstract class InternalEndpointCheckAttribute : Attribute { /// /// Executed when the endpoint is invoked. The endpoint invocation is skipped if any of the checks fail. /// /// True to allow invocation, false to prevent. public abstract bool Check(HttpListenerRequest req); + internal abstract void SetInstance(object? instance); +} + +[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = true)] +public abstract class BaseEndpointCheckAttribute : InternalEndpointCheckAttribute { + /// + /// A reference to the instance of the class that this attribute is attached to. + /// Will be null iff an class factory was passed in . + /// + protected internal T? EndpointClassInstance { get; internal set; } = default; + public BaseEndpointCheckAttribute() { } + internal override void SetInstance(object? instance) { + if (instance != null) + EndpointClassInstance = (T?) instance; + } } \ No newline at end of file diff --git a/SimpleHttpServer/Types/EndpointInvocationInfo.cs b/SimpleHttpServer/Types/EndpointInvocationInfo.cs index 803b75c..ea4424b 100644 --- a/SimpleHttpServer/Types/EndpointInvocationInfo.cs +++ b/SimpleHttpServer/Types/EndpointInvocationInfo.cs @@ -7,12 +7,17 @@ internal readonly struct EndpointInvocationInfo { internal readonly MethodInfo methodInfo; internal readonly List queryParameters; - internal readonly BaseEndpointCheckAttribute[] requiredChecks; + internal readonly InternalEndpointCheckAttribute[] requiredChecks; + /// + /// a reference to the object in which this method is defined (or null if the class is static) + /// + internal readonly object? typeInstanceReference; - public EndpointInvocationInfo(MethodInfo methodInfo, List queryParameters, BaseEndpointCheckAttribute[] requiredChecks) { + public EndpointInvocationInfo(MethodInfo methodInfo, List queryParameters, InternalEndpointCheckAttribute[] requiredChecks, object? typeInstanceReference) { this.methodInfo = methodInfo ?? throw new ArgumentNullException(nameof(methodInfo)); this.queryParameters = queryParameters ?? throw new ArgumentNullException(nameof(queryParameters)); this.requiredChecks = requiredChecks; + this.typeInstanceReference = typeInstanceReference; } public readonly bool CheckAll(HttpListenerRequest req) => requiredChecks.All(x => x.Check(req));