Got at least one data fetching method working; turns out, we can't use a patched LogicStack to get the data

This commit is contained in:
2026-01-14 22:11:11 +01:00
parent 40a8431464
commit 3f7122d30a
350 changed files with 41444 additions and 119 deletions

View File

@@ -0,0 +1,31 @@
using System;
namespace EmbedIO.Routing
{
/// <summary>
/// Decorate methods within controllers with this attribute in order to make them callable from the Web API Module
/// Method Must match the WebServerModule.
/// </summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public class BaseRouteAttribute : RouteAttribute
{
/// <summary>
/// Initializes a new instance of the <see cref="BaseRouteAttribute"/> class.
/// </summary>
/// <param name="verb">The verb.</param>
/// <param name="route">The route.</param>
/// <exception cref="ArgumentNullException"><paramref name="route"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentException">
/// <para><paramref name="route"/> is empty.</para>
/// <para>- or -</para>
/// <para><paramref name="route"/> does not start with a slash (<c>/</c>) character.</para>
/// <para>- or -</para>
/// <para><paramref name="route"/> does not comply with route syntax.</para>
/// </exception>
/// <seealso cref="Routing.Route.IsValid"/>
public BaseRouteAttribute(HttpVerbs verb, string route)
: base(verb, route, true)
{
}
}
}

310
Vendor/EmbedIO-3.5.2/Routing/Route.cs vendored Normal file
View File

@@ -0,0 +1,310 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using EmbedIO.WebApi;
namespace EmbedIO.Routing
{
/// <summary>
/// Provides utility methods to work with routes.
/// </summary>
/// <seealso cref="WebApiModule"/>
/// <seealso cref="WebApiController"/>
/// <seealso cref="RouteAttribute"/>
public static class Route
{
// Characters in ValidParameterNameChars MUST be in ascending ordinal order!
private static readonly char[] ValidParameterNameChars =
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz".ToCharArray();
/// <summary>
/// <para>Determines whether a string is a valid route parameter name.</para>
/// <para>To be considered a valid route parameter name, the specified string:</para>
/// <list type="bullet">
/// <item><description>must not be <see langword="null"/>;</description></item>
/// <item><description>must not be the empty string;</description></item>
/// <item><description>must consist entirely of decimal digits, upper- or lower-case
/// letters of the English alphabet, or underscore (<c>'_'</c>) characters;</description></item>
/// <item><description>must not start with a decimal digit.</description></item>
/// </list>
/// </summary>
/// <param name="value">The value.</param>
/// <returns><see langword="true"/> if <paramref name="value"/> is a valid route parameter;
/// otherwise, <see langword="false"/>.</returns>
public static bool IsValidParameterName(string value)
=> !string.IsNullOrEmpty(value)
&& value[0] > '9'
&& !value.Any(c => c < '0' || c > 'z' || Array.BinarySearch(ValidParameterNameChars, c) < 0);
/// <summary>
/// <para>Determines whether a string is a valid route.</para>
/// <para>To be considered a valid route, the specified string:</para>
/// <list type="bullet">
/// <item><description>must not be <see langword="null"/>;</description></item>
/// <item><description>must not be the empty string;</description></item>
/// <item><description>must start with a slash (<c>'/'</c>) character;</description></item>
/// <item><description>if a base route, must end with a slash (<c>'/'</c>) character;</description></item>
/// <item><description>if not a base route, must not end with a slash (<c>'/'</c>) character,
/// unless it is the only character in the string;</description></item>
/// <item><description>must not contain consecutive runs of two or more slash (<c>'/'</c>) characters;</description></item>
/// <item><description>may contain one or more parameter specifications.</description></item>
/// </list>
/// <para>Each parameter specification must be enclosed in curly brackets (<c>'{'</c>
/// and <c>'}'</c>. No whitespace is allowed inside a parameter specification.</para>
/// <para>Two parameter specifications must be separated by literal text.</para>
/// <para>A parameter specification consists of a valid parameter name, optionally
/// followed by a <c>'?'</c> character to signify that it will also match an empty string.</para>
/// <para>If <c>'?'</c> is not present, a parameter by default will NOT match an empty string.</para>
/// <para>See <see cref="IsValidParameterName"/> for the definition of a valid parameter name.</para>
/// <para>To include a literal open curly bracket in the route, it must be doubled (<c>"{{"</c>).</para>
/// <para>A literal closed curly bracket (<c>'}'</c>) may be included in the route as-is.</para>
/// <para>A segment of a base route cannot consist only of an optional parameter.</para>
/// </summary>
/// <param name="route">The route to check.</param>
/// <param name="isBaseRoute"><see langword="true"/> if checking for a base route;
/// otherwise, <see langword="false"/>.</param>
/// <returns><see langword="true"/> if <paramref name="route"/> is a valid route;
/// otherwise, <see langword="false"/>.</returns>
public static bool IsValid(string route, bool isBaseRoute) => ValidateInternal(nameof(route), route, isBaseRoute) == null;
// Check the validity of a route by parsing it without storing the results.
// Returns: ArgumentNullException, ArgumentException, null if OK
internal static Exception? ValidateInternal(string argumentName, string value, bool isBaseRoute) => ParseInternal(value, isBaseRoute, null) switch {
ArgumentNullException _ => new ArgumentNullException(argumentName),
FormatException formatException => new ArgumentException(formatException.Message, argumentName),
Exception exception => exception,
_ => null
};
// Validate and parse a route, constructing a Regex pattern.
// setResult will be called at the end with the isBaseRoute flag, parameter names and the constructed pattern.
// Returns: ArgumentNullException, FormatException, null if OK
internal static Exception? ParseInternal(string route, bool isBaseRoute, Action<bool, IEnumerable<string>, string>? setResult)
{
if (route == null)
return new ArgumentNullException(nameof(route));
if (route.Length == 0)
return new FormatException("Route is empty.");
if (route[0] != '/')
return new FormatException("Route does not start with a slash.");
/*
* Regex options set at start of pattern:
* IgnoreCase : no
* Multiline : no
* Singleline : yes
* ExplicitCapture : yes
* IgnorePatternWhitespace : no
* See https://docs.microsoft.com/en-us/dotnet/standard/base-types/regular-expression-options
* See https://docs.microsoft.com/en-us/dotnet/standard/base-types/grouping-constructs-in-regular-expressions#group_options
*/
const string InitialRegexOptions = "(?sn-imx)";
// If setResult is null we don't need the StringBuilder.
var sb = setResult == null ? null : new StringBuilder("^");
var parameterNames = new List<string>();
if (route.Length == 1)
{
// If the route consists of a single slash, only a single slash will match.
sb?.Append(isBaseRoute ? "/" : "/$");
}
else
{
// First of all divide the route in segments.
// Segments are separated by slashes.
// The route is not necessarily normalized, so there could be runs of consecutive slashes.
var segmentCount = 0;
var optionalSegmentCount = 0;
foreach (var segment in GetSegments(route))
{
segmentCount++;
// Parse the segment, looking alternately for a '{', that opens a parameter specification,
// then for a '}', that closes it.
// Characters outside parameter specifications are Regex-escaped and added to the pattern.
// A parameter specification consists of a parameter name, optionally followed by '?'
// to indicate that an empty parameter will match.
// The default is to NOT match empty parameters, consistently with ASP.NET and EmbedIO version 2.
// More syntax rules:
// - There cannot be two parameters without literal text in between.
// - If a segment consists ONLY of an OPTIONAL parameter, then the slash preceding it is optional too.
var inParameterSpec = false;
var afterParameter = false;
for (var position = 0; ;)
{
if (inParameterSpec)
{
// Look for end of spec, bail out if not found.
var closePosition = segment.IndexOf('}', position);
if (closePosition < 0)
return new FormatException("Route syntax error: unclosed parameter specification.");
// Parameter spec cannot be empty.
if (closePosition == position)
return new FormatException("Route syntax error: empty parameter specification.");
// Check the last character:
// {name} means empty parameter does not match
// {name?} means empty parameter matches
// If '?'is found, the parameter name ends before it
var nameEndPosition = closePosition;
var allowEmpty = false;
if (segment[closePosition - 1] == '?')
{
allowEmpty = true;
nameEndPosition--;
}
// Bail out if only '?' is found inside the spec.
if (nameEndPosition == position)
return new FormatException("Route syntax error: missing parameter name.");
// Extract the parameter name.
var parameterName = segment.Substring(position, nameEndPosition - position);
// Ensure that the parameter name contains only valid characters.
if (!IsValidParameterName(parameterName))
return new FormatException("Route syntax error: parameter name contains one or more invalid characters.");
// Ensure that the parameter name is not a duplicate.
if (parameterNames.Contains(parameterName))
return new FormatException("Route syntax error: duplicate parameter name.");
// The spec is valid, so add the parameter to the list.
parameterNames.Add(parameterName);
// Append a capturing group with the same name to the pattern.
// Parameters must be made of non-slash characters ("[^/]")
// and must match non-greedily ("*?" if optional, "+?" if non optional).
// Position will be 1 at the start, not 0, because we've skipped the opening '{'.
if (allowEmpty && position == 1 && closePosition == segment.Length - 1)
{
if (isBaseRoute)
return new FormatException("No segment of a base route can be optional.");
// If the segment consists only of an optional parameter,
// then the slash preceding the segment is optional as well.
// In this case the parameter must match only is not empty,
// because it's (slash + parameter) that is optional.
sb?.Append("(/(?<").Append(parameterName).Append(">[^/]+?))?");
optionalSegmentCount++;
}
else
{
// If at the start of a segment, don't forget the slash!
// Position will be 1 at the start, not 0, because we've skipped the opening '{'.
if (position == 1)
sb?.Append('/');
sb?.Append("(?<").Append(parameterName).Append(">[^/]").Append(allowEmpty ? '*' : '+').Append("?)");
}
// Go on with parsing.
position = closePosition + 1;
inParameterSpec = false;
afterParameter = true;
}
else
{
// Look for start of parameter spec.
var openPosition = segment.IndexOf('{', position);
if (openPosition < 0)
{
// If at the start of a segment, don't forget the slash.
if (position == 0)
sb?.Append('/');
// No more parameter specs: escape the remainder of the string
// and add it to the pattern.
sb?.Append(Regex.Escape(segment.Substring(position)));
break;
}
var nextPosition = openPosition + 1;
if (nextPosition < segment.Length && segment[nextPosition] == '{')
{
// If another identical char follows, treat the two as a single literal char.
// If at the start of a segment, don't forget the slash!
if (position == 0)
sb?.Append('/');
sb?.Append(@"\\{");
}
else if (afterParameter && openPosition == position)
{
// If a parameter immediately follows another parameter,
// with no literal text in between, it's a syntax error.
return new FormatException("Route syntax error: parameters must be separated by literal text.");
}
else
{
// If at the start of a segment, don't forget the slash,
// but only if there actually is some literal text.
// Otherwise let the parameter spec parsing code deal with the slash,
// because we don't know whether this is an optional segment yet.
if (position == 0 && openPosition > 0)
sb?.Append('/');
// Escape the part of the pattern outside the parameter spec
// and add it to the pattern.
sb?.Append(Regex.Escape(segment.Substring(position, openPosition - position)));
inParameterSpec = true;
}
// Go on parsing.
position = nextPosition;
afterParameter = false;
}
}
}
// Close the pattern
sb?.Append(isBaseRoute ? "(/|$)" : "$");
// If all segments are optional segments, "/" must match too.
if (optionalSegmentCount == segmentCount)
sb?.Insert(0, "(/$)|(").Append(')');
}
// Pass the results to the callback if needed.
setResult?.Invoke(isBaseRoute, parameterNames, InitialRegexOptions + sb);
// Everything's fine, thus no exception.
return null;
}
// Enumerate the segments of a route, ignoring consecutive slashes.
private static IEnumerable<string> GetSegments(string route)
{
var length = route.Length;
var position = 0;
for (; ; )
{
while (route[position] == '/')
{
position++;
if (position >= length)
break;
}
if (position >= length)
break;
var slashPosition = route.IndexOf('/', position);
if (slashPosition < 0)
{
yield return route.Substring(position);
break;
}
yield return route.Substring(position, slashPosition - position);
position = slashPosition;
}
}
}
}

View File

@@ -0,0 +1,54 @@
using System;
namespace EmbedIO.Routing
{
/// <summary>
/// Decorate methods within controllers with this attribute in order to make them callable from the Web API Module
/// Method Must match the WebServerModule.
/// </summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public class RouteAttribute : Attribute
{
/// <summary>
/// Initializes a new instance of the <see cref="RouteAttribute"/> class.
/// </summary>
/// <param name="isBaseRoute"><see langword="true"/> if this attribute represents a base route;
/// <see langword="false"/> (the default) if it represents a terminal (non-base) route.</param>
/// <param name="verb">The verb.</param>
/// <param name="route">The route.</param>
/// <exception cref="ArgumentNullException"><paramref name="route"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentException">
/// <para><paramref name="route"/> is empty.</para>
/// <para>- or -</para>
/// <para><paramref name="route"/> does not start with a slash (<c>/</c>) character.</para>
/// <para>- or -</para>
/// <para><paramref name="route"/> does not comply with route syntax.</para>
/// </exception>
/// <seealso cref="Routing.Route.IsValid"/>
public RouteAttribute(HttpVerbs verb, string route, bool isBaseRoute = false)
{
Matcher = RouteMatcher.Parse(route, isBaseRoute);
Verb = verb;
}
/// <summary>
/// Gets the HTTP verb handled by a method with this attribute.
/// </summary>
public HttpVerbs Verb { get; }
/// <summary>
/// Gets a <see cref="RouteMatcher"/> that will match URLs against this attribute's data.
/// </summary>
public RouteMatcher Matcher { get; }
/// <summary>
/// Gets the route handled by a method with this attribute.
/// </summary>
public string Route => Matcher.Route;
/// <summary>
/// Gets a value indicating whether this attribute represents a base route.
/// </summary>
public bool IsBaseRoute => Matcher.IsBaseRoute;
}
}

View File

@@ -0,0 +1,13 @@
using System.Threading.Tasks;
namespace EmbedIO.Routing
{
/// <summary>
/// Base class for callbacks used to handle routed requests.
/// </summary>
/// <param name="context">An <see cref="IHttpContext" /> interface representing the context of the request.</param>
/// <param name="route">The matched route.</param>
/// <returns>A <see cref="Task" /> representing the ongoing operation.</returns>
/// <seealso cref="RouteMatch"/>
public delegate Task RouteHandlerCallback(IHttpContext context, RouteMatch route);
}

View File

@@ -0,0 +1,199 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using EmbedIO.Utilities;
namespace EmbedIO.Routing
{
/// <summary>
/// <para>Represents a route resolved by a <see cref="RouteResolverBase{TData}"/>.</para>
/// <para>This class may be used both as a dictionary of route parameter names and values,
/// and a list of the values.</para>
/// <para>Because of its double nature, this class cannot be enumerated directly. However,
/// you may use the <see cref="Pairs"/> property to iterate over name / value pairs, and the
/// <see cref="Values"/> property to iterate over values.</para>
/// <para>When enumerated in a non-generic fashion via the <see cref="IEnumerable"/> interface,
/// this class iterates over name / value pairs.</para>
/// </summary>
#pragma warning disable CA1710 // Rename class to end in "Collection"
public sealed class RouteMatch : IReadOnlyList<string>, IReadOnlyDictionary<string, string>
#pragma warning restore CA1710
{
private static readonly IReadOnlyList<string> EmptyStringList = Array.Empty<string>();
private readonly IReadOnlyList<string> _values;
internal RouteMatch(string path, IReadOnlyList<string> names, IReadOnlyList<string> values, string? subPath)
{
Path = path;
Names = names;
_values = values;
SubPath = subPath;
}
/// <summary>
/// Gets a <see cref="RouteMatch"/> instance that represents no match at all.
/// </summary>
/// <remarks>
/// <para>The <see cref="RouteMatch"/> instance returned by this property
/// has the following specifications:</para>
/// <list type="bullet">
/// <item><description>its <see cref="Path">Path</see> property is the empty string;</description></item>
/// <item><description>it has no parameters;</description></item>
/// <item><description>its <see cref="SubPath">SubPath</see> property is <see langword="null"/>.</description></item>
/// </list>
/// <para>This <see cref="RouteMatch"/> instance is only useful to initialize
/// a non-nullable property of type <see cref="RouteMatch"/>, provided that it is subsequently
/// set to a meaningful value before being used.</para>
/// </remarks>
public static RouteMatch None { get; } = new RouteMatch(
string.Empty,
Array.Empty<string>(),
Array.Empty<string>(),
null);
/// <summary>
/// Gets the URL path that was successfully matched against the route.
/// </summary>
public string Path { get; }
/// <summary>
/// <para>For a base route, gets the part of <see cref="Path"/> that follows the matched route;
/// for a non-base route, this property is always <see langword="null"/>.</para>
/// </summary>
public string? SubPath { get; }
/// <summary>
/// Gets a list of the names of the route's parameters.
/// </summary>
public IReadOnlyList<string> Names { get; }
/// <inheritdoc cref="IReadOnlyCollection{T}.Count"/>
public int Count => _values.Count;
/// <inheritdoc />
public IEnumerable<string> Keys => Names;
/// <inheritdoc />
public IEnumerable<string> Values => _values;
/// <summary>
/// Gets an <see cref="IEnumerable{T}"/> interface that can be used
/// to iterate over name / value pairs.
/// </summary>
public IEnumerable<KeyValuePair<string, string>> Pairs => this;
/// <inheritdoc />
public string this[int index] => _values[index];
/// <inheritdoc />
public string this[string key]
{
get
{
var count = Names.Count;
for (var i = 0; i < count; i++)
{
if (Names[i] == key)
{
return _values[i];
}
}
throw new KeyNotFoundException("The parameter name was not found.");
}
}
/// <summary>
/// Returns a <see cref="RouteMatch"/> object equal to the one
/// that would result by matching the specified URL path against a
/// base route of <c>"/"</c>.
/// </summary>
/// <param name="urlPath">The URL path to match.</param>
/// <returns>A newly-constructed <see cref="RouteMatch"/>.</returns>
/// <remarks>
/// <para>This method assumes that <paramref name="urlPath"/>
/// is a valid, non-base URL path or route. Otherwise, the behavior of this method
/// is unspecified.</para>
/// <para>Ensure that you validate <paramref name="urlPath"/> before
/// calling this method, using either <see cref="Validate.UrlPath"/>
/// or <see cref="UrlPath.IsValid"/>.</para>
/// </remarks>
public static RouteMatch UnsafeFromRoot(string urlPath)
=> new RouteMatch(urlPath, EmptyStringList, EmptyStringList, urlPath);
/// <summary>
/// Returns a <see cref="RouteMatch"/> object equal to the one
/// that would result by matching the specified URL path against
/// the specified parameterless base route.
/// </summary>
/// <param name="baseUrlPath">The base route to match <paramref name="urlPath"/> against.</param>
/// <param name="urlPath">The URL path to match.</param>
/// <returns>A newly-constructed <see cref="RouteMatch"/>.</returns>
/// <remarks>
/// <para>This method assumes that <paramref name="baseUrlPath"/> is a
/// valid base URL path, and <paramref name="urlPath"/>
/// is a valid, non-base URL path or route. Otherwise, the behavior of this method
/// is unspecified.</para>
/// <para>Ensure that you validate both parameters before
/// calling this method, using either <see cref="Validate.UrlPath"/>
/// or <see cref="UrlPath.IsValid"/>.</para>
/// </remarks>
public static RouteMatch? UnsafeFromBasePath(string baseUrlPath, string urlPath)
{
var subPath = UrlPath.UnsafeStripPrefix(urlPath, baseUrlPath);
return subPath == null ? null : new RouteMatch(urlPath, EmptyStringList, EmptyStringList, "/" + subPath);
}
/// <inheritdoc />
public bool ContainsKey(string key) => Names.Any(n => n == key);
/// <inheritdoc />
public bool TryGetValue(string key, out string? value)
{
var count = Names.Count;
for (var i = 0; i < count; i++)
{
if (Names[i] == key)
{
value = _values[i];
return true;
}
}
value = null;
return false;
}
/// <summary>
/// Returns the index of the parameter with the specified name.
/// </summary>
/// <param name="name">The parameter name.</param>
/// <returns>The index of the parameter, or -1 if none of the
/// route parameters have the specified name.</returns>
public int IndexOf(string name)
{
var count = Names.Count;
for (var i = 0; i < count; i++)
{
if (Names[i] == name)
{
return i;
}
}
return -1;
}
/// <inheritdoc />
IEnumerator<KeyValuePair<string, string>> IEnumerable<KeyValuePair<string, string>>.GetEnumerator()
=> Names.Zip(_values, (n, v) => new KeyValuePair<string, string>(n, v)).GetEnumerator();
/// <inheritdoc />
IEnumerator<string> IEnumerable<string>.GetEnumerator() => _values.GetEnumerator();
/// <inheritdoc />
IEnumerator IEnumerable.GetEnumerator() => Pairs.GetEnumerator();
}
}

View File

@@ -0,0 +1,183 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text.RegularExpressions;
using EmbedIO.Utilities;
using Swan;
namespace EmbedIO.Routing
{
/// <summary>
/// Matches URL paths against a route.
/// </summary>
public sealed class RouteMatcher : IEquatable<RouteMatcher>
{
private static readonly object SyncRoot = new object();
private static readonly Dictionary<(bool, string), RouteMatcher> Cache = new Dictionary<(bool, string), RouteMatcher>();
private readonly Regex _regex;
private RouteMatcher(bool isBaseRoute, string route, string pattern, IReadOnlyList<string> parameterNames)
{
IsBaseRoute = isBaseRoute;
Route = route;
ParameterNames = parameterNames;
_regex = new Regex(pattern, RegexOptions.Compiled | RegexOptions.CultureInvariant);
}
/// <summary>
/// Gets a value indicating whether the <see cref="Route"/> property
/// is a base route.
/// </summary>
public bool IsBaseRoute { get; }
/// <summary>
/// Gets the route this instance matches URL paths against.
/// </summary>
public string Route { get; }
/// <summary>
/// Gets the names of the route's parameters.
/// </summary>
public IReadOnlyList<string> ParameterNames { get; }
/// <summary>
/// Constructs an instance of <see cref="RouteMatcher"/> by parsing the specified route.
/// <para>If the same route was previously parsed and the <see cref="ClearCache"/> method has not been called since,
/// this method obtains an instance from a static cache.</para>
/// </summary>
/// <param name="route">The route to parse.</param>
/// <param name="isBaseRoute"><see langword="true"/> if the route to parse
/// is a base route; otherwise, <see langword="false"/>.</param>
/// <returns>A newly-constructed instance of <see cref="RouteMatcher"/>
/// that will match URL paths against <paramref name="route"/>.</returns>
/// <exception cref="ArgumentNullException"><paramref name="route"/> is <see langword="null"/>.</exception>
/// <exception cref="FormatException"><paramref name="route"/> is not a valid route.</exception>
/// <seealso cref="TryParse"/>
/// <seealso cref="ClearCache"/>
public static RouteMatcher Parse(string route, bool isBaseRoute)
{
var exception = TryParseInternal(route, isBaseRoute, out var result);
if (exception != null)
throw exception;
return result!;
}
/// <summary>
/// <para>Attempts to obtain an instance of <see cref="RouteMatcher" /> by parsing the specified route.</para>
/// <para>If the same route was previously parsed and the <see cref="ClearCache"/> method has not been called since,
/// this method obtains an instance from a static cache.</para>
/// </summary>
/// <param name="route">The route to parse.</param>
/// <param name="isBaseRoute"><see langword="true"/> if the route to parse
/// is a base route; otherwise, <see langword="false"/>.</param>
/// <param name="result">When this method returns <see langword="true"/>, a newly-constructed instance of <see cref="RouteMatcher" />
/// that will match URL paths against <paramref name="route"/>; otherwise, <see langword="null"/>.
/// This parameter is passed uninitialized.</param>
/// <returns><see langword="true"/> if parsing was successful; otherwise, <see langword="false"/>.</returns>
/// <seealso cref="Parse"/>
/// <seealso cref="ClearCache"/>
public static bool TryParse(string route, bool isBaseRoute, out RouteMatcher? result)
=> TryParseInternal(route, isBaseRoute, out result) == null;
/// <summary>
/// Clears <see cref="RouteMatcher"/>'s internal instance cache.
/// </summary>
/// <seealso cref="Parse"/>
/// <seealso cref="TryParse"/>
public static void ClearCache()
{
lock (SyncRoot)
{
Cache.Clear();
}
}
/// <summary>
/// Returns a hash code for this instance.
/// </summary>
/// <returns>A hash code for this instance, suitable for use in hashing algorithms
/// and data structures like a hash table.</returns>
public override int GetHashCode() => CompositeHashCode.Using(Route, IsBaseRoute);
/// <summary>
/// Determines whether the specified <see cref="object"/> is equal to this instance.
/// </summary>
/// <param name="obj">The <see cref="object"/> to compare with this instance.</param>
/// <returns><see langword="true"/> if <paramref name="obj"/> is equal to this instance;
/// otherwise, <see langword="false"/>.</returns>
public override bool Equals(object? obj) => obj is RouteMatcher other && Equals(other);
/// <summary>
/// Indicates whether this instance is equal to another instance of <see cref="RouteMatcher"/>.
/// </summary>
/// <param name="other">A <see cref="RouteMatcher"/> to compare with this instance.</param>
/// <returns><see langword="true"/> if this instance is equal to <paramref name="other"/>;
/// otherwise, <see langword="false"/>.</returns>
public bool Equals(RouteMatcher? other)
=> other != null
&& other.Route == Route
&& other.IsBaseRoute == IsBaseRoute;
/// <summary>
/// Matches the specified URL path against <see cref="Route"/>
/// and extracts values for the route's parameters.
/// </summary>
/// <param name="path">The URL path to match.</param>
/// <returns>If the match is successful, a <see cref="RouteMatch"/> object;
/// otherwise, <see langword="null"/>.</returns>
public RouteMatch? Match(string path)
{
if (path == null)
return null;
// Optimize for parameterless base routes
if (IsBaseRoute)
{
if (Route.Length == 1)
return RouteMatch.UnsafeFromRoot(path);
if (ParameterNames.Count == 0)
return RouteMatch.UnsafeFromBasePath(Route, path);
}
var match = _regex.Match(path);
if (!match.Success)
return null;
return new RouteMatch(
path,
ParameterNames,
match.Groups.Cast<Group>().Skip(1).Select(g => WebUtility.UrlDecode(g.Value)).ToArray(),
IsBaseRoute ? "/" + path.Substring(match.Groups[0].Length) : null);
}
private static Exception? TryParseInternal(string route, bool isBaseRoute, out RouteMatcher? result)
{
lock (SyncRoot)
{
string? pattern = null;
var parameterNames = new List<string>();
var exception = Routing.Route.ParseInternal(route, isBaseRoute, (_, n, p) => {
parameterNames.AddRange(n);
pattern = p;
});
if (exception != null)
{
result = null;
return exception;
}
route = UrlPath.UnsafeNormalize(route, isBaseRoute);
if (Cache.TryGetValue((isBaseRoute, route), out result))
return null;
result = new RouteMatcher(isBaseRoute, route, pattern!, parameterNames);
Cache.Add((isBaseRoute, route), result);
return null;
}
}
}
}

View File

@@ -0,0 +1,35 @@
namespace EmbedIO.Routing
{
/// <summary>
/// Represents the outcome of resolving a context and a path against a route.
/// </summary>
public enum RouteResolutionResult
{
/* DO NOT reorder members!
* RouteNotMatched < NoHandlerSelected < NoHandlerSuccessful < Success
*
* See comments in RouteResolverBase<,>.ResolveAsync for further explanation.
*/
/// <summary>
/// The route didn't match.
/// </summary>
RouteNotMatched,
/// <summary>
/// The route did match, but no registered handler was suitable for the context.
/// </summary>
NoHandlerSelected,
/// <summary>
/// The route matched and one or more suitable handlers were found,
/// but none of them returned <see langword="true"/>.
/// </summary>
NoHandlerSuccessful,
/// <summary>
/// The route has been resolved.
/// </summary>
Success,
}
}

View File

@@ -0,0 +1,166 @@
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);
}
}

View File

@@ -0,0 +1,150 @@
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
{
/// <summary>
/// Implements the logic for resolving a context and a URL path against a list of routes,
/// possibly handling different HTTP methods via different handlers.
/// </summary>
/// <typeparam name="TData">The type of the data used to select a suitable handler
/// for a context.</typeparam>
/// <typeparam name="TResolver">The type of the route resolver.</typeparam>
/// <seealso cref="Utilities.ComponentCollection{T}" />
public abstract class RouteResolverCollectionBase<TData, TResolver> : ConfiguredObject
where TResolver : RouteResolverBase<TData>
{
private readonly List<TResolver> _resolvers = new List<TResolver>();
/// <summary>
/// Associates some data and a route to a handler.
/// </summary>
/// <param name="data">Data used to determine which contexts are
/// suitable to be handled by <paramref name="handler"/>.</param>
/// <param name="matcher">The <see cref="RouteMatcher"/>to match URL paths against.</param>
/// <param name="handler">A callback used to handle matching contexts.</param>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="matcher"/> is <see langword="null"/>.</para>
/// <para>- or -</para>
/// <para><paramref name="handler"/> is <see langword="null"/>.</para>
/// </exception>
/// <exception cref="InvalidOperationException">The <see cref="CreateResolver"/> method
/// returned <see langword="null"/>.</exception>
/// <seealso cref="ResolveAsync"/>
/// <seealso cref="Add(TData,RouteMatcher,SyncRouteHandlerCallback)"/>
/// <seealso cref="RouteResolverBase{TData}.Add(TData,RouteHandlerCallback)"/>
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);
}
/// <summary>
/// Associates some data and a route to a synchronous handler.
/// </summary>
/// <param name="data">Data used to determine which contexts are
/// suitable to be handled by <paramref name="handler"/>.</param>
/// <param name="matcher">The <see cref="RouteMatcher"/>to match URL paths against.</param>
/// <param name="handler">A callback used to handle matching contexts.</param>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="matcher"/> is <see langword="null"/>.</para>
/// <para>- or -</para>
/// <para><paramref name="handler"/> is <see langword="null"/>.</para>
/// </exception>
/// <exception cref="InvalidOperationException">The <see cref="CreateResolver"/> method
/// returned <see langword="null"/>.</exception>
/// <seealso cref="ResolveAsync"/>
/// <seealso cref="Add(TData,RouteMatcher,RouteHandlerCallback)"/>
/// <seealso cref="RouteResolverBase{TData}.Add(TData,SyncRouteHandlerCallback)"/>
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);
}
/// <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 resolvers are tried in the same order they were added by calling
/// <see cref="Utilities.IComponentCollection{T}.Add"/>.</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="RouteResolverBase{TData}.ResolveAsync"/>
public async Task<RouteResolutionResult> 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;
}
/// <summary>
/// Locks this collection, preventing further additions.
/// </summary>
public void Lock() => LockConfiguration();
/// <inheritdoc />
protected override void OnBeforeLockConfiguration()
{
foreach (var resolver in _resolvers)
resolver.Lock();
}
/// <summary>
/// <para>Called by <see cref="Add(TData,RouteMatcher,RouteHandlerCallback)"/>
/// and <see cref="Add(TData,RouteMatcher,SyncRouteHandlerCallback)"/> to create an instance
/// of <typeparamref name="TResolver"/> that can resolve the specified route.</para>
/// <para>If this method returns <see langword="null"/>, an <see cref="InvalidOperationException"/>
/// is thrown by the calling method.</para>
/// </summary>
/// <param name="matcher">The <see cref="RouteMatcher"/>to match URL paths against.</param>
/// <returns>A newly-constructed instance of <typeparamref name="TResolver"/>.</returns>
protected abstract TResolver CreateResolver(RouteMatcher matcher);
/// <summary>
/// <para>Called by <see cref="ResolveAsync"/> when a resolver's
/// <see cref="RouteResolverBase{TData}.ResolveAsync">ResolveAsync</see> method has been called
/// to resolve a context.</para>
/// <para>This callback method may be used e.g. for logging or testing.</para>
/// </summary>
/// <param name="context">The context to handle.</param>
/// <param name="resolver">The resolver just called.</param>
/// <param name="result">The result returned by <paramref name="resolver"/>.<see cref="RouteResolverBase{TData}.ResolveAsync">ResolveAsync</see>.</param>
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;
}
}
}

View File

@@ -0,0 +1,30 @@
using System;
namespace EmbedIO.Routing
{
/// <summary>
/// Handles a HTTP request by matching it against a route,
/// possibly handling different HTTP methods via different handlers.
/// </summary>
public sealed class RouteVerbResolver : RouteResolverBase<HttpVerbs>
{
/// <summary>
/// Initializes a new instance of the <see cref="RouteVerbResolver"/> 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>
public RouteVerbResolver(RouteMatcher matcher)
: base(matcher)
{
}
/// <inheritdoc />
protected override HttpVerbs GetContextData(IHttpContext context) => context.Request.HttpVerb;
/// <inheritdoc />
protected override bool MatchContextData(HttpVerbs contextVerb, HttpVerbs handlerVerb)
=> handlerVerb == HttpVerbs.Any || contextVerb == handlerVerb;
}
}

View File

@@ -0,0 +1,140 @@
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
{
/// <summary>
/// Handles a HTTP request by matching it against a list of routes,
/// possibly handling different HTTP methods via different handlers.
/// </summary>
/// <seealso cref="RouteResolverBase{TData}"/>
/// <seealso cref="RouteVerbResolver"/>
public sealed class RouteVerbResolverCollection : RouteResolverCollectionBase<HttpVerbs, RouteVerbResolver>
{
private readonly string _logSource;
internal RouteVerbResolverCollection(string logSource)
{
_logSource = logSource;
}
/// <summary>
/// <para>Adds handlers, associating them with HTTP method / route pairs by means
/// of <see cref="RouteAttribute">Route</see> attributes.</para>
/// <para>A compatible handler is a static or instance method that takes 2
/// parameters having the following types, in order:</para>
/// <list type="number">
/// <item><description><see cref="IHttpContext"/></description></item>
/// <item><description><see cref="RouteMatch"/></description></item>
/// </list>
/// <para>The return type of a compatible handler may be either <see langword="void"/>
/// or <see cref="Task"/>.</para>
/// <para>A compatible handler, in order to be added to a <see cref="RouteVerbResolverCollection"/>,
/// must have one or more <see cref="RouteAttribute">Route</see> 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).</para>
/// <para>This method behaves according to the type of the <paramref name="target"/>
/// parameter:</para>
/// <list type="bullet">
/// <item><description>if <paramref name="target"/> is a <see cref="Type"/>, 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;</description></item>
/// <item><description>if <paramref name="target"/> is an <see cref="Assembly"/>, 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;</description></item>
/// <item><description>if <paramref name="target"/> is a <see cref="MethodInfo"/> referring to a compatible handler,
/// it will be added to the collection;</description></item>
/// <item><description>if <paramref name="target"/> is a <see langword="delegate"/> whose <see cref="Delegate.Method">Method</see>
/// refers to a compatible handler, that method will be added to the collection;</description></item>
/// <item><description>if <paramref name="target"/> 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 <paramref name="target"/>
/// and added to the collection.</description></item>
/// </list>
/// </summary>
/// <param name="target">Where to look for compatible handlers. See the Summary section for more information.</param>
/// <returns>
/// <para>The number of handlers that were added to the collection.</para>
/// <para>Note that methods with multiple <see cref="RouteAttribute">Route</see> attributes
/// will count as one for each attribute.</para>
/// </returns>
/// <exception cref="ArgumentNullException"><paramref name="target"/> is <see langword="null"/>.</exception>
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())
};
/// <inheritdoc />
protected override RouteVerbResolver CreateResolver(RouteMatcher matcher) => new RouteVerbResolver(matcher);
/// <inheritdoc />
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<RouteAttribute>().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<Expression>());
if (isSynchronous)
{
// Convert void to Task by evaluating Task.CompletedTask
body = Expression.Block(typeof(Task), body, Expression.Constant(Task.CompletedTask));
}
var handler = Expression.Lambda<RouteHandlerCallback>(body, parameters).Compile();
foreach (var attribute in attributes)
{
Add(attribute.Verb, attribute.Matcher, handler);
}
return attributes.Length;
}
}
}

View File

@@ -0,0 +1,62 @@
using System;
namespace EmbedIO.Routing
{
/// <summary>
/// A module that handles requests by resolving route / method pairs associated with handlers.
/// </summary>
/// <seealso cref="WebModuleBase" />
public class RoutingModule : RoutingModuleBase
{
/// <inheritdoc cref="WebModuleBase(string)"/>
/// <summary>
/// Initializes a new instance of the <see cref="RoutingModule"/> class.
/// </summary>
public RoutingModule(string baseRoute)
: base(baseRoute)
{
}
/// <summary>
/// Associates a HTTP method and a route to a handler.
/// </summary>
/// <param name="verb">A <see cref="HttpVerbs"/> constant representing the HTTP method
/// to associate with <paramref name="handler"/>, or <see cref="HttpVerbs.Any"/>
/// if <paramref name="handler"/> can handle all HTTP methods.</param>
/// <param name="matcher">The <see cref="RouteMatcher"/> used to match URL paths.</param>
/// <param name="handler">A callback used to handle matching contexts.</param>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="matcher"/> is <see langword="null"/>.</para>
/// <para>- or -</para>
/// <para><paramref name="handler"/> is <see langword="null"/>.</para>
/// </exception>
public void Add(HttpVerbs verb, RouteMatcher matcher, RouteHandlerCallback handler)
=> AddHandler(verb, matcher, handler);
/// <summary>
/// Associates a HTTP method and a route to a synchronous handler.
/// </summary>
/// <param name="verb">A <see cref="HttpVerbs"/> constant representing the HTTP method
/// to associate with <paramref name="handler"/>, or <see cref="HttpVerbs.Any"/>
/// if <paramref name="handler"/> can handle all HTTP methods.</param>
/// <param name="matcher">The <see cref="RouteMatcher"/> used to match URL paths.</param>
/// <param name="handler">A callback used to handle matching contexts.</param>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="matcher"/> is <see langword="null"/>.</para>
/// <para>- or -</para>
/// <para><paramref name="handler"/> is <see langword="null"/>.</para>
/// </exception>
public void Add(HttpVerbs verb, RouteMatcher matcher, SyncRouteHandlerCallback handler)
=> AddHandler(verb, matcher, handler);
/// <summary>
/// <para>Adds handlers, associating them with HTTP method / route pairs by means
/// of <see cref="RouteAttribute">Route</see> attributes.</para>
/// <para>See <see cref="RouteVerbResolverCollection.AddFrom(object)"/> for further information.</para>
/// </summary>
/// <param name="target">Where to look for compatible handlers.</param>
/// <returns>The number of handlers that were added.</returns>
/// <exception cref="ArgumentNullException"><paramref name="target"/> is <see langword="null"/>.</exception>
public int AddFrom(object target) => AddHandlersFrom(target);
}
}

View File

@@ -0,0 +1,317 @@
using System;
using System.Threading.Tasks;
using Swan;
namespace EmbedIO.Routing
{
/// <summary>
/// Base class for modules that handle requests by resolving route / method pairs associated with handlers.
/// </summary>
/// <seealso cref="WebModuleBase" />
public abstract class RoutingModuleBase : WebModuleBase
{
private readonly RouteVerbResolverCollection _resolvers = new RouteVerbResolverCollection(nameof(RoutingModuleBase));
/// <inheritdoc cref="WebModuleBase(string)"/>
/// <summary>
/// Initializes a new instance of the <see cref="RoutingModuleBase"/> class.
/// </summary>
protected RoutingModuleBase(string baseRoute)
: base(baseRoute)
{
}
/// <inheritdoc />
public override bool IsFinalHandler => true;
/// <inheritdoc />
protected override async Task OnRequestAsync(IHttpContext context)
{
var result = await _resolvers.ResolveAsync(context).ConfigureAwait(false);
switch (result)
{
case RouteResolutionResult.RouteNotMatched:
case RouteResolutionResult.NoHandlerSuccessful:
await OnPathNotFoundAsync(context).ConfigureAwait(false);
break;
case RouteResolutionResult.NoHandlerSelected:
await OnMethodNotAllowedAsync(context).ConfigureAwait(false);
break;
case RouteResolutionResult.Success:
return;
default:
throw SelfCheck.Failure($"Internal error: unknown route resolution result {result}.");
}
}
/// <summary>
/// Associates a HTTP method and a route to a handler.
/// </summary>
/// <param name="verb">A <see cref="HttpVerbs"/> constant representing the HTTP method
/// to associate with <paramref name="handler"/>, or <see cref="HttpVerbs.Any"/>
/// if <paramref name="handler"/> can handle all HTTP methods.</param>
/// <param name="matcher">The <see cref="RouteMatcher"/> used to match URL paths.</param>
/// <param name="handler">A callback used to handle matching contexts.</param>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="matcher"/> is <see langword="null"/>.</para>
/// <para>- or -</para>
/// <para><paramref name="handler"/> is <see langword="null"/>.</para>
/// </exception>
protected void AddHandler(HttpVerbs verb, RouteMatcher matcher, RouteHandlerCallback handler)
=> _resolvers.Add(verb, matcher, handler);
/// <summary>
/// Associates a HTTP method and a route to a synchronous handler.
/// </summary>
/// <param name="verb">A <see cref="HttpVerbs"/> constant representing the HTTP method
/// to associate with <paramref name="handler"/>, or <see cref="HttpVerbs.Any"/>
/// if <paramref name="handler"/> can handle all HTTP methods.</param>
/// <param name="matcher">The <see cref="RouteMatcher"/> used to match URL paths.</param>
/// <param name="handler">A callback used to handle matching contexts.</param>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="matcher"/> is <see langword="null"/>.</para>
/// <para>- or -</para>
/// <para><paramref name="handler"/> is <see langword="null"/>.</para>
/// </exception>
protected void AddHandler(HttpVerbs verb, RouteMatcher matcher, SyncRouteHandlerCallback handler)
=> _resolvers.Add(verb, matcher, handler);
/// <summary>
/// <para>Adds handlers, associating them with HTTP method / route pairs by means
/// of <see cref="RouteAttribute">Route</see> attributes.</para>
/// <para>See <see cref="RouteVerbResolverCollection.AddFrom(object)"/> for further information.</para>
/// </summary>
/// <param name="target">Where to look for compatible handlers.</param>
/// <returns>The number of handlers that were added.</returns>
/// <exception cref="ArgumentNullException"><paramref name="target"/> is <see langword="null"/>.</exception>
protected int AddHandlersFrom(object target)
=> _resolvers.AddFrom(target);
/// <summary>
/// Associates all requests matching a route to a handler.
/// </summary>
/// <param name="matcher">The <see cref="RouteMatcher"/> used to match URL paths.</param>
/// <param name="handler">A callback used to handle matching contexts.</param>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="matcher"/> is <see langword="null"/>.</para>
/// <para>- or -</para>
/// <para><paramref name="handler"/> is <see langword="null"/>.</para>
/// </exception>
protected void OnAny(RouteMatcher matcher, RouteHandlerCallback handler)
=> _resolvers.Add(HttpVerbs.Any, matcher, handler);
/// <summary>
/// Associates all requests matching a route to a synchronous handler.
/// </summary>
/// <param name="matcher">The <see cref="RouteMatcher"/> used to match URL paths.</param>
/// <param name="handler">A callback used to handle matching contexts.</param>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="matcher"/> is <see langword="null"/>.</para>
/// <para>- or -</para>
/// <para><paramref name="handler"/> is <see langword="null"/>.</para>
/// </exception>
protected void OnAny(RouteMatcher matcher, SyncRouteHandlerCallback handler)
=> _resolvers.Add(HttpVerbs.Any, matcher, handler);
/// <summary>
/// Associates <c>DELETE</c> requests matching a route to a handler.
/// </summary>
/// <param name="matcher">The <see cref="RouteMatcher"/> used to match URL paths.</param>
/// <param name="handler">A callback used to handle matching contexts.</param>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="matcher"/> is <see langword="null"/>.</para>
/// <para>- or -</para>
/// <para><paramref name="handler"/> is <see langword="null"/>.</para>
/// </exception>
protected void OnDelete(RouteMatcher matcher, RouteHandlerCallback handler)
=> _resolvers.Add(HttpVerbs.Delete, matcher, handler);
/// <summary>
/// Associates <c>DELETE</c> requests matching a route to a synchronous handler.
/// </summary>
/// <param name="matcher">The <see cref="RouteMatcher"/> used to match URL paths.</param>
/// <param name="handler">A callback used to handle matching contexts.</param>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="matcher"/> is <see langword="null"/>.</para>
/// <para>- or -</para>
/// <para><paramref name="handler"/> is <see langword="null"/>.</para>
/// </exception>
protected void OnDelete(RouteMatcher matcher, SyncRouteHandlerCallback handler)
=> _resolvers.Add(HttpVerbs.Delete, matcher, handler);
/// <summary>
/// Associates <c>GET</c> requests matching a route to a handler.
/// </summary>
/// <param name="matcher">The <see cref="RouteMatcher"/> used to match URL paths.</param>
/// <param name="handler">A callback used to handle matching contexts.</param>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="matcher"/> is <see langword="null"/>.</para>
/// <para>- or -</para>
/// <para><paramref name="handler"/> is <see langword="null"/>.</para>
/// </exception>
protected void OnGet(RouteMatcher matcher, RouteHandlerCallback handler)
=> _resolvers.Add(HttpVerbs.Get, matcher, handler);
/// <summary>
/// Associates <c>GET</c> requests matching a route to a synchronous handler.
/// </summary>
/// <param name="matcher">The <see cref="RouteMatcher"/> used to match URL paths.</param>
/// <param name="handler">A callback used to handle matching contexts.</param>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="matcher"/> is <see langword="null"/>.</para>
/// <para>- or -</para>
/// <para><paramref name="handler"/> is <see langword="null"/>.</para>
/// </exception>
protected void OnGet(RouteMatcher matcher, SyncRouteHandlerCallback handler)
=> _resolvers.Add(HttpVerbs.Get, matcher, handler);
/// <summary>
/// Associates <c>HEAD</c> requests matching a route to a handler.
/// </summary>
/// <param name="matcher">The <see cref="RouteMatcher"/> used to match URL paths.</param>
/// <param name="handler">A callback used to handle matching contexts.</param>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="matcher"/> is <see langword="null"/>.</para>
/// <para>- or -</para>
/// <para><paramref name="handler"/> is <see langword="null"/>.</para>
/// </exception>
protected void OnHead(RouteMatcher matcher, RouteHandlerCallback handler)
=> _resolvers.Add(HttpVerbs.Head, matcher, handler);
/// <summary>
/// Associates <c>HEAD</c> requests matching a route to a synchronous handler.
/// </summary>
/// <param name="matcher">The <see cref="RouteMatcher"/> used to match URL paths.</param>
/// <param name="handler">A callback used to handle matching contexts.</param>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="matcher"/> is <see langword="null"/>.</para>
/// <para>- or -</para>
/// <para><paramref name="handler"/> is <see langword="null"/>.</para>
/// </exception>
protected void OnHead(RouteMatcher matcher, SyncRouteHandlerCallback handler)
=> _resolvers.Add(HttpVerbs.Head, matcher, handler);
/// <summary>
/// Associates <c>OPTIONS</c> requests matching a route to a handler.
/// </summary>
/// <param name="matcher">The <see cref="RouteMatcher"/> used to match URL paths.</param>
/// <param name="handler">A callback used to handle matching contexts.</param>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="matcher"/> is <see langword="null"/>.</para>
/// <para>- or -</para>
/// <para><paramref name="handler"/> is <see langword="null"/>.</para>
/// </exception>
protected void OnOptions(RouteMatcher matcher, RouteHandlerCallback handler)
=> _resolvers.Add(HttpVerbs.Options, matcher, handler);
/// <summary>
/// Associates <c>OPTIONS</c> requests matching a route to a synchronous handler.
/// </summary>
/// <param name="matcher">The <see cref="RouteMatcher"/> used to match URL paths.</param>
/// <param name="handler">A callback used to handle matching contexts.</param>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="matcher"/> is <see langword="null"/>.</para>
/// <para>- or -</para>
/// <para><paramref name="handler"/> is <see langword="null"/>.</para>
/// </exception>
protected void OnOptions(RouteMatcher matcher, SyncRouteHandlerCallback handler)
=> _resolvers.Add(HttpVerbs.Options, matcher, handler);
/// <summary>
/// Associates <c>PATCH</c> requests matching a route to a handler.
/// </summary>
/// <param name="matcher">The <see cref="RouteMatcher"/> used to match URL paths.</param>
/// <param name="handler">A callback used to handle matching contexts.</param>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="matcher"/> is <see langword="null"/>.</para>
/// <para>- or -</para>
/// <para><paramref name="handler"/> is <see langword="null"/>.</para>
/// </exception>
protected void OnPatch(RouteMatcher matcher, RouteHandlerCallback handler)
=> _resolvers.Add(HttpVerbs.Patch, matcher, handler);
/// <summary>
/// Associates <c>PATCH</c> requests matching a route to a synchronous handler.
/// </summary>
/// <param name="matcher">The <see cref="RouteMatcher"/> used to match URL paths.</param>
/// <param name="handler">A callback used to handle matching contexts.</param>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="matcher"/> is <see langword="null"/>.</para>
/// <para>- or -</para>
/// <para><paramref name="handler"/> is <see langword="null"/>.</para>
/// </exception>
protected void OnPatch(RouteMatcher matcher, SyncRouteHandlerCallback handler)
=> _resolvers.Add(HttpVerbs.Patch, matcher, handler);
/// <summary>
/// Associates <c>POST</c> requests matching a route to a handler.
/// </summary>
/// <param name="matcher">The <see cref="RouteMatcher"/> used to match URL paths.</param>
/// <param name="handler">A callback used to handle matching contexts.</param>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="matcher"/> is <see langword="null"/>.</para>
/// <para>- or -</para>
/// <para><paramref name="handler"/> is <see langword="null"/>.</para>
/// </exception>
protected void OnPost(RouteMatcher matcher, RouteHandlerCallback handler)
=> _resolvers.Add(HttpVerbs.Post, matcher, handler);
/// <summary>
/// Associates <c>POST</c> requests matching a route to a synchronous handler.
/// </summary>
/// <param name="matcher">The <see cref="RouteMatcher"/> used to match URL paths.</param>
/// <param name="handler">A callback used to handle matching contexts.</param>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="matcher"/> is <see langword="null"/>.</para>
/// <para>- or -</para>
/// <para><paramref name="handler"/> is <see langword="null"/>.</para>
/// </exception>
protected void OnPost(RouteMatcher matcher, SyncRouteHandlerCallback handler)
=> _resolvers.Add(HttpVerbs.Post, matcher, handler);
/// <summary>
/// Associates <c>PUT</c> requests matching a route to a handler.
/// </summary>
/// <param name="matcher">The <see cref="RouteMatcher"/> used to match URL paths.</param>
/// <param name="handler">A callback used to handle matching contexts.</param>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="matcher"/> is <see langword="null"/>.</para>
/// <para>- or -</para>
/// <para><paramref name="handler"/> is <see langword="null"/>.</para>
/// </exception>
protected void OnPut(RouteMatcher matcher, RouteHandlerCallback handler)
=> _resolvers.Add(HttpVerbs.Put, matcher, handler);
/// <summary>
/// Associates <c>PUT</c> requests matching a route to a synchronous handler.
/// </summary>
/// <param name="matcher">The <see cref="RouteMatcher"/> used to match URL paths.</param>
/// <param name="handler">A callback used to handle matching contexts.</param>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="matcher"/> is <see langword="null"/>.</para>
/// <para>- or -</para>
/// <para><paramref name="handler"/> is <see langword="null"/>.</para>
/// </exception>
protected void OnPut(RouteMatcher matcher, SyncRouteHandlerCallback handler)
=> _resolvers.Add(HttpVerbs.Put, matcher, handler);
/// <summary>
/// <para>Called when no route is matched for the requested URL path.</para>
/// <para>The default behavior is to send an empty <c>404 Not Found</c> response.</para>
/// </summary>
/// <param name="context">The context of the request being handled.</param>
/// <returns>A <see cref="Task"/> representing the ongoing operation.</returns>
protected virtual Task OnPathNotFoundAsync(IHttpContext context)
=> throw HttpException.NotFound();
/// <summary>
/// <para>Called when at least one route is matched for the requested URL path,
/// but none of them is associated with the HTTP method of the request.</para>
/// <para>The default behavior is to send an empty <c>405 Method Not Allowed</c> response.</para>
/// </summary>
/// <param name="context">The context of the request being handled.</param>
/// <returns>A <see cref="Task"/> representing the ongoing operation.</returns>
protected virtual Task OnMethodNotAllowedAsync(IHttpContext context)
=> throw HttpException.MethodNotAllowed();
}
}

View File

@@ -0,0 +1,429 @@
using System;
namespace EmbedIO.Routing
{
partial class RoutingModuleExtensions
{
/// <summary>
/// Adds a handler to a <see cref="RoutingModule"/>.
/// </summary>
/// <param name="this">The <see cref="RoutingModule"/> on which this method is called.</param>
/// <param name="verb">A <see cref="HttpVerbs"/> constant representing the HTTP method
/// to associate with <paramref name="handler"/>, or <see cref="HttpVerbs.Any"/>
/// if <paramref name="handler"/> can handle all HTTP methods.</param>
/// <param name="route">The route to match URL paths against.</param>
/// <param name="isBaseRoute"><see langword="true"/> if <paramref name="route"/>
/// is a base route; <see langword="false"/> if <paramref name="route"/>
/// is a terminal (non-base) route.</param>
/// <param name="handler">A callback used to handle matching contexts.</param>
/// <returns><paramref name="this"/> with the handler added.</returns>
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="route"/> is <see langword="null"/>.</para>
/// <para>- or -</para>
/// <para><paramref name="handler"/> is <see langword="null"/>.</para>
/// </exception>
/// <exception cref="FormatException"><paramref name="route"/> is not a valid route.</exception>
/// <seealso cref="RoutingModule.Add(HttpVerbs,RouteMatcher,RouteHandlerCallback)"/>
public static RoutingModule Handle(this RoutingModule @this, HttpVerbs verb, string route, bool isBaseRoute, RouteHandlerCallback handler)
{
@this.Add(verb, RouteMatcher.Parse(route, isBaseRoute), handler);
return @this;
}
/// <summary>
/// Adds a synchronous handler to a <see cref="RoutingModule"/>.
/// </summary>
/// <param name="this">The <see cref="RoutingModule"/> on which this method is called.</param>
/// <param name="verb">A <see cref="HttpVerbs"/> constant representing the HTTP method
/// to associate with <paramref name="handler"/>, or <see cref="HttpVerbs.Any"/>
/// if <paramref name="handler"/> can handle all HTTP methods.</param>
/// <param name="route">The route to match URL paths against.</param>
/// <param name="isBaseRoute"><see langword="true"/> if <paramref name="route"/>
/// is a base route; <see langword="false"/> if <paramref name="route"/>
/// is a terminal (non-base) route.</param>
/// <param name="handler">A callback used to handle matching contexts.</param>
/// <returns><paramref name="this"/> with the handler added.</returns>
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="route"/> is <see langword="null"/>.</para>
/// <para>- or -</para>
/// <para><paramref name="handler"/> is <see langword="null"/>.</para>
/// </exception>
/// <exception cref="FormatException"><paramref name="route"/> is not a valid route.</exception>
/// <seealso cref="RoutingModule.Add(HttpVerbs,RouteMatcher,RouteHandlerCallback)"/>
public static RoutingModule Handle(this RoutingModule @this, HttpVerbs verb, string route, bool isBaseRoute, SyncRouteHandlerCallback handler)
{
@this.Add(verb, RouteMatcher.Parse(route, isBaseRoute), handler);
return @this;
}
/// <summary>
/// Associates all requests matching a route to a handler.
/// </summary>
/// <param name="this">The <see cref="RoutingModule"/> on which this method is called.</param>
/// <param name="route">The route to match URL paths against.</param>
/// <param name="isBaseRoute"><see langword="true"/> if <paramref name="route"/>
/// is a base route; <see langword="false"/> if <paramref name="route"/>
/// is a terminal (non-base) route.</param>
/// <param name="handler">A callback used to handle matching contexts.</param>
/// <returns><paramref name="this"/> with the handler added.</returns>
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="route"/> is <see langword="null"/>.</para>
/// <para>- or -</para>
/// <para><paramref name="handler"/> is <see langword="null"/>.</para>
/// </exception>
/// <exception cref="FormatException"><paramref name="route"/> is not a valid route.</exception>
public static RoutingModule OnAny(this RoutingModule @this, string route, bool isBaseRoute, RouteHandlerCallback handler)
{
@this.Add(HttpVerbs.Any, RouteMatcher.Parse(route, isBaseRoute), handler);
return @this;
}
/// <summary>
/// Associates all requests matching a route to a synchronous handler.
/// </summary>
/// <param name="this">The <see cref="RoutingModule"/> on which this method is called.</param>
/// <param name="route">The route to match URL paths against.</param>
/// <param name="isBaseRoute"><see langword="true"/> if <paramref name="route"/>
/// is a base route; <see langword="false"/> if <paramref name="route"/>
/// is a terminal (non-base) route.</param>
/// <param name="handler">A callback used to handle matching contexts.</param>
/// <returns><paramref name="this"/> with the handler added.</returns>
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="route"/> is <see langword="null"/>.</para>
/// <para>- or -</para>
/// <para><paramref name="handler"/> is <see langword="null"/>.</para>
/// </exception>
/// <exception cref="FormatException"><paramref name="route"/> is not a valid route.</exception>
public static RoutingModule OnAny(this RoutingModule @this, string route, bool isBaseRoute, SyncRouteHandlerCallback handler)
{
@this.Add(HttpVerbs.Any, RouteMatcher.Parse(route, isBaseRoute), handler);
return @this;
}
/// <summary>
/// Associates <c>DELETE</c> requests matching a route to a handler.
/// </summary>
/// <param name="this">The <see cref="RoutingModule"/> on which this method is called.</param>
/// <param name="route">The route to match URL paths against.</param>
/// <param name="isBaseRoute"><see langword="true"/> if <paramref name="route"/>
/// is a base route; <see langword="false"/> if <paramref name="route"/>
/// is a terminal (non-base) route.</param>
/// <param name="handler">A callback used to handle matching contexts.</param>
/// <returns><paramref name="this"/> with the handler added.</returns>
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="route"/> is <see langword="null"/>.</para>
/// <para>- or -</para>
/// <para><paramref name="handler"/> is <see langword="null"/>.</para>
/// </exception>
/// <exception cref="FormatException"><paramref name="route"/> is not a valid route.</exception>
public static RoutingModule OnDelete(this RoutingModule @this, string route, bool isBaseRoute, RouteHandlerCallback handler)
{
@this.Add(HttpVerbs.Delete, RouteMatcher.Parse(route, isBaseRoute), handler);
return @this;
}
/// <summary>
/// Associates <c>DELETE</c> requests matching a route to a synchronous handler.
/// </summary>
/// <param name="this">The <see cref="RoutingModule"/> on which this method is called.</param>
/// <param name="route">The route to match URL paths against.</param>
/// <param name="isBaseRoute"><see langword="true"/> if <paramref name="route"/>
/// is a base route; <see langword="false"/> if <paramref name="route"/>
/// is a terminal (non-base) route.</param>
/// <param name="handler">A callback used to handle matching contexts.</param>
/// <returns><paramref name="this"/> with the handler added.</returns>
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="route"/> is <see langword="null"/>.</para>
/// <para>- or -</para>
/// <para><paramref name="handler"/> is <see langword="null"/>.</para>
/// </exception>
/// <exception cref="FormatException"><paramref name="route"/> is not a valid route.</exception>
public static RoutingModule OnDelete(this RoutingModule @this, string route, bool isBaseRoute, SyncRouteHandlerCallback handler)
{
@this.Add(HttpVerbs.Delete, RouteMatcher.Parse(route, isBaseRoute), handler);
return @this;
}
/// <summary>
/// Associates <c>GET</c> requests matching a route to a handler.
/// </summary>
/// <param name="this">The <see cref="RoutingModule"/> on which this method is called.</param>
/// <param name="route">The route to match URL paths against.</param>
/// <param name="isBaseRoute"><see langword="true"/> if <paramref name="route"/>
/// is a base route; <see langword="false"/> if <paramref name="route"/>
/// is a terminal (non-base) route.</param>
/// <param name="handler">A callback used to handle matching contexts.</param>
/// <returns><paramref name="this"/> with the handler added.</returns>
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="route"/> is <see langword="null"/>.</para>
/// <para>- or -</para>
/// <para><paramref name="handler"/> is <see langword="null"/>.</para>
/// </exception>
/// <exception cref="FormatException"><paramref name="route"/> is not a valid route.</exception>
public static RoutingModule OnGet(this RoutingModule @this, string route, bool isBaseRoute, RouteHandlerCallback handler)
{
@this.Add(HttpVerbs.Get, RouteMatcher.Parse(route, isBaseRoute), handler);
return @this;
}
/// <summary>
/// Associates <c>GET</c> requests matching a route to a synchronous handler.
/// </summary>
/// <param name="this">The <see cref="RoutingModule"/> on which this method is called.</param>
/// <param name="route">The route to match URL paths against.</param>
/// <param name="isBaseRoute"><see langword="true"/> if <paramref name="route"/>
/// is a base route; <see langword="false"/> if <paramref name="route"/>
/// is a terminal (non-base) route.</param>
/// <param name="handler">A callback used to handle matching contexts.</param>
/// <returns><paramref name="this"/> with the handler added.</returns>
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="route"/> is <see langword="null"/>.</para>
/// <para>- or -</para>
/// <para><paramref name="handler"/> is <see langword="null"/>.</para>
/// </exception>
/// <exception cref="FormatException"><paramref name="route"/> is not a valid route.</exception>
public static RoutingModule OnGet(this RoutingModule @this, string route, bool isBaseRoute, SyncRouteHandlerCallback handler)
{
@this.Add(HttpVerbs.Get, RouteMatcher.Parse(route, isBaseRoute), handler);
return @this;
}
/// <summary>
/// Associates <c>HEAD</c> requests matching a route to a handler.
/// </summary>
/// <param name="this">The <see cref="RoutingModule"/> on which this method is called.</param>
/// <param name="route">The route to match URL paths against.</param>
/// <param name="isBaseRoute"><see langword="true"/> if <paramref name="route"/>
/// is a base route; <see langword="false"/> if <paramref name="route"/>
/// is a terminal (non-base) route.</param>
/// <param name="handler">A callback used to handle matching contexts.</param>
/// <returns><paramref name="this"/> with the handler added.</returns>
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="route"/> is <see langword="null"/>.</para>
/// <para>- or -</para>
/// <para><paramref name="handler"/> is <see langword="null"/>.</para>
/// </exception>
/// <exception cref="FormatException"><paramref name="route"/> is not a valid route.</exception>
public static RoutingModule OnHead(this RoutingModule @this, string route, bool isBaseRoute, RouteHandlerCallback handler)
{
@this.Add(HttpVerbs.Head, RouteMatcher.Parse(route, isBaseRoute), handler);
return @this;
}
/// <summary>
/// Associates <c>HEAD</c> requests matching a route to a synchronous handler.
/// </summary>
/// <param name="this">The <see cref="RoutingModule"/> on which this method is called.</param>
/// <param name="route">The route to match URL paths against.</param>
/// <param name="isBaseRoute"><see langword="true"/> if <paramref name="route"/>
/// is a base route; <see langword="false"/> if <paramref name="route"/>
/// is a terminal (non-base) route.</param>
/// <param name="handler">A callback used to handle matching contexts.</param>
/// <returns><paramref name="this"/> with the handler added.</returns>
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="route"/> is <see langword="null"/>.</para>
/// <para>- or -</para>
/// <para><paramref name="handler"/> is <see langword="null"/>.</para>
/// </exception>
/// <exception cref="FormatException"><paramref name="route"/> is not a valid route.</exception>
public static RoutingModule OnHead(this RoutingModule @this, string route, bool isBaseRoute, SyncRouteHandlerCallback handler)
{
@this.Add(HttpVerbs.Head, RouteMatcher.Parse(route, isBaseRoute), handler);
return @this;
}
/// <summary>
/// Associates <c>OPTIONS</c> requests matching a route to a handler.
/// </summary>
/// <param name="this">The <see cref="RoutingModule"/> on which this method is called.</param>
/// <param name="route">The route to match URL paths against.</param>
/// <param name="isBaseRoute"><see langword="true"/> if <paramref name="route"/>
/// is a base route; <see langword="false"/> if <paramref name="route"/>
/// is a terminal (non-base) route.</param>
/// <param name="handler">A callback used to handle matching contexts.</param>
/// <returns><paramref name="this"/> with the handler added.</returns>
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="route"/> is <see langword="null"/>.</para>
/// <para>- or -</para>
/// <para><paramref name="handler"/> is <see langword="null"/>.</para>
/// </exception>
/// <exception cref="FormatException"><paramref name="route"/> is not a valid route.</exception>
public static RoutingModule OnOptions(this RoutingModule @this, string route, bool isBaseRoute, RouteHandlerCallback handler)
{
@this.Add(HttpVerbs.Options, RouteMatcher.Parse(route, isBaseRoute), handler);
return @this;
}
/// <summary>
/// Associates <c>OPTIONS</c> requests matching a route to a synchronous handler.
/// </summary>
/// <param name="this">The <see cref="RoutingModule"/> on which this method is called.</param>
/// <param name="route">The route to match URL paths against.</param>
/// <param name="isBaseRoute"><see langword="true"/> if <paramref name="route"/>
/// is a base route; <see langword="false"/> if <paramref name="route"/>
/// is a terminal (non-base) route.</param>
/// <param name="handler">A callback used to handle matching contexts.</param>
/// <returns><paramref name="this"/> with the handler added.</returns>
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="route"/> is <see langword="null"/>.</para>
/// <para>- or -</para>
/// <para><paramref name="handler"/> is <see langword="null"/>.</para>
/// </exception>
/// <exception cref="FormatException"><paramref name="route"/> is not a valid route.</exception>
public static RoutingModule OnOptions(this RoutingModule @this, string route, bool isBaseRoute, SyncRouteHandlerCallback handler)
{
@this.Add(HttpVerbs.Options, RouteMatcher.Parse(route, isBaseRoute), handler);
return @this;
}
/// <summary>
/// Associates <c>PATCH</c> requests matching a route to a handler.
/// </summary>
/// <param name="this">The <see cref="RoutingModule"/> on which this method is called.</param>
/// <param name="route">The route to match URL paths against.</param>
/// <param name="isBaseRoute"><see langword="true"/> if <paramref name="route"/>
/// is a base route; <see langword="false"/> if <paramref name="route"/>
/// is a terminal (non-base) route.</param>
/// <param name="handler">A callback used to handle matching contexts.</param>
/// <returns><paramref name="this"/> with the handler added.</returns>
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="route"/> is <see langword="null"/>.</para>
/// <para>- or -</para>
/// <para><paramref name="handler"/> is <see langword="null"/>.</para>
/// </exception>
/// <exception cref="FormatException"><paramref name="route"/> is not a valid route.</exception>
public static RoutingModule OnPatch(this RoutingModule @this, string route, bool isBaseRoute, RouteHandlerCallback handler)
{
@this.Add(HttpVerbs.Patch, RouteMatcher.Parse(route, isBaseRoute), handler);
return @this;
}
/// <summary>
/// Associates <c>PATCH</c> requests matching a route to a synchronous handler.
/// </summary>
/// <param name="this">The <see cref="RoutingModule"/> on which this method is called.</param>
/// <param name="route">The route to match URL paths against.</param>
/// <param name="isBaseRoute"><see langword="true"/> if <paramref name="route"/>
/// is a base route; <see langword="false"/> if <paramref name="route"/>
/// is a terminal (non-base) route.</param>
/// <param name="handler">A callback used to handle matching contexts.</param>
/// <returns><paramref name="this"/> with the handler added.</returns>
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="route"/> is <see langword="null"/>.</para>
/// <para>- or -</para>
/// <para><paramref name="handler"/> is <see langword="null"/>.</para>
/// </exception>
/// <exception cref="FormatException"><paramref name="route"/> is not a valid route.</exception>
public static RoutingModule OnPatch(this RoutingModule @this, string route, bool isBaseRoute, SyncRouteHandlerCallback handler)
{
@this.Add(HttpVerbs.Patch, RouteMatcher.Parse(route, isBaseRoute), handler);
return @this;
}
/// <summary>
/// Associates <c>POST</c> requests matching a route to a handler.
/// </summary>
/// <param name="this">The <see cref="RoutingModule"/> on which this method is called.</param>
/// <param name="route">The route to match URL paths against.</param>
/// <param name="isBaseRoute"><see langword="true"/> if <paramref name="route"/>
/// is a base route; <see langword="false"/> if <paramref name="route"/>
/// is a terminal (non-base) route.</param>
/// <param name="handler">A callback used to handle matching contexts.</param>
/// <returns><paramref name="this"/> with the handler added.</returns>
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="route"/> is <see langword="null"/>.</para>
/// <para>- or -</para>
/// <para><paramref name="handler"/> is <see langword="null"/>.</para>
/// </exception>
/// <exception cref="FormatException"><paramref name="route"/> is not a valid route.</exception>
public static RoutingModule OnPost(this RoutingModule @this, string route, bool isBaseRoute, RouteHandlerCallback handler)
{
@this.Add(HttpVerbs.Post, RouteMatcher.Parse(route, isBaseRoute), handler);
return @this;
}
/// <summary>
/// Associates <c>POST</c> requests matching a route to a synchronous handler.
/// </summary>
/// <param name="this">The <see cref="RoutingModule"/> on which this method is called.</param>
/// <param name="route">The route to match URL paths against.</param>
/// <param name="isBaseRoute"><see langword="true"/> if <paramref name="route"/>
/// is a base route; <see langword="false"/> if <paramref name="route"/>
/// is a terminal (non-base) route.</param>
/// <param name="handler">A callback used to handle matching contexts.</param>
/// <returns><paramref name="this"/> with the handler added.</returns>
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="route"/> is <see langword="null"/>.</para>
/// <para>- or -</para>
/// <para><paramref name="handler"/> is <see langword="null"/>.</para>
/// </exception>
/// <exception cref="FormatException"><paramref name="route"/> is not a valid route.</exception>
public static RoutingModule OnPost(this RoutingModule @this, string route, bool isBaseRoute, SyncRouteHandlerCallback handler)
{
@this.Add(HttpVerbs.Post, RouteMatcher.Parse(route, isBaseRoute), handler);
return @this;
}
/// <summary>
/// Associates <c>PUT</c> requests matching a route to a handler.
/// </summary>
/// <param name="this">The <see cref="RoutingModule"/> on which this method is called.</param>
/// <param name="route">The route to match URL paths against.</param>
/// <param name="isBaseRoute"><see langword="true"/> if <paramref name="route"/>
/// is a base route; <see langword="false"/> if <paramref name="route"/>
/// is a terminal (non-base) route.</param>
/// <param name="handler">A callback used to handle matching contexts.</param>
/// <returns><paramref name="this"/> with the handler added.</returns>
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="route"/> is <see langword="null"/>.</para>
/// <para>- or -</para>
/// <para><paramref name="handler"/> is <see langword="null"/>.</para>
/// </exception>
/// <exception cref="FormatException"><paramref name="route"/> is not a valid route.</exception>
public static RoutingModule OnPut(this RoutingModule @this, string route, bool isBaseRoute, RouteHandlerCallback handler)
{
@this.Add(HttpVerbs.Put, RouteMatcher.Parse(route, isBaseRoute), handler);
return @this;
}
/// <summary>
/// Associates <c>PUT</c> requests matching a route to a synchronous handler.
/// </summary>
/// <param name="this">The <see cref="RoutingModule"/> on which this method is called.</param>
/// <param name="route">The route to match URL paths against.</param>
/// <param name="isBaseRoute"><see langword="true"/> if <paramref name="route"/>
/// is a base route; <see langword="false"/> if <paramref name="route"/>
/// is a terminal (non-base) route.</param>
/// <param name="handler">A callback used to handle matching contexts.</param>
/// <returns><paramref name="this"/> with the handler added.</returns>
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="route"/> is <see langword="null"/>.</para>
/// <para>- or -</para>
/// <para><paramref name="handler"/> is <see langword="null"/>.</para>
/// </exception>
/// <exception cref="FormatException"><paramref name="route"/> is not a valid route.</exception>
public static RoutingModule OnPut(this RoutingModule @this, string route, bool isBaseRoute, SyncRouteHandlerCallback handler)
{
@this.Add(HttpVerbs.Put, RouteMatcher.Parse(route, isBaseRoute), handler);
return @this;
}
}
}

View File

@@ -0,0 +1,357 @@
using System;
namespace EmbedIO.Routing
{
partial class RoutingModuleExtensions
{
/// <summary>
/// Adds a handler to a <see cref="RoutingModule"/>.
/// </summary>
/// <param name="this">The <see cref="RoutingModule"/> on which this method is called.</param>
/// <param name="verb">A <see cref="HttpVerbs"/> constant representing the HTTP method
/// to associate with <paramref name="handler"/>, or <see cref="HttpVerbs.Any"/>
/// if <paramref name="handler"/> can handle all HTTP methods.</param>
/// <param name="matcher">The <see cref="RouteMatcher"/> used to match URL paths.</param>
/// <param name="handler">A callback used to handle matching contexts.</param>
/// <returns><paramref name="this"/> with the handler added.</returns>
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="matcher"/> is <see langword="null"/>.</para>
/// <para>- or -</para>
/// <para><paramref name="handler"/> is <see langword="null"/>.</para>
/// </exception>
/// <seealso cref="RoutingModule.Add(HttpVerbs,RouteMatcher,RouteHandlerCallback)"/>
public static RoutingModule Handle(this RoutingModule @this, HttpVerbs verb, RouteMatcher matcher, RouteHandlerCallback handler)
{
@this.Add(verb, matcher, handler);
return @this;
}
/// <summary>
/// Adds a synchronous handler to a <see cref="RoutingModule"/>.
/// </summary>
/// <param name="this">The <see cref="RoutingModule"/> on which this method is called.</param>
/// <param name="verb">A <see cref="HttpVerbs"/> constant representing the HTTP method
/// to associate with <paramref name="handler"/>, or <see cref="HttpVerbs.Any"/>
/// if <paramref name="handler"/> can handle all HTTP methods.</param>
/// <param name="matcher">The <see cref="RouteMatcher"/> used to match URL paths.</param>
/// <param name="handler">A callback used to handle matching contexts.</param>
/// <returns><paramref name="this"/> with the handler added.</returns>
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="matcher"/> is <see langword="null"/>.</para>
/// <para>- or -</para>
/// <para><paramref name="handler"/> is <see langword="null"/>.</para>
/// </exception>
/// <seealso cref="RoutingModule.Add(HttpVerbs,RouteMatcher,SyncRouteHandlerCallback)"/>
public static RoutingModule Handle(this RoutingModule @this, HttpVerbs verb, RouteMatcher matcher, SyncRouteHandlerCallback handler)
{
@this.Add(verb, matcher, handler);
return @this;
}
/// <summary>
/// Associates all requests matching a route to a handler.
/// </summary>
/// <param name="this">The <see cref="RoutingModule"/> on which this method is called.</param>
/// <param name="matcher">The <see cref="RouteMatcher"/> used to match URL paths.</param>
/// <param name="handler">A callback used to handle matching contexts.</param>
/// <returns><paramref name="this"/> with the handler added.</returns>
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="matcher"/> is <see langword="null"/>.</para>
/// <para>- or -</para>
/// <para><paramref name="handler"/> is <see langword="null"/>.</para>
/// </exception>
public static RoutingModule OnAny(this RoutingModule @this, RouteMatcher matcher, RouteHandlerCallback handler)
{
@this.Add(HttpVerbs.Any, matcher, handler);
return @this;
}
/// <summary>
/// Associates all requests matching a route to a synchronous handler.
/// </summary>
/// <param name="this">The <see cref="RoutingModule"/> on which this method is called.</param>
/// <param name="matcher">The <see cref="RouteMatcher"/> used to match URL paths.</param>
/// <param name="handler">A callback used to handle matching contexts.</param>
/// <returns><paramref name="this"/> with the handler added.</returns>
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="matcher"/> is <see langword="null"/>.</para>
/// <para>- or -</para>
/// <para><paramref name="handler"/> is <see langword="null"/>.</para>
/// </exception>
public static RoutingModule OnAny(this RoutingModule @this, RouteMatcher matcher, SyncRouteHandlerCallback handler)
{
@this.Add(HttpVerbs.Any, matcher, handler);
return @this;
}
/// <summary>
/// Associates <c>DELETE</c> requests matching a route to a handler.
/// </summary>
/// <param name="this">The <see cref="RoutingModule"/> on which this method is called.</param>
/// <param name="matcher">The <see cref="RouteMatcher"/> used to match URL paths.</param>
/// <param name="handler">A callback used to handle matching contexts.</param>
/// <returns><paramref name="this"/> with the handler added.</returns>
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="matcher"/> is <see langword="null"/>.</para>
/// <para>- or -</para>
/// <para><paramref name="handler"/> is <see langword="null"/>.</para>
/// </exception>
public static RoutingModule OnDelete(this RoutingModule @this, RouteMatcher matcher, RouteHandlerCallback handler)
{
@this.Add(HttpVerbs.Delete, matcher, handler);
return @this;
}
/// <summary>
/// Associates <c>DELETE</c> requests matching a route to a synchronous handler.
/// </summary>
/// <param name="this">The <see cref="RoutingModule"/> on which this method is called.</param>
/// <param name="matcher">The <see cref="RouteMatcher"/> used to match URL paths.</param>
/// <param name="handler">A callback used to handle matching contexts.</param>
/// <returns><paramref name="this"/> with the handler added.</returns>
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="matcher"/> is <see langword="null"/>.</para>
/// <para>- or -</para>
/// <para><paramref name="handler"/> is <see langword="null"/>.</para>
/// </exception>
public static RoutingModule OnDelete(this RoutingModule @this, RouteMatcher matcher, SyncRouteHandlerCallback handler)
{
@this.Add(HttpVerbs.Delete, matcher, handler);
return @this;
}
/// <summary>
/// Associates <c>GET</c> requests matching a route to a handler.
/// </summary>
/// <param name="this">The <see cref="RoutingModule"/> on which this method is called.</param>
/// <param name="matcher">The <see cref="RouteMatcher"/> used to match URL paths.</param>
/// <param name="handler">A callback used to handle matching contexts.</param>
/// <returns><paramref name="this"/> with the handler added.</returns>
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="matcher"/> is <see langword="null"/>.</para>
/// <para>- or -</para>
/// <para><paramref name="handler"/> is <see langword="null"/>.</para>
/// </exception>
public static RoutingModule OnGet(this RoutingModule @this, RouteMatcher matcher, RouteHandlerCallback handler)
{
@this.Add(HttpVerbs.Get, matcher, handler);
return @this;
}
/// <summary>
/// Associates <c>GET</c> requests matching a route to a synchronous handler.
/// </summary>
/// <param name="this">The <see cref="RoutingModule"/> on which this method is called.</param>
/// <param name="matcher">The <see cref="RouteMatcher"/> used to match URL paths.</param>
/// <param name="handler">A callback used to handle matching contexts.</param>
/// <returns><paramref name="this"/> with the handler added.</returns>
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="matcher"/> is <see langword="null"/>.</para>
/// <para>- or -</para>
/// <para><paramref name="handler"/> is <see langword="null"/>.</para>
/// </exception>
public static RoutingModule OnGet(this RoutingModule @this, RouteMatcher matcher, SyncRouteHandlerCallback handler)
{
@this.Add(HttpVerbs.Get, matcher, handler);
return @this;
}
/// <summary>
/// Associates <c>HEAD</c> requests matching a route to a handler.
/// </summary>
/// <param name="this">The <see cref="RoutingModule"/> on which this method is called.</param>
/// <param name="matcher">The <see cref="RouteMatcher"/> used to match URL paths.</param>
/// <param name="handler">A callback used to handle matching contexts.</param>
/// <returns><paramref name="this"/> with the handler added.</returns>
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="matcher"/> is <see langword="null"/>.</para>
/// <para>- or -</para>
/// <para><paramref name="handler"/> is <see langword="null"/>.</para>
/// </exception>
public static RoutingModule OnHead(this RoutingModule @this, RouteMatcher matcher, RouteHandlerCallback handler)
{
@this.Add(HttpVerbs.Head, matcher, handler);
return @this;
}
/// <summary>
/// Associates <c>HEAD</c> requests matching a route to a synchronous handler.
/// </summary>
/// <param name="this">The <see cref="RoutingModule"/> on which this method is called.</param>
/// <param name="matcher">The <see cref="RouteMatcher"/> used to match URL paths.</param>
/// <param name="handler">A callback used to handle matching contexts.</param>
/// <returns><paramref name="this"/> with the handler added.</returns>
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="matcher"/> is <see langword="null"/>.</para>
/// <para>- or -</para>
/// <para><paramref name="handler"/> is <see langword="null"/>.</para>
/// </exception>
public static RoutingModule OnHead(this RoutingModule @this, RouteMatcher matcher, SyncRouteHandlerCallback handler)
{
@this.Add(HttpVerbs.Head, matcher, handler);
return @this;
}
/// <summary>
/// Associates <c>OPTIONS</c> requests matching a route to a handler.
/// </summary>
/// <param name="this">The <see cref="RoutingModule"/> on which this method is called.</param>
/// <param name="matcher">The <see cref="RouteMatcher"/> used to match URL paths.</param>
/// <param name="handler">A callback used to handle matching contexts.</param>
/// <returns><paramref name="this"/> with the handler added.</returns>
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="matcher"/> is <see langword="null"/>.</para>
/// <para>- or -</para>
/// <para><paramref name="handler"/> is <see langword="null"/>.</para>
/// </exception>
public static RoutingModule OnOptions(this RoutingModule @this, RouteMatcher matcher, RouteHandlerCallback handler)
{
@this.Add(HttpVerbs.Options, matcher, handler);
return @this;
}
/// <summary>
/// Associates <c>OPTIONS</c> requests matching a route to a synchronous handler.
/// </summary>
/// <param name="this">The <see cref="RoutingModule"/> on which this method is called.</param>
/// <param name="matcher">The <see cref="RouteMatcher"/> used to match URL paths.</param>
/// <param name="handler">A callback used to handle matching contexts.</param>
/// <returns><paramref name="this"/> with the handler added.</returns>
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="matcher"/> is <see langword="null"/>.</para>
/// <para>- or -</para>
/// <para><paramref name="handler"/> is <see langword="null"/>.</para>
/// </exception>
public static RoutingModule OnOptions(this RoutingModule @this, RouteMatcher matcher, SyncRouteHandlerCallback handler)
{
@this.Add(HttpVerbs.Options, matcher, handler);
return @this;
}
/// <summary>
/// Associates <c>PATCH</c> requests matching a route to a handler.
/// </summary>
/// <param name="this">The <see cref="RoutingModule"/> on which this method is called.</param>
/// <param name="matcher">The <see cref="RouteMatcher"/> used to match URL paths.</param>
/// <param name="handler">A callback used to handle matching contexts.</param>
/// <returns><paramref name="this"/> with the handler added.</returns>
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="matcher"/> is <see langword="null"/>.</para>
/// <para>- or -</para>
/// <para><paramref name="handler"/> is <see langword="null"/>.</para>
/// </exception>
public static RoutingModule OnPatch(this RoutingModule @this, RouteMatcher matcher, RouteHandlerCallback handler)
{
@this.Add(HttpVerbs.Patch, matcher, handler);
return @this;
}
/// <summary>
/// Associates <c>PATCH</c> requests matching a route to a synchronous handler.
/// </summary>
/// <param name="this">The <see cref="RoutingModule"/> on which this method is called.</param>
/// <param name="matcher">The <see cref="RouteMatcher"/> used to match URL paths.</param>
/// <param name="handler">A callback used to handle matching contexts.</param>
/// <returns><paramref name="this"/> with the handler added.</returns>
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="matcher"/> is <see langword="null"/>.</para>
/// <para>- or -</para>
/// <para><paramref name="handler"/> is <see langword="null"/>.</para>
/// </exception>
public static RoutingModule OnPatch(this RoutingModule @this, RouteMatcher matcher, SyncRouteHandlerCallback handler)
{
@this.Add(HttpVerbs.Patch, matcher, handler);
return @this;
}
/// <summary>
/// Associates <c>POST</c> requests matching a route to a handler.
/// </summary>
/// <param name="this">The <see cref="RoutingModule"/> on which this method is called.</param>
/// <param name="matcher">The <see cref="RouteMatcher"/> used to match URL paths.</param>
/// <param name="handler">A callback used to handle matching contexts.</param>
/// <returns><paramref name="this"/> with the handler added.</returns>
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="matcher"/> is <see langword="null"/>.</para>
/// <para>- or -</para>
/// <para><paramref name="handler"/> is <see langword="null"/>.</para>
/// </exception>
public static RoutingModule OnPost(this RoutingModule @this, RouteMatcher matcher, RouteHandlerCallback handler)
{
@this.Add(HttpVerbs.Post, matcher, handler);
return @this;
}
/// <summary>
/// Associates <c>POST</c> requests matching a route to a synchronous handler.
/// </summary>
/// <param name="this">The <see cref="RoutingModule"/> on which this method is called.</param>
/// <param name="matcher">The <see cref="RouteMatcher"/> used to match URL paths.</param>
/// <param name="handler">A callback used to handle matching contexts.</param>
/// <returns><paramref name="this"/> with the handler added.</returns>
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="matcher"/> is <see langword="null"/>.</para>
/// <para>- or -</para>
/// <para><paramref name="handler"/> is <see langword="null"/>.</para>
/// </exception>
public static RoutingModule OnPost(this RoutingModule @this, RouteMatcher matcher, SyncRouteHandlerCallback handler)
{
@this.Add(HttpVerbs.Post, matcher, handler);
return @this;
}
/// <summary>
/// Associates <c>PUT</c> requests matching a route to a handler.
/// </summary>
/// <param name="this">The <see cref="RoutingModule"/> on which this method is called.</param>
/// <param name="matcher">The <see cref="RouteMatcher"/> used to match URL paths.</param>
/// <param name="handler">A callback used to handle matching contexts.</param>
/// <returns><paramref name="this"/> with the handler added.</returns>
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="matcher"/> is <see langword="null"/>.</para>
/// <para>- or -</para>
/// <para><paramref name="handler"/> is <see langword="null"/>.</para>
/// </exception>
public static RoutingModule OnPut(this RoutingModule @this, RouteMatcher matcher, RouteHandlerCallback handler)
{
@this.Add(HttpVerbs.Put, matcher, handler);
return @this;
}
/// <summary>
/// Associates <c>PUT</c> requests matching a route to a synchronous handler.
/// </summary>
/// <param name="this">The <see cref="RoutingModule"/> on which this method is called.</param>
/// <param name="matcher">The <see cref="RouteMatcher"/> used to match URL paths.</param>
/// <param name="handler">A callback used to handle matching contexts.</param>
/// <returns><paramref name="this"/> with the handler added.</returns>
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="matcher"/> is <see langword="null"/>.</para>
/// <para>- or -</para>
/// <para><paramref name="handler"/> is <see langword="null"/>.</para>
/// </exception>
public static RoutingModule OnPut(this RoutingModule @this, RouteMatcher matcher, SyncRouteHandlerCallback handler)
{
@this.Add(HttpVerbs.Put, matcher, handler);
return @this;
}
}
}

View File

@@ -0,0 +1,375 @@
using System;
namespace EmbedIO.Routing
{
partial class RoutingModuleExtensions
{
/// <summary>
/// Adds a handler to a <see cref="RoutingModule"/>.
/// </summary>
/// <param name="this">The <see cref="RoutingModule"/> on which this method is called.</param>
/// <param name="verb">A <see cref="HttpVerbs"/> constant representing the HTTP method
/// to associate with <paramref name="handler"/>, or <see cref="HttpVerbs.Any"/>
/// if <paramref name="handler"/> can handle all HTTP methods.</param>
/// <param name="route">The route to match URL paths against.</param>
/// <param name="handler">A callback used to handle matching contexts.</param>
/// <returns><paramref name="this"/> with the handler added.</returns>
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="route"/> is <see langword="null"/>.</para>
/// <para>- or -</para>
/// <para><paramref name="handler"/> is <see langword="null"/>.</para>
/// </exception>
/// <exception cref="FormatException"><paramref name="route"/> is not a valid route.</exception>
/// <seealso cref="RoutingModule.Add(HttpVerbs,RouteMatcher,RouteHandlerCallback)"/>
public static RoutingModule Handle(this RoutingModule @this, HttpVerbs verb, string route, RouteHandlerCallback handler)
{
@this.Add(verb, RouteMatcher.Parse(route, false), handler);
return @this;
}
/// <summary>
/// Adds a synchronous handler to a <see cref="RoutingModule"/>.
/// </summary>
/// <param name="this">The <see cref="RoutingModule"/> on which this method is called.</param>
/// <param name="verb">A <see cref="HttpVerbs"/> constant representing the HTTP method
/// to associate with <paramref name="handler"/>, or <see cref="HttpVerbs.Any"/>
/// if <paramref name="handler"/> can handle all HTTP methods.</param>
/// <param name="route">The route to match URL paths against.</param>
/// <param name="handler">A callback used to handle matching contexts.</param>
/// <returns><paramref name="this"/> with the handler added.</returns>
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="route"/> is <see langword="null"/>.</para>
/// <para>- or -</para>
/// <para><paramref name="handler"/> is <see langword="null"/>.</para>
/// </exception>
/// <exception cref="FormatException"><paramref name="route"/> is not a valid route.</exception>
/// <seealso cref="RoutingModule.Add(HttpVerbs,RouteMatcher,RouteHandlerCallback)"/>
public static RoutingModule Handle(this RoutingModule @this, HttpVerbs verb, string route, SyncRouteHandlerCallback handler)
{
@this.Add(verb, RouteMatcher.Parse(route, false), handler);
return @this;
}
/// <summary>
/// Associates all requests matching a route to a handler.
/// </summary>
/// <param name="this">The <see cref="RoutingModule"/> on which this method is called.</param>
/// <param name="route">The route to match URL paths against.</param>
/// <param name="handler">A callback used to handle matching contexts.</param>
/// <returns><paramref name="this"/> with the handler added.</returns>
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="route"/> is <see langword="null"/>.</para>
/// <para>- or -</para>
/// <para><paramref name="handler"/> is <see langword="null"/>.</para>
/// </exception>
/// <exception cref="FormatException"><paramref name="route"/> is not a valid route.</exception>
public static RoutingModule OnAny(this RoutingModule @this, string route, RouteHandlerCallback handler)
{
@this.Add(HttpVerbs.Any, RouteMatcher.Parse(route, false), handler);
return @this;
}
/// <summary>
/// Associates all requests matching a route to a synchronous handler.
/// </summary>
/// <param name="this">The <see cref="RoutingModule"/> on which this method is called.</param>
/// <param name="route">The route to match URL paths against.</param>
/// <param name="handler">A callback used to handle matching contexts.</param>
/// <returns><paramref name="this"/> with the handler added.</returns>
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="route"/> is <see langword="null"/>.</para>
/// <para>- or -</para>
/// <para><paramref name="handler"/> is <see langword="null"/>.</para>
/// </exception>
/// <exception cref="FormatException"><paramref name="route"/> is not a valid route.</exception>
public static RoutingModule OnAny(this RoutingModule @this, string route, SyncRouteHandlerCallback handler)
{
@this.Add(HttpVerbs.Any, RouteMatcher.Parse(route, false), handler);
return @this;
}
/// <summary>
/// Associates <c>DELETE</c> requests matching a route to a handler.
/// </summary>
/// <param name="this">The <see cref="RoutingModule"/> on which this method is called.</param>
/// <param name="route">The route to match URL paths against.</param>
/// <param name="handler">A callback used to handle matching contexts.</param>
/// <returns><paramref name="this"/> with the handler added.</returns>
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="route"/> is <see langword="null"/>.</para>
/// <para>- or -</para>
/// <para><paramref name="handler"/> is <see langword="null"/>.</para>
/// </exception>
/// <exception cref="FormatException"><paramref name="route"/> is not a valid route.</exception>
public static RoutingModule OnDelete(this RoutingModule @this, string route, RouteHandlerCallback handler)
{
@this.Add(HttpVerbs.Delete, RouteMatcher.Parse(route, false), handler);
return @this;
}
/// <summary>
/// Associates <c>DELETE</c> requests matching a route to a synchronous handler.
/// </summary>
/// <param name="this">The <see cref="RoutingModule"/> on which this method is called.</param>
/// <param name="route">The route to match URL paths against.</param>
/// <param name="handler">A callback used to handle matching contexts.</param>
/// <returns><paramref name="this"/> with the handler added.</returns>
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="route"/> is <see langword="null"/>.</para>
/// <para>- or -</para>
/// <para><paramref name="handler"/> is <see langword="null"/>.</para>
/// </exception>
/// <exception cref="FormatException"><paramref name="route"/> is not a valid route.</exception>
public static RoutingModule OnDelete(this RoutingModule @this, string route, SyncRouteHandlerCallback handler)
{
@this.Add(HttpVerbs.Delete, RouteMatcher.Parse(route, false), handler);
return @this;
}
/// <summary>
/// Associates <c>GET</c> requests matching a route to a handler.
/// </summary>
/// <param name="this">The <see cref="RoutingModule"/> on which this method is called.</param>
/// <param name="route">The route to match URL paths against.</param>
/// <param name="handler">A callback used to handle matching contexts.</param>
/// <returns><paramref name="this"/> with the handler added.</returns>
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="route"/> is <see langword="null"/>.</para>
/// <para>- or -</para>
/// <para><paramref name="handler"/> is <see langword="null"/>.</para>
/// </exception>
/// <exception cref="FormatException"><paramref name="route"/> is not a valid route.</exception>
public static RoutingModule OnGet(this RoutingModule @this, string route, RouteHandlerCallback handler)
{
@this.Add(HttpVerbs.Get, RouteMatcher.Parse(route, false), handler);
return @this;
}
/// <summary>
/// Associates <c>GET</c> requests matching a route to a synchronous handler.
/// </summary>
/// <param name="this">The <see cref="RoutingModule"/> on which this method is called.</param>
/// <param name="route">The route to match URL paths against.</param>
/// <param name="handler">A callback used to handle matching contexts.</param>
/// <returns><paramref name="this"/> with the handler added.</returns>
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="route"/> is <see langword="null"/>.</para>
/// <para>- or -</para>
/// <para><paramref name="handler"/> is <see langword="null"/>.</para>
/// </exception>
/// <exception cref="FormatException"><paramref name="route"/> is not a valid route.</exception>
public static RoutingModule OnGet(this RoutingModule @this, string route, SyncRouteHandlerCallback handler)
{
@this.Add(HttpVerbs.Get, RouteMatcher.Parse(route, false), handler);
return @this;
}
/// <summary>
/// Associates <c>HEAD</c> requests matching a route to a handler.
/// </summary>
/// <param name="this">The <see cref="RoutingModule"/> on which this method is called.</param>
/// <param name="route">The route to match URL paths against.</param>
/// <param name="handler">A callback used to handle matching contexts.</param>
/// <returns><paramref name="this"/> with the handler added.</returns>
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="route"/> is <see langword="null"/>.</para>
/// <para>- or -</para>
/// <para><paramref name="handler"/> is <see langword="null"/>.</para>
/// </exception>
/// <exception cref="FormatException"><paramref name="route"/> is not a valid route.</exception>
public static RoutingModule OnHead(this RoutingModule @this, string route, RouteHandlerCallback handler)
{
@this.Add(HttpVerbs.Head, RouteMatcher.Parse(route, false), handler);
return @this;
}
/// <summary>
/// Associates <c>HEAD</c> requests matching a route to a synchronous handler.
/// </summary>
/// <param name="this">The <see cref="RoutingModule"/> on which this method is called.</param>
/// <param name="route">The route to match URL paths against.</param>
/// <param name="handler">A callback used to handle matching contexts.</param>
/// <returns><paramref name="this"/> with the handler added.</returns>
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="route"/> is <see langword="null"/>.</para>
/// <para>- or -</para>
/// <para><paramref name="handler"/> is <see langword="null"/>.</para>
/// </exception>
/// <exception cref="FormatException"><paramref name="route"/> is not a valid route.</exception>
public static RoutingModule OnHead(this RoutingModule @this, string route, SyncRouteHandlerCallback handler)
{
@this.Add(HttpVerbs.Head, RouteMatcher.Parse(route, false), handler);
return @this;
}
/// <summary>
/// Associates <c>OPTIONS</c> requests matching a route to a handler.
/// </summary>
/// <param name="this">The <see cref="RoutingModule"/> on which this method is called.</param>
/// <param name="route">The route to match URL paths against.</param>
/// <param name="handler">A callback used to handle matching contexts.</param>
/// <returns><paramref name="this"/> with the handler added.</returns>
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="route"/> is <see langword="null"/>.</para>
/// <para>- or -</para>
/// <para><paramref name="handler"/> is <see langword="null"/>.</para>
/// </exception>
/// <exception cref="FormatException"><paramref name="route"/> is not a valid route.</exception>
public static RoutingModule OnOptions(this RoutingModule @this, string route, RouteHandlerCallback handler)
{
@this.Add(HttpVerbs.Options, RouteMatcher.Parse(route, false), handler);
return @this;
}
/// <summary>
/// Associates <c>OPTIONS</c> requests matching a route to a synchronous handler.
/// </summary>
/// <param name="this">The <see cref="RoutingModule"/> on which this method is called.</param>
/// <param name="route">The route to match URL paths against.</param>
/// <param name="handler">A callback used to handle matching contexts.</param>
/// <returns><paramref name="this"/> with the handler added.</returns>
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="route"/> is <see langword="null"/>.</para>
/// <para>- or -</para>
/// <para><paramref name="handler"/> is <see langword="null"/>.</para>
/// </exception>
/// <exception cref="FormatException"><paramref name="route"/> is not a valid route.</exception>
public static RoutingModule OnOptions(this RoutingModule @this, string route, SyncRouteHandlerCallback handler)
{
@this.Add(HttpVerbs.Options, RouteMatcher.Parse(route, false), handler);
return @this;
}
/// <summary>
/// Associates <c>PATCH</c> requests matching a route to a handler.
/// </summary>
/// <param name="this">The <see cref="RoutingModule"/> on which this method is called.</param>
/// <param name="route">The route to match URL paths against.</param>
/// <param name="handler">A callback used to handle matching contexts.</param>
/// <returns><paramref name="this"/> with the handler added.</returns>
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="route"/> is <see langword="null"/>.</para>
/// <para>- or -</para>
/// <para><paramref name="handler"/> is <see langword="null"/>.</para>
/// </exception>
/// <exception cref="FormatException"><paramref name="route"/> is not a valid route.</exception>
public static RoutingModule OnPatch(this RoutingModule @this, string route, RouteHandlerCallback handler)
{
@this.Add(HttpVerbs.Patch, RouteMatcher.Parse(route, false), handler);
return @this;
}
/// <summary>
/// Associates <c>PATCH</c> requests matching a route to a synchronous handler.
/// </summary>
/// <param name="this">The <see cref="RoutingModule"/> on which this method is called.</param>
/// <param name="route">The route to match URL paths against.</param>
/// <param name="handler">A callback used to handle matching contexts.</param>
/// <returns><paramref name="this"/> with the handler added.</returns>
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="route"/> is <see langword="null"/>.</para>
/// <para>- or -</para>
/// <para><paramref name="handler"/> is <see langword="null"/>.</para>
/// </exception>
/// <exception cref="FormatException"><paramref name="route"/> is not a valid route.</exception>
public static RoutingModule OnPatch(this RoutingModule @this, string route, SyncRouteHandlerCallback handler)
{
@this.Add(HttpVerbs.Patch, RouteMatcher.Parse(route, false), handler);
return @this;
}
/// <summary>
/// Associates <c>POST</c> requests matching a route to a handler.
/// </summary>
/// <param name="this">The <see cref="RoutingModule"/> on which this method is called.</param>
/// <param name="route">The route to match URL paths against.</param>
/// <param name="handler">A callback used to handle matching contexts.</param>
/// <returns><paramref name="this"/> with the handler added.</returns>
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="route"/> is <see langword="null"/>.</para>
/// <para>- or -</para>
/// <para><paramref name="handler"/> is <see langword="null"/>.</para>
/// </exception>
/// <exception cref="FormatException"><paramref name="route"/> is not a valid route.</exception>
public static RoutingModule OnPost(this RoutingModule @this, string route, RouteHandlerCallback handler)
{
@this.Add(HttpVerbs.Post, RouteMatcher.Parse(route, false), handler);
return @this;
}
/// <summary>
/// Associates <c>POST</c> requests matching a route to a synchronous handler.
/// </summary>
/// <param name="this">The <see cref="RoutingModule"/> on which this method is called.</param>
/// <param name="route">The route to match URL paths against.</param>
/// <param name="handler">A callback used to handle matching contexts.</param>
/// <returns><paramref name="this"/> with the handler added.</returns>
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="route"/> is <see langword="null"/>.</para>
/// <para>- or -</para>
/// <para><paramref name="handler"/> is <see langword="null"/>.</para>
/// </exception>
/// <exception cref="FormatException"><paramref name="route"/> is not a valid route.</exception>
public static RoutingModule OnPost(this RoutingModule @this, string route, SyncRouteHandlerCallback handler)
{
@this.Add(HttpVerbs.Post, RouteMatcher.Parse(route, false), handler);
return @this;
}
/// <summary>
/// Associates <c>PUT</c> requests matching a route to a handler.
/// </summary>
/// <param name="this">The <see cref="RoutingModule"/> on which this method is called.</param>
/// <param name="route">The route to match URL paths against.</param>
/// <param name="handler">A callback used to handle matching contexts.</param>
/// <returns><paramref name="this"/> with the handler added.</returns>
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="route"/> is <see langword="null"/>.</para>
/// <para>- or -</para>
/// <para><paramref name="handler"/> is <see langword="null"/>.</para>
/// </exception>
/// <exception cref="FormatException"><paramref name="route"/> is not a valid route.</exception>
public static RoutingModule OnPut(this RoutingModule @this, string route, RouteHandlerCallback handler)
{
@this.Add(HttpVerbs.Put, RouteMatcher.Parse(route, false), handler);
return @this;
}
/// <summary>
/// Associates <c>PUT</c> requests matching a route to a synchronous handler.
/// </summary>
/// <param name="this">The <see cref="RoutingModule"/> on which this method is called.</param>
/// <param name="route">The route to match URL paths against.</param>
/// <param name="handler">A callback used to handle matching contexts.</param>
/// <returns><paramref name="this"/> with the handler added.</returns>
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="route"/> is <see langword="null"/>.</para>
/// <para>- or -</para>
/// <para><paramref name="handler"/> is <see langword="null"/>.</para>
/// </exception>
/// <exception cref="FormatException"><paramref name="route"/> is not a valid route.</exception>
public static RoutingModule OnPut(this RoutingModule @this, string route, SyncRouteHandlerCallback handler)
{
@this.Add(HttpVerbs.Put, RouteMatcher.Parse(route, false), handler);
return @this;
}
}
}

View File

@@ -0,0 +1,26 @@
using System;
namespace EmbedIO.Routing
{
/// <summary>
/// Provides extension methods for <see cref="RoutingModule"/>.
/// </summary>
public static partial class RoutingModuleExtensions
{
/// <summary>
/// <para>Adds handlers, associating them with HTTP method / route pairs by means
/// of <see cref="RouteAttribute">Route</see> attributes.</para>
/// <para>See <see cref="RouteVerbResolverCollection.AddFrom(object)"/> for further information.</para>
/// </summary>
/// <param name="this">The <see cref="RoutingModule"/> on which this method is called.</param>
/// <param name="target">Where to look for compatible handlers.</param>
/// <returns><paramref name="this"/> with handlers added.</returns>
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException"><paramref name="target"/> is <see langword="null"/>.</exception>
public static RoutingModule WithHandlersFrom(this RoutingModule @this, object target)
{
@this.AddFrom(target);
return @this;
}
}
}

View File

@@ -0,0 +1,10 @@
namespace EmbedIO.Routing
{
/// <summary>
/// Base class for callbacks used to handle routed requests synchronously.
/// </summary>
/// <param name="context">An <see cref="IHttpContext" /> interface representing the context of the request.</param>
/// <param name="route">The matched route.</param>
/// <seealso cref="RouteMatch"/>
public delegate void SyncRouteHandlerCallback(IHttpContext context, RouteMatch route);
}