From b6500e17313aa0b10a0d8a4de75b248692a01890 Mon Sep 17 00:00:00 2001 From: redth Date: Tue, 23 Apr 2024 16:16:45 -0400 Subject: [PATCH] Abstract our request handler resolution Currently the default is to inspect all the assemblies in the current appdomain for types implementing `IRequestHandler` but it would be nice to be able to customize this resolution. This abstracts the concept out so you can provide your own resolver and suggest the types explicitly that should be used. --- ...AppDomainAssemblyRequestHandlerResolver.cs | 43 +++++++++++++++++ .../IRequestHandlerResolver.cs | 8 ++++ Source/Web.Maple.Server/MapleServer.cs | 46 +++++++++++-------- 3 files changed, 77 insertions(+), 20 deletions(-) create mode 100644 Source/Web.Maple.Server/AppDomainAssemblyRequestHandlerResolver.cs create mode 100644 Source/Web.Maple.Server/IRequestHandlerResolver.cs diff --git a/Source/Web.Maple.Server/AppDomainAssemblyRequestHandlerResolver.cs b/Source/Web.Maple.Server/AppDomainAssemblyRequestHandlerResolver.cs new file mode 100644 index 0000000..bf088d5 --- /dev/null +++ b/Source/Web.Maple.Server/AppDomainAssemblyRequestHandlerResolver.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Meadow.Logging; + +namespace Meadow.Foundation.Web.Maple; + +public class AppDomainAssemblyRequestHandlerResolver : IRequestHandlerResolver +{ + public AppDomainAssemblyRequestHandlerResolver(Logger? logger) + { + Logger = logger; + } + + readonly Logger? Logger; + + public Type[] Resolve() + { + var resolved = new List(); + + // Get classes that implement IRequestHandler + var assemblies = AppDomain.CurrentDomain.GetAssemblies(); + + // loop through each assembly in the app and all the classes in it + foreach (var assembly in assemblies) + { + var types = assembly.GetTypes(); + foreach (var t in types) + { + // if it inherits `IRequestHandler`, add it to the list + if (t.BaseType != null) + { + if (t.BaseType.GetInterfaces().Contains(typeof(IRequestHandler))) + { + resolved.Add(t); + } + } + } + } + + return resolved.ToArray(); + } +} \ No newline at end of file diff --git a/Source/Web.Maple.Server/IRequestHandlerResolver.cs b/Source/Web.Maple.Server/IRequestHandlerResolver.cs new file mode 100644 index 0000000..1b3e679 --- /dev/null +++ b/Source/Web.Maple.Server/IRequestHandlerResolver.cs @@ -0,0 +1,8 @@ +using System; + +namespace Meadow.Foundation.Web.Maple; + +public interface IRequestHandlerResolver +{ + Type[] Resolve(); +} \ No newline at end of file diff --git a/Source/Web.Maple.Server/MapleServer.cs b/Source/Web.Maple.Server/MapleServer.cs index f41c8d0..6892f61 100644 --- a/Source/Web.Maple.Server/MapleServer.cs +++ b/Source/Web.Maple.Server/MapleServer.cs @@ -44,6 +44,11 @@ public partial class MapleServer /// public RequestProcessMode ThreadingMode { get; protected set; } + /// + /// Resolver to find types which implement IRequestHandler. + /// + public IRequestHandlerResolver? RequestHandlerResolver { get; private set; } + /// /// Whether or not the server should advertise it's name /// and IP via UDP for discovery. @@ -77,8 +82,9 @@ public MapleServer( int port = DefaultPort, bool advertise = false, RequestProcessMode processMode = RequestProcessMode.Serial, - Logger logger = null) - : this(IPAddress.Parse(ipAddress), port, advertise, processMode, logger) + Logger? logger = null, + IRequestHandlerResolver? requestHandlerResolver = null) + : this(IPAddress.Parse(ipAddress), port, advertise, processMode, logger, requestHandlerResolver) { } @@ -92,24 +98,29 @@ public MapleServer( /// Whether or not the server should respond to /// requests in parallel or serial. For Meadow, only Serial works /// reliably today. + /// Logger to write output information to. + /// Resolver for request handler types. + /// By default, the AppDomainAssemblyRequestHandlerResolver is used. public MapleServer( IPAddress ipAddress, int port = DefaultPort, bool advertise = false, RequestProcessMode processMode = RequestProcessMode.Serial, - Logger? logger = null) + Logger? logger = null, + IRequestHandlerResolver? requestHandlerResolver = null) { Logger = logger; MethodCache = new RequestMethodCache(Logger); ErrorPageGenerator = new ErrorPageGenerator(); - Create(ipAddress, port, advertise, processMode); + Create(ipAddress, port, advertise, processMode, requestHandlerResolver); } private void Create(IPAddress ipAddress, int port, bool advertise, - RequestProcessMode processMode) + RequestProcessMode processMode, + IRequestHandlerResolver? requestHandlerResolver = null) { IPAddress = ipAddress ?? throw new ArgumentNullException(nameof(ipAddress)); Port = port; @@ -117,6 +128,8 @@ private void Create(IPAddress ipAddress, Advertise = advertise; ThreadingMode = processMode; + RequestHandlerResolver = requestHandlerResolver; + if (IPAddress.Equals(IPAddress.Any)) { // because .NET is apparently too stupid to understand "bind to all" @@ -238,28 +251,21 @@ protected void StartUdpAdvertisement() /// protected void LoadRequestHandlers() { - // Get classes that implement IRequestHandler - var assemblies = AppDomain.CurrentDomain.GetAssemblies(); + var resolver = RequestHandlerResolver ?? new AppDomainAssemblyRequestHandlerResolver(Logger); var typesAdded = 0; - - // loop through each assembly in the app and all the classes in it - foreach (var assembly in assemblies) + var resolvedTypes = resolver.Resolve(); + foreach (var resolvedType in resolvedTypes) { - var types = assembly.GetTypes(); - foreach (var t in types) + if (resolvedType.BaseType != null) { - // if it inherits `IRequestHandler`, add it to the list - if (t.BaseType != null) + if (resolvedType.BaseType.GetInterfaces().Contains(typeof(IRequestHandler))) { - if (t.BaseType.GetInterfaces().Contains(typeof(IRequestHandler))) - { - MethodCache.AddType(t); - typesAdded++; - } + MethodCache.AddType(resolvedType); + typesAdded++; } } } - + if (typesAdded == 0) { Console.WriteLine("Warning: No Maple Server `IRequestHandler`s found. Server will not operate.");