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;
}
}
}