| | | 1 | | /******************************************************************************** |
| | | 2 | | * Router.cs * |
| | | 3 | | * * |
| | | 4 | | * Author: Denes Solti * |
| | | 5 | | ********************************************************************************/ |
| | | 6 | | using System; |
| | | 7 | | using System.Diagnostics.CodeAnalysis; |
| | | 8 | | using System.Net.Http; |
| | | 9 | | using System.Threading; |
| | | 10 | | using System.Threading.Tasks; |
| | | 11 | | |
| | | 12 | | namespace NanoRoute |
| | | 13 | | { |
| | | 14 | | using Internals; |
| | | 15 | | |
| | | 16 | | /// <summary> |
| | | 17 | | /// Executes the route matching pipeline built by <see cref="RouteBuilder"/>. |
| | | 18 | | /// </summary> |
| | | 19 | | /// <remarks> |
| | | 20 | | /// A router is created from a builder snapshot. Matching walks the configured route tree, attaches bound parameters |
| | | 21 | | /// handlers in order until one returns a response without delegating further. |
| | | 22 | | /// </remarks> |
| | | 23 | | public abstract class Router: RoutingContext |
| | | 24 | | { |
| | | 25 | | /// <summary> |
| | | 26 | | /// The request property key that stores the trace identifier associated with the current request. |
| | | 27 | | /// </summary> |
| | | 28 | | public const string TraceIdName = "TraceId"; |
| | | 29 | | |
| | | 30 | | /// <summary> |
| | | 31 | | /// The request property key that stores the original transport-specific request object. |
| | | 32 | | /// </summary> |
| | | 33 | | public const string OriginalRequestName = "OriginalRequest"; |
| | | 34 | | |
| | | 35 | | private static RouteNode CopyRoot(RouteBuilder routeBuilder) |
| | 2 | 36 | | { |
| | | 37 | | // The base() ctor invocation runs first so we have to do the validation here |
| | 2 | 38 | | Ensure.NotNull(routeBuilder); |
| | 2 | 39 | | return routeBuilder.GetRoot(frozen: true); |
| | 2 | 40 | | } |
| | | 41 | | |
| | | 42 | | /// <summary> |
| | | 43 | | /// Initializes a router from a route builder snapshot and router configuration. |
| | | 44 | | /// </summary> |
| | | 45 | | /// <param name="routeBuilder">The builder whose current route tree should be frozen into this router.</param> |
| | | 46 | | /// <param name="config">The router configuration that controls runtime behavior.</param> |
| | | 47 | | [SuppressMessage("ApiDesign", "RS0022:Constructor make noninheritable base class inheritable")] |
| | 2 | 48 | | protected Router(RouteBuilder routeBuilder, RouterConfig config): base(CopyRoot(routeBuilder)) |
| | 2 | 49 | | { |
| | | 50 | | //Ensure.NotNull(routeBuilder); |
| | 2 | 51 | | Ensure.NotNull(config); |
| | | 52 | | |
| | 2 | 53 | | MatchingPrecedence = config.MatchingPrecedence; |
| | 2 | 54 | | } |
| | | 55 | | |
| | | 56 | | /// <summary> |
| | | 57 | | /// Gets the configured precedence between literal and parameterized child segments. |
| | | 58 | | /// </summary> |
| | | 59 | | public MatchingPrecedence MatchingPrecedence |
| | | 60 | | { |
| | | 61 | | get; |
| | | 62 | | private init |
| | 2 | 63 | | { |
| | 2 | 64 | | if (!Enum.IsDefined(typeof(MatchingPrecedence), value)) |
| | 1 | 65 | | throw new ArgumentOutOfRangeException(nameof(value)); |
| | | 66 | | |
| | 2 | 67 | | field = value; |
| | 2 | 68 | | } |
| | | 69 | | } |
| | | 70 | | |
| | | 71 | | /// <summary> |
| | | 72 | | /// Routes an <see cref="HttpRequestMessage"/> through the configured handler pipeline. |
| | | 73 | | /// </summary> |
| | | 74 | | /// <param name="request">The request to process.</param> |
| | | 75 | | /// <param name="services">The service provider exposed to value parsers and handlers.</param> |
| | | 76 | | /// <param name="cancellation">A token that can cancel request processing.</param> |
| | | 77 | | /// <returns>The <see cref="HttpResponseMessage"/> produced by the matching handlers.</returns> |
| | | 78 | | /// <remarks> |
| | | 79 | | /// Prefix routes can participate in the same pipeline as exact routes. Consecutive <c>/</c> separators in the |
| | | 80 | | /// request path are treated as a single separator during matching. When several handlers match, NanoRoute |
| | | 81 | | /// evaluates compatible matches from shorter prefixes toward more specific matches and honors |
| | | 82 | | /// <see cref="MatchingPrecedence"/> when both literal and parameterized segments are available at the same dept |
| | | 83 | | /// Once a branch is selected at a given depth, NanoRoute does not return to sibling branches later in the pipel |
| | | 84 | | /// </remarks> |
| | | 85 | | /// <exception cref="HttpRequestException">Thrown when no handler matches the request path.</exception> |
| | | 86 | | /// <exception cref="ArgumentException">Thrown when the request uses an unsupported HTTP method.</exception> |
| | | 87 | | /// <exception cref="OperationCanceledException"> |
| | | 88 | | /// Thrown when the caller cancels the <paramref name="cancellation"/>. |
| | | 89 | | /// </exception> |
| | | 90 | | #if DEBUG |
| | | 91 | | internal |
| | | 92 | | #endif |
| | | 93 | | protected async Task<HttpResponseMessage> Handle(HttpRequestMessage request, IServiceProvider services, Cancella |
| | | 94 | | { |
| | | 95 | | Ensure.NotNull(request); |
| | | 96 | | Ensure.NotNull(services); |
| | | 97 | | |
| | | 98 | | RouterEventSource.Info.Write("RequestProcessingStarted", static request => new |
| | | 99 | | { |
| | | 100 | | RequestUri = request.RequestUri.OriginalString, |
| | | 101 | | Verb = request.Method.Method |
| | | 102 | | }, request); |
| | | 103 | | |
| | | 104 | | await using RequestPipeline pipeline = new(_root, MatchingPrecedence, request, services, cancellation); |
| | | 105 | | |
| | | 106 | | return await pipeline.RunAsync(); |
| | | 107 | | } |
| | | 108 | | } |
| | | 109 | | } |