using System; using System.Collections.Generic; using System.Threading.Tasks; using EmbedIO.Internal; using EmbedIO.Utilities; using Swan.Configuration; namespace EmbedIO.Routing { /// /// Implements the logic for resolving the requested path of a HTTP context against a route, /// possibly handling different contexts via different handlers. /// /// The type of the data used to select a suitable handler /// for the context. /// public abstract class RouteResolverBase : ConfiguredObject { private readonly List<(TData data, RouteHandlerCallback handler)> _dataHandlerPairs = new List<(TData data, RouteHandlerCallback handler)>(); /// /// Initializes a new instance of the class. /// /// The to match URL paths against. /// /// is . /// protected RouteResolverBase(RouteMatcher matcher) { Matcher = Validate.NotNull(nameof(matcher), matcher); } /// /// Gets the used to match routes. /// public RouteMatcher Matcher { get; } /// /// Gets the route this resolver matches URL paths against. /// public string Route => Matcher.Route; /// /// Gets a value indicating whether is a base route. /// public bool IsBaseRoute => Matcher.IsBaseRoute; /// /// Associates some data to a handler. /// The method calls /// to extract data from the context; then, for each registered data / handler pair, /// is called to determine whether /// should be called. /// /// Data used to determine which contexts are /// suitable to be handled by . /// A callback used to handle matching contexts. /// is . /// /// /// /// public void Add(TData data, RouteHandlerCallback handler) { EnsureConfigurationNotLocked(); handler = Validate.NotNull(nameof(handler), handler); _dataHandlerPairs.Add((data, handler)); } /// /// Associates some data to a synchronous handler. /// The method calls /// to extract data from the context; then, for each registered data / handler pair, /// is called to determine whether /// should be called. /// /// Data used to determine which contexts are /// suitable to be handled by . /// A callback used to handle matching contexts. /// is . /// /// /// /// public void Add(TData data, SyncRouteHandlerCallback handler) { EnsureConfigurationNotLocked(); handler = Validate.NotNull(nameof(handler), handler); _dataHandlerPairs.Add((data, (ctx, route) => { handler(ctx, route); return Task.CompletedTask; })); } /// /// Locks this instance, preventing further handler additions. /// public void Lock() => LockConfiguration(); /// /// 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 data / handler pairs are tried in the same order they were added. /// /// 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) { LockConfiguration(); var match = Matcher.Match(context.RequestedPath); if (match == null) return RouteResolutionResult.RouteNotMatched; var contextData = GetContextData(context); var result = RouteResolutionResult.NoHandlerSelected; foreach (var (data, handler) in _dataHandlerPairs) { if (!MatchContextData(contextData, data)) continue; try { await handler(context, match).ConfigureAwait(false); return RouteResolutionResult.Success; } catch (RequestHandlerPassThroughException) { result = RouteResolutionResult.NoHandlerSuccessful; } } return result; } /// /// Called by to extract data from a context. /// The extracted data are then used to select which handlers are suitable /// to handle the context. /// /// The HTTP context to extract data from. /// The extracted data. /// /// protected abstract TData GetContextData(IHttpContext context); /// /// Called by to match data extracted from a context /// against data associated with a handler. /// /// The data extracted from the context. /// The data associated with the handler. /// if the handler should be called to handle the context; /// otherwise, . protected abstract bool MatchContextData(TData contextData, TData handlerData); } }