using System; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Threading.Tasks; using EmbedIO.Utilities; using Swan.Logging; namespace EmbedIO.Routing { /// /// Handles a HTTP request by matching it against a list of routes, /// possibly handling different HTTP methods via different handlers. /// /// /// public sealed class RouteVerbResolverCollection : RouteResolverCollectionBase { private readonly string _logSource; internal RouteVerbResolverCollection(string logSource) { _logSource = logSource; } /// /// Adds handlers, associating them with HTTP method / route pairs by means /// of Route attributes. /// A compatible handler is a static or instance method that takes 2 /// parameters having the following types, in order: /// /// /// /// /// The return type of a compatible handler may be either /// or . /// A compatible handler, in order to be added to a , /// must have one or more Route attributes. /// The same handler will be added once for each such attribute, either declared on the handler, /// or inherited (if the handler is a virtual method). /// This method behaves according to the type of the /// parameter: /// /// if is a , all public static methods of /// the type (either declared on the same type or inherited) that are compatible handlers will be added /// to the collection; /// if is an , all public static methods of /// each exported type of the assembly (either declared on the same type or inherited) that are compatible handlers will be added /// to the collection; /// if is a referring to a compatible handler, /// it will be added to the collection; /// if is a whose Method /// refers to a compatible handler, that method will be added to the collection; /// if is none of the above, all public instance methods of /// its type (either declared on the same type or inherited) that are compatible handlers will be bound to /// and added to the collection. /// /// /// Where to look for compatible handlers. See the Summary section for more information. /// /// The number of handlers that were added to the collection. /// Note that methods with multiple Route attributes /// will count as one for each attribute. /// /// is . public int AddFrom(object target) => Validate.NotNull(nameof(target), target) switch { Type type => AddFrom(null, type), Assembly assembly => assembly.GetExportedTypes().Sum(t => AddFrom(null, t)), MethodInfo method => method.IsStatic ? Add(null, method) : 0, Delegate callback => Add(callback.Target, callback.Method), _ => AddFrom(target, target.GetType()) }; /// protected override RouteVerbResolver CreateResolver(RouteMatcher matcher) => new RouteVerbResolver(matcher); /// protected override void OnResolverCalled(IHttpContext context, RouteVerbResolver resolver, RouteResolutionResult result) => $"[{context.Id}] Route {resolver.Route} : {result}".Trace(_logSource); private static bool IsHandlerCompatibleMethod(MethodInfo method, out bool isSynchronous) { isSynchronous = false; var returnType = method.ReturnType; if (returnType == typeof(void)) { isSynchronous = true; } else if (returnType != typeof(Task)) { return false; } var parameters = method.GetParameters(); return parameters.Length == 2 && parameters[0].ParameterType.IsAssignableFrom(typeof(IHttpContext)) && parameters[1].ParameterType.IsAssignableFrom(typeof(RouteMatch)); } // Call Add with all suitable methods of a Type, return sum of results. private int AddFrom(object? target, Type type) => type.GetMethods(target == null ? BindingFlags.Public | BindingFlags.Static : BindingFlags.Public | BindingFlags.Instance) .Where(method => method.IsPublic && !method.IsAbstract && !method.ContainsGenericParameters) .Sum(m => Add(target, m)); private int Add(object? target, MethodInfo method) { if (!IsHandlerCompatibleMethod(method, out var isSynchronous)) return 0; var attributes = method.GetCustomAttributes(true).OfType().ToArray(); if (attributes.Length == 0) return 0; var parameters = new[] { Expression.Parameter(typeof(IHttpContext), "context"), Expression.Parameter(typeof(RouteMatch), "route"), }; Expression body = Expression.Call(Expression.Constant(target), method, parameters.Cast()); if (isSynchronous) { // Convert void to Task by evaluating Task.CompletedTask body = Expression.Block(typeof(Task), body, Expression.Constant(Task.CompletedTask)); } var handler = Expression.Lambda(body, parameters).Compile(); foreach (var attribute in attributes) { Add(attribute.Verb, attribute.Matcher, handler); } return attributes.Length; } } }