166 lines
7.4 KiB
C#
166 lines
7.4 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Threading.Tasks;
|
|
using EmbedIO.Internal;
|
|
using EmbedIO.Utilities;
|
|
using Swan.Configuration;
|
|
|
|
namespace EmbedIO.Routing
|
|
{
|
|
/// <summary>
|
|
/// Implements the logic for resolving the requested path of a HTTP context against a route,
|
|
/// possibly handling different contexts via different handlers.
|
|
/// </summary>
|
|
/// <typeparam name="TData">The type of the data used to select a suitable handler
|
|
/// for the context.</typeparam>
|
|
/// <seealso cref="ConfiguredObject" />
|
|
public abstract class RouteResolverBase<TData> : ConfiguredObject
|
|
{
|
|
private readonly List<(TData data, RouteHandlerCallback handler)> _dataHandlerPairs
|
|
= new List<(TData data, RouteHandlerCallback handler)>();
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="RouteResolverBase{TData}"/> class.
|
|
/// </summary>
|
|
/// <param name="matcher">The <see cref="RouteMatcher"/> to match URL paths against.</param>
|
|
/// <exception cref="ArgumentNullException">
|
|
/// <para><paramref name="matcher"/> is <see langword="null"/>.</para>
|
|
/// </exception>
|
|
protected RouteResolverBase(RouteMatcher matcher)
|
|
{
|
|
Matcher = Validate.NotNull(nameof(matcher), matcher);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the <see cref="RouteMatcher"/> used to match routes.
|
|
/// </summary>
|
|
public RouteMatcher Matcher { get; }
|
|
|
|
/// <summary>
|
|
/// Gets the route this resolver matches URL paths against.
|
|
/// </summary>
|
|
public string Route => Matcher.Route;
|
|
|
|
/// <summary>
|
|
/// Gets a value indicating whether <see cref="Route"/> is a base route.
|
|
/// </summary>
|
|
public bool IsBaseRoute => Matcher.IsBaseRoute;
|
|
|
|
/// <summary>
|
|
/// <para>Associates some data to a handler.</para>
|
|
/// <para>The <see cref="ResolveAsync"/> method calls <see cref="GetContextData"/>
|
|
/// to extract data from the context; then, for each registered data / handler pair,
|
|
/// <see cref="MatchContextData"/> is called to determine whether <paramref name="handler"/>
|
|
/// should be called.</para>
|
|
/// </summary>
|
|
/// <param name="data">Data used to determine which contexts are
|
|
/// suitable to be handled by <paramref name="handler"/>.</param>
|
|
/// <param name="handler">A callback used to handle matching contexts.</param>
|
|
/// <exception cref="ArgumentNullException"><paramref name="handler"/> is <see langword="null"/>.</exception>
|
|
/// <seealso cref="RouteHandlerCallback"/>
|
|
/// <seealso cref="ResolveAsync"/>
|
|
/// <seealso cref="GetContextData"/>
|
|
/// <seealso cref="MatchContextData"/>
|
|
public void Add(TData data, RouteHandlerCallback handler)
|
|
{
|
|
EnsureConfigurationNotLocked();
|
|
|
|
handler = Validate.NotNull(nameof(handler), handler);
|
|
_dataHandlerPairs.Add((data, handler));
|
|
}
|
|
|
|
/// <summary>
|
|
/// <para>Associates some data to a synchronous handler.</para>
|
|
/// <para>The <see cref="ResolveAsync"/> method calls <see cref="GetContextData"/>
|
|
/// to extract data from the context; then, for each registered data / handler pair,
|
|
/// <see cref="MatchContextData"/> is called to determine whether <paramref name="handler"/>
|
|
/// should be called.</para>
|
|
/// </summary>
|
|
/// <param name="data">Data used to determine which contexts are
|
|
/// suitable to be handled by <paramref name="handler"/>.</param>
|
|
/// <param name="handler">A callback used to handle matching contexts.</param>
|
|
/// <exception cref="ArgumentNullException"><paramref name="handler"/> is <see langword="null"/>.</exception>
|
|
/// <seealso cref="RouteHandlerCallback"/>
|
|
/// <seealso cref="ResolveAsync"/>
|
|
/// <seealso cref="GetContextData"/>
|
|
/// <seealso cref="MatchContextData"/>
|
|
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;
|
|
}));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Locks this instance, preventing further handler additions.
|
|
/// </summary>
|
|
public void Lock() => LockConfiguration();
|
|
|
|
/// <summary>
|
|
/// Asynchronously matches a URL path against <see cref="Route"/>;
|
|
/// if the match is successful, tries to handle the specified <paramref name="context"/>
|
|
/// using handlers selected according to data extracted from the context.
|
|
/// <para>Registered data / handler pairs are tried in the same order they were added.</para>
|
|
/// </summary>
|
|
/// <param name="context">The context to handle.</param>
|
|
/// <returns>A <see cref="Task"/>, representing the ongoing operation,
|
|
/// that will return a result in the form of one of the <see cref="RouteResolutionResult"/> constants.</returns>
|
|
/// <seealso cref="Add(TData,RouteHandlerCallback)"/>
|
|
/// <seealso cref="Add(TData,SyncRouteHandlerCallback)"/>
|
|
/// <seealso cref="GetContextData"/>
|
|
/// <seealso cref="MatchContextData"/>
|
|
public async Task<RouteResolutionResult> 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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// <para>Called by <see cref="ResolveAsync"/> to extract data from a context.</para>
|
|
/// <para>The extracted data are then used to select which handlers are suitable
|
|
/// to handle the context.</para>
|
|
/// </summary>
|
|
/// <param name="context">The HTTP context to extract data from.</param>
|
|
/// <returns>The extracted data.</returns>
|
|
/// <seealso cref="ResolveAsync"/>
|
|
/// <seealso cref="MatchContextData"/>
|
|
protected abstract TData GetContextData(IHttpContext context);
|
|
|
|
/// <summary>
|
|
/// Called by <see cref="ResolveAsync"/> to match data extracted from a context
|
|
/// against data associated with a handler.
|
|
/// </summary>
|
|
/// <param name="contextData">The data extracted from the context.</param>
|
|
/// <param name="handlerData">The data associated with the handler.</param>
|
|
/// <returns><see langword="true"/> if the handler should be called to handle the context;
|
|
/// otherwise, <see langword="false"/>.</returns>
|
|
protected abstract bool MatchContextData(TData contextData, TData handlerData);
|
|
}
|
|
} |