using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using EmbedIO.Internal; using EmbedIO.Utilities; using Swan; using Swan.Collections; using Swan.Configuration; namespace EmbedIO.Routing { /// /// Implements the logic for resolving a context and a URL path against a list of routes, /// possibly handling different HTTP methods via different handlers. /// /// The type of the data used to select a suitable handler /// for a context. /// The type of the route resolver. /// public abstract class RouteResolverCollectionBase : ConfiguredObject where TResolver : RouteResolverBase { private readonly List _resolvers = new List(); /// /// Associates some data and a route to a handler. /// /// Data used to determine which contexts are /// suitable to be handled by . /// The to match URL paths against. /// A callback used to handle matching contexts. /// /// is . /// - or - /// is . /// /// The method /// returned . /// /// /// public void Add(TData data, RouteMatcher matcher, RouteHandlerCallback handler) { matcher = Validate.NotNull(nameof(matcher), matcher); handler = Validate.NotNull(nameof(handler), handler); GetResolver(matcher).Add(data, handler); } /// /// Associates some data and a route to a synchronous handler. /// /// Data used to determine which contexts are /// suitable to be handled by . /// The to match URL paths against. /// A callback used to handle matching contexts. /// /// is . /// - or - /// is . /// /// The method /// returned . /// /// /// public void Add(TData data, RouteMatcher matcher, SyncRouteHandlerCallback handler) { matcher = Validate.NotNull(nameof(matcher), matcher); handler = Validate.NotNull(nameof(handler), handler); GetResolver(matcher).Add(data, handler); } /// /// Asynchronously matches a URL path against ; /// if the match is successful, tries to handle the specified /// using handlers selected according to data extracted from the context. /// Registered resolvers are tried in the same order they were added by calling /// . /// /// The context to handle. /// A , representing the ongoing operation, /// that will return a result in the form of one of the constants. /// public async Task ResolveAsync(IHttpContext context) { var result = RouteResolutionResult.RouteNotMatched; foreach (var resolver in _resolvers) { var resolverResult = await resolver.ResolveAsync(context).ConfigureAwait(false); OnResolverCalled(context, resolver, resolverResult); if (resolverResult == RouteResolutionResult.Success) return RouteResolutionResult.Success; // This is why RouteResolutionResult constants must not be reordered. if (resolverResult > result) result = resolverResult; } return result; } /// /// Locks this collection, preventing further additions. /// public void Lock() => LockConfiguration(); /// protected override void OnBeforeLockConfiguration() { foreach (var resolver in _resolvers) resolver.Lock(); } /// /// Called by /// and to create an instance /// of that can resolve the specified route. /// If this method returns , an /// is thrown by the calling method. /// /// The to match URL paths against. /// A newly-constructed instance of . protected abstract TResolver CreateResolver(RouteMatcher matcher); /// /// Called by when a resolver's /// ResolveAsync method has been called /// to resolve a context. /// This callback method may be used e.g. for logging or testing. /// /// The context to handle. /// The resolver just called. /// The result returned by .ResolveAsync. protected virtual void OnResolverCalled(IHttpContext context, TResolver resolver, RouteResolutionResult result) { } private TResolver GetResolver(RouteMatcher matcher) { var resolver = _resolvers.FirstOrDefault(r => r.Matcher.Equals(matcher)); if (resolver != null) return resolver; resolver = CreateResolver(matcher); _resolvers.Add(resolver ?? throw SelfCheck.Failure($"{nameof(CreateResolver)} returned null.")); return resolver; } } }