< Summary

Information
Class: NanoRoute.ValueSourceAttribute
Assembly: NanoRoute.dll
File(s): /home/runner/work/nanoroute/nanoroute/Src/NanoRoute/Public/Extensions/NanoRouteHandlerExtensions.cs
Line coverage
100%
Covered lines: 7
Uncovered lines: 0
Coverable lines: 7
Total lines: 687
Line coverage: 100%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBlocks covered Blocks not covered
ValueSourceAttribute(...)20

File(s)

/home/runner/work/nanoroute/nanoroute/Src/NanoRoute/Public/Extensions/NanoRouteHandlerExtensions.cs

#LineLine coverage
 1/********************************************************************************
 2* NanoRouteHandlerExtensions.cs                                                 *
 3*                                                                               *
 4* Author: Denes Solti                                                           *
 5********************************************************************************/
 6using System;
 7using System.Collections.Generic;
 8using System.Diagnostics;
 9using System.Diagnostics.CodeAnalysis;
 10using System.Linq.Expressions;
 11using System.Net.Http;
 12using System.Reflection;
 13using System.Runtime.CompilerServices;
 14using System.Threading;
 15using System.Threading.Tasks;
 16
 17using Microsoft.Extensions.DependencyInjection;
 18
 19namespace NanoRoute
 20{
 21    using Internals;
 22    using Properties;
 23
 24    /// <summary>
 25    /// Represents a typed endpoint handler in the router pipeline.
 26    /// </summary>
 27    /// <typeparam name="TRequestContext">
 28    /// The request-object type populated from the current route parameters, query bindings, services, and special frame
 29    /// </typeparam>
 30    /// <param name="requestContext">
 31    /// The typed request object built from the current <see cref="RequestContext"/>.
 32    /// </param>
 33    /// <returns>The response produced by the current handler.</returns>
 34    /// <remarks>
 35    /// This delegate is used by <see cref="NanoRouteHandlerExtensions"/> overloads that do not expose
 36    /// <see cref="CallNextHandlerDelegate"/>. The pipeline stops when the handler returns.
 37    /// Exceptions thrown by the handler propagate through the routing pipeline unless middleware handles them.
 38    /// </remarks>
 39    /// <example>
 40    /// <code>
 41    /// builder.AddHandler&lt;UserRequest&gt;("GET", "/users/{id:int}/", request =&gt;
 42    ///     Results.Ok(request.Id));
 43    /// </code>
 44    /// </example>
 45    public delegate Task<HttpResponseMessage> TypedRequestEndpointHandlerDelegate<[DynamicallyAccessedMembers(Dynamicall
 46
 47    /// <summary>
 48    /// Represents a typed request handler in the router pipeline.
 49    /// </summary>
 50    /// <typeparam name="TRequestContext">
 51    /// The request-object type populated from the current route parameters, query bindings, services, and special frame
 52    /// </typeparam>
 53    /// <param name="requestContext">
 54    /// The typed request object built from the current <see cref="RequestContext"/>.
 55    /// </param>
 56    /// <param name="callNext">A delegate that invokes the next compatible handler in the pipeline.</param>
 57    /// <returns>
 58    /// The response produced by the current handler, or by a later handler when <paramref name="callNext"/> is invoked.
 59    /// </returns>
 60    /// <remarks>
 61    /// This delegate is used by <see cref="NanoRouteHandlerExtensions"/> overloads that expose
 62    /// <see cref="CallNextHandlerDelegate"/>.
 63    /// Exceptions thrown by the handler propagate through the routing pipeline unless middleware handles them.
 64    /// </remarks>
 65    /// <example>
 66    /// <code>
 67    /// builder.AddHandler&lt;UserRequest&gt;("GET", "/users/{id:int}/*", (request, next) =&gt; next());
 68    /// </code>
 69    /// </example>
 70    public delegate Task<HttpResponseMessage> TypedRequestHandlerDelegate<[DynamicallyAccessedMembers(DynamicallyAccesse
 71
 72    /// <summary>
 73    /// Describes how a typed handler property is populated.
 74    /// </summary>
 75    /// <example>
 76    /// <code>
 77    /// public sealed class UserRequest
 78    /// {
 79    ///     [ValueSource(ValueSource.Parameter, Name = "id")]
 80    ///     public int Id { get; init; }
 81    /// }
 82    /// </code>
 83    /// </example>
 84    public enum ValueSource
 85    {
 86        /// <summary>
 87        /// Leaves the property untouched.
 88        /// </summary>
 89        /// <example>
 90        /// <code>
 91        /// [ValueSource(ValueSource.Skip)]
 92        /// public string? Ignored { get; init; }
 93        /// </code>
 94        /// </example>
 95        Skip,
 96
 97        /// <summary>
 98        /// Reads the value from <see cref="RequestContext.Parameters"/>.
 99        /// </summary>
 100        /// <example>
 101        /// <code>
 102        /// [ValueSource(ValueSource.Parameter, Name = "id")]
 103        /// public int Id { get; init; }
 104        /// </code>
 105        /// </example>
 106        Parameter,
 107
 108        /// <summary>
 109        /// Resolves the value from <see cref="RequestContext.Services"/>.
 110        /// </summary>
 111        /// <example>
 112        /// <code>
 113        /// [ValueSource(ValueSource.ServiceLocator)]
 114        /// public IUserRepository Users { get; init; } = null!;
 115        /// </code>
 116        /// </example>
 117        ServiceLocator
 118    }
 119
 120    /// <summary>
 121    /// Overrides the default binding behavior for a typed handler request property.
 122    /// </summary>
 123    /// <param name="source">The source used to populate the annotated property.</param>
 124    /// <example>
 125    /// <code>
 126    /// public sealed class UserRequest
 127    /// {
 128    ///     [ValueSource(ValueSource.Parameter, Name = "user_id")]
 129    ///     public int Id { get; init; }
 130    /// }
 131    /// </code>
 132    /// </example>
 133    [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
 1134    public sealed class ValueSourceAttribute(ValueSource source) : Attribute
 135    {
 136        /// <summary>
 137        /// Gets the binding source used for the annotated property.
 138        /// </summary>
 1139        public ValueSource Source { get; } = source;
 140
 141        /// <summary>
 142        /// Gets or sets an optional binding name.
 143        /// </summary>
 144        /// <remarks>
 145        /// For <see cref="ValueSource.Parameter"/>, this overrides the key looked up in
 146        /// <see cref="RequestContext.Parameters"/>. For <see cref="ValueSource.ServiceLocator"/>,
 147        /// this is treated as the keyed service name. <see cref="ValueSource.Skip"/> does not allow
 148        /// a name because no value is read.
 149        /// </remarks>
 150        /// <exception cref="InvalidOperationException">Thrown when a name is assigned while <see cref="Source"/> is <se
 151        /// <example>
 152        /// <code>
 153        /// [ValueSource(ValueSource.Parameter, Name = "user_id")]
 154        /// public int Id { get; init; }
 155        /// </code>
 156        /// </example>
 157        public string? Name
 158        {
 159            get;
 160            init
 1161            {
 1162                if (Source is ValueSource.Skip && value is not null)
 1163                    throw new InvalidOperationException(Resources.ERR_SKIPPED_VALUE_SOURCE_NAME);
 164
 1165                field = value;
 1166            }
 167        }
 168    }
 169
 170    /// <summary>
 171    /// Adds typed handler overloads that project a <see cref="RequestContext"/> into a request object.
 172    /// </summary>
 173    /// <remarks>
 174    /// <para>
 175    /// By default, writable public properties are bound from <see cref="RequestContext.Parameters"/>
 176    /// using the property name as the lookup key.
 177    /// </para>
 178    /// <para>
 179    /// Properties of type <see cref="RequestContext"/> and <see cref="CancellationToken"/> are populated
 180    /// automatically from the current request.
 181    /// </para>
 182    /// <para>
 183    /// Use <see cref="ValueSourceAttribute"/> to bind a property from a specific parameter key or service.
 184    /// Missing required parameter values and services throw <see cref="InvalidOperationException"/>.
 185    /// </para>
 186    /// </remarks>
 187    /// <example>
 188    /// <code>
 189    /// builder
 190    ///     .AddDefaultValueParsers()
 191    ///     .AddHandler&lt;UserRequest&gt;("GET", "/users/{id:int}/", request =&gt;
 192    ///         Results.Ok(request.Id));
 193    /// </code>
 194    /// </example>
 195    public static class NanoRouteHandlerExtensions
 196    {
 197        #region Private
 198        private static class ContextMapper<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties |
 199        {
 200            private static readonly Lazy<Func<RequestContext, TRequestContext>> s_MapperFunction = new
 201            (
 202                static () =>
 203                {
 204                    ParameterExpression
 205                        source = Expression.Parameter(typeof(RequestContext), nameof(source)),
 206                        result = Expression.Variable(typeof(TRequestContext), nameof(result));
 207
 208                    List<Expression> propSetters =
 209                    [
 210                        Expression.Assign
 211                        (
 212                            result,
 213                            Expression.New
 214                            (
 215                                typeof(TRequestContext).GetConstructor(Type.EmptyTypes)
 216                            )
 217                        )
 218                    ];
 219
 220                    foreach (PropertyInfo prop in typeof(TRequestContext).GetProperties(BindingFlags.Instance | BindingF
 221                    {
 222                        if (!prop.CanWrite)
 223                            continue;
 224
 225                        ValueSourceAttribute? valueSource = prop.GetCustomAttribute<ValueSourceAttribute>();
 226
 227                        switch (valueSource?.Source)
 228                        {
 229                            case ValueSource.Skip:
 230                                continue;
 231                            case ValueSource.ServiceLocator:
 232                            {
 233                                SetProperty
 234                                (
 235                                    valueSource?.Name is { } name
 236                                        ? context => context.Services.GetRequiredKeyedService(prop.PropertyType, name)
 237                                        : context => context.Services.GetRequiredService(prop.PropertyType)
 238                                );
 239                                continue;
 240                            }
 241                            case ValueSource.Parameter:
 242                            {
 243                                string name = valueSource?.Name ?? prop.Name;
 244                                SetProperty
 245                                (
 246                                    context => context.Parameters.TryGetValue(name, out object? arg)
 247                                        ? arg!
 248                                        // InvalidOperationException may map to HTTP 500 but the developer message will 
 249                                        : throw new InvalidOperationException(string.Format(Resources.Culture, Resources
 250                                );
 251                                continue;
 252                            }
 253                            case null:
 254                                switch (prop.PropertyType)
 255                                {
 256                                    case Type x when x == typeof(RequestContext):
 257                                        SetPropertyValue(source);
 258                                        continue;
 259                                    case Type x when x == typeof(CancellationToken):
 260                                        SetProperty(static context => context.Cancellation);
 261                                        continue;
 262                                }
 263                                goto case ValueSource.Parameter;
 264                            default:
 265                                Debug.Fail($"Unknown source: {valueSource.Source}");
 266                                break;
 267                        }
 268
 269                        void SetPropertyValue(Expression value) => propSetters.Add
 270                        (
 271                            Expression.Assign
 272                            (
 273                                Expression.Property(result, prop),
 274                                Expression.Convert(value, prop.PropertyType)
 275                            )
 276                        );
 277
 278                        void SetProperty(Func<RequestContext, object> del) => SetPropertyValue
 279                        (
 280                            Expression.Invoke
 281                            (
 282                                Expression.Constant(del, typeof(Func<RequestContext, object>)),
 283                                source
 284                            )
 285                        );
 286                    }
 287
 288                    propSetters.Add(result);  // return result;
 289
 290                    // In native AOT context this will be interpreted rather than compiled
 291                    return Expression
 292                        .Lambda<Func<RequestContext, TRequestContext>>
 293                        (
 294                            Expression.Block([result], propSetters),
 295                            source
 296                        ).
 297                        Compile
 298                        (
 299                            preferInterpretation: !RuntimeFeature.IsDynamicCodeSupported
 300                        );
 301                },
 302                isThreadSafe: true
 303            );
 304
 305            public static Func<RequestContext, TRequestContext> Value => s_MapperFunction.Value;
 306        }
 307
 308        private static TBuilder AddTypedHandlerCore<TBuilder, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes
 309        {
 310            Ensure.NotNull(routeScopeBuilder);
 311            Ensure.NotNull(verbs);
 312            Ensure.NotNull(pattern);
 313            Ensure.NotNull(handler);
 314
 315            Func<RequestContext, TRequestContext> mapContext = ContextMapper<TRequestContext>.Value;
 316
 317            routeScopeBuilder.AddHandler(verbs, pattern, (context, next) => handler(mapContext(context), next));
 318
 319            return routeScopeBuilder;
 320        }
 321
 322        private static EndpointBuilder WithTypedHandlerCore<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.P
 323        {
 324            Ensure.NotNull(endpointBuilder);
 325            Ensure.NotNull(handler);
 326
 327            Func<RequestContext, TRequestContext> mapContext = ContextMapper<TRequestContext>.Value;
 328
 329            return endpointBuilder.WithHandler((context, next) => handler(mapContext(context), next));
 330        }
 331        #endregion
 332
 333        extension<TBuilder>(TBuilder routeScopeBuilder) where TBuilder : RouteScopeBuilder
 334        {
 335            /// <summary>
 336            /// Registers a typed handler that receives a request object built from the current <see cref="RequestContex
 337            /// </summary>
 338            /// <typeparam name="TRequestContext">
 339            /// The request-object type populated from the current route parameters, query bindings, services, and speci
 340            /// </typeparam>
 341            /// <param name="pattern">The route pattern to register for all supported HTTP methods.</param>
 342            /// <param name="handler">The typed handler delegate.</param>
 343            /// <returns>The current <paramref name="routeScopeBuilder"/>.</returns>
 344            /// <remarks>
 345            /// <para>
 346            /// Writable public properties are bound from <see cref="RequestContext.Parameters"/> by default.
 347            /// </para>
 348            /// <para>
 349            /// A property of type <see cref="RequestContext"/> receives the current context, and a property of type
 350            /// <see cref="CancellationToken"/> receives the active request token.
 351            /// </para>
 352            /// <para>
 353            /// Apply <see cref="ValueSourceAttribute"/> to bind a property from a different parameter name
 354            /// or from the request service provider.
 355            /// </para>
 356            /// </remarks>
 357            /// <exception cref="ArgumentNullException">Thrown when <paramref name="routeScopeBuilder"/>, <paramref name
 358            /// <exception cref="ArgumentException">Thrown when <paramref name="pattern"/> has invalid route-template sy
 359            /// <exception cref="InvalidOperationException">Thrown for unsupported route-template features, missing valu
 360            /// <example>
 361            /// <code>
 362            /// builder.AddHandler&lt;UserRequest&gt;("/users/{id:int}/", request =&gt;
 363            ///     Results.Ok(request.Id));
 364            /// </code>
 365            /// </example>
 366            public TBuilder AddHandler<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | Dyn
 367                routeScopeBuilder.AddHandler(HttpVerb.Names, pattern, handler);
 368
 369            /// <summary>
 370            /// Registers a typed handler that receives a request object built from the current <see cref="RequestContex
 371            /// </summary>
 372            /// <typeparam name="TRequestContext">
 373            /// The request-object type populated from the current route parameters, query bindings, services, and speci
 374            /// </typeparam>
 375            /// <param name="verb">The HTTP verb handled by the route.</param>
 376            /// <param name="pattern">The route pattern to register.</param>
 377            /// <param name="handler">The typed handler delegate.</param>
 378            /// <returns>The current <paramref name="routeScopeBuilder"/>.</returns>
 379            /// <remarks>
 380            /// <para>
 381            /// Writable public properties are bound from <see cref="RequestContext.Parameters"/> by default.
 382            /// </para>
 383            /// <para>
 384            /// A property of type <see cref="RequestContext"/> receives the current context, and a property of type
 385            /// <see cref="CancellationToken"/> receives the active request token.
 386            /// </para>
 387            /// <para>
 388            /// Apply <see cref="ValueSourceAttribute"/> to bind a property from a different parameter name
 389            /// or from the request service provider.
 390            /// </para>
 391            /// </remarks>
 392            /// <exception cref="ArgumentNullException">Thrown when <paramref name="routeScopeBuilder"/>, <paramref name
 393            /// <exception cref="ArgumentException">Thrown when <paramref name="verb"/> is not supported or <paramref na
 394            /// <exception cref="InvalidOperationException">Thrown for unsupported route-template features, missing valu
 395            /// <example>
 396            /// <code>
 397            /// builder.AddHandler&lt;UserRequest&gt;("GET", "/users/{id:int}/", request =&gt;
 398            ///     Results.Ok(request.Id));
 399            /// </code>
 400            /// </example>
 401            public TBuilder AddHandler<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | Dyn
 402                routeScopeBuilder.AddHandler([verb /*will be null checked*/], pattern, handler);
 403
 404            /// <summary>
 405            /// Registers a typed handler that receives a request object built from the current <see cref="RequestContex
 406            /// </summary>
 407            /// <typeparam name="TRequestContext">
 408            /// The request-object type populated from the current route parameters, query bindings, services, and speci
 409            /// </typeparam>
 410            /// <param name="verbs">The HTTP verbs handled by the route.</param>
 411            /// <param name="pattern">The route pattern to register.</param>
 412            /// <param name="handler">The typed handler delegate.</param>
 413            /// <returns>The current <paramref name="routeScopeBuilder"/>.</returns>
 414            /// <remarks>
 415            /// <para>
 416            /// Writable public properties are bound from <see cref="RequestContext.Parameters"/> by default.
 417            /// </para>
 418            /// <para>
 419            /// A property of type <see cref="RequestContext"/> receives the current context, and a property of type
 420            /// <see cref="CancellationToken"/> receives the active request token.
 421            /// </para>
 422            /// <para>
 423            /// Apply <see cref="ValueSourceAttribute"/> to bind a property from a different parameter name
 424            /// or from the request service provider.
 425            /// </para>
 426            /// </remarks>
 427            /// <exception cref="ArgumentNullException">Thrown when <paramref name="routeScopeBuilder"/>, <paramref name
 428            /// <exception cref="ArgumentException">Thrown when an entry in <paramref name="verbs"/> is not supported or
 429            /// <exception cref="InvalidOperationException">Thrown for unsupported route-template features, missing valu
 430            /// <example>
 431            /// <code>
 432            /// builder.AddHandler&lt;UserRequest&gt;(["GET", "HEAD"], "/users/{id:int}/", request =&gt;
 433            ///     Results.Ok(request.Id));
 434            /// </code>
 435            /// </example>
 436            public TBuilder AddHandler<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | Dyn
 437            {
 438                Ensure.NotNull(handler);
 439                return AddTypedHandlerCore(routeScopeBuilder, verbs, pattern, (TRequestContext context, CallNextHandlerD
 440            }
 441
 442            /// <summary>
 443            /// Registers a typed handler that receives a request object and the next handler in the pipeline.
 444            /// </summary>
 445            /// <typeparam name="TRequestContext">
 446            /// The request-object type populated from the current route parameters, query bindings, services, and speci
 447            /// </typeparam>
 448            /// <param name="pattern">The route pattern to register for all supported HTTP methods.</param>
 449            /// <param name="handler">The typed handler delegate.</param>
 450            /// <returns>The current <paramref name="routeScopeBuilder"/>.</returns>
 451            /// <remarks>
 452            /// <para>
 453            /// Writable public properties are bound from <see cref="RequestContext.Parameters"/> by default.
 454            /// </para>
 455            /// <para>
 456            /// A property of type <see cref="RequestContext"/> receives the current context, and a property of type
 457            /// <see cref="CancellationToken"/> receives the active request token.
 458            /// </para>
 459            /// <para>
 460            /// Apply <see cref="ValueSourceAttribute"/> to bind a property from a different parameter name
 461            /// or from the request service provider.
 462            /// </para>
 463            /// </remarks>
 464            /// <exception cref="ArgumentNullException">Thrown when <paramref name="routeScopeBuilder"/>, <paramref name
 465            /// <exception cref="ArgumentException">Thrown when <paramref name="pattern"/> has invalid route-template sy
 466            /// <exception cref="InvalidOperationException">Thrown for unsupported route-template features, missing valu
 467            /// <example>
 468            /// <code>
 469            /// builder.AddHandler&lt;UserRequest&gt;("/users/{id:int}/*", (request, next) =&gt; next());
 470            /// </code>
 471            /// </example>
 472            public TBuilder AddHandler<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | Dyn
 473                routeScopeBuilder.AddHandler(HttpVerb.Names, pattern, handler);
 474
 475            /// <summary>
 476            /// Registers a typed handler that receives a request object and the next handler in the pipeline.
 477            /// </summary>
 478            /// <typeparam name="TRequestContext">
 479            /// The request-object type populated from the current route parameters, query bindings, services, and speci
 480            /// </typeparam>
 481            /// <param name="verb">The HTTP verb handled by the route.</param>
 482            /// <param name="pattern">The route pattern to register.</param>
 483            /// <param name="handler">The typed handler delegate.</param>
 484            /// <returns>The current <paramref name="routeScopeBuilder"/>.</returns>
 485            /// <remarks>
 486            /// <para>
 487            /// Writable public properties are bound from <see cref="RequestContext.Parameters"/> by default.
 488            /// </para>
 489            /// <para>
 490            /// A property of type <see cref="RequestContext"/> receives the current context, and a property of type
 491            /// <see cref="CancellationToken"/> receives the active request token.
 492            /// </para>
 493            /// <para>
 494            /// Apply <see cref="ValueSourceAttribute"/> to bind a property from a different parameter name
 495            /// or from the request service provider.
 496            /// </para>
 497            /// </remarks>
 498            /// <exception cref="ArgumentNullException">Thrown when <paramref name="routeScopeBuilder"/>, <paramref name
 499            /// <exception cref="ArgumentException">Thrown when <paramref name="verb"/> is not supported or <paramref na
 500            /// <exception cref="InvalidOperationException">Thrown for unsupported route-template features, missing valu
 501            /// <example>
 502            /// <code>
 503            /// builder.AddHandler&lt;UserRequest&gt;("GET", "/users/{id:int}/*", (request, next) =&gt; next());
 504            /// </code>
 505            /// </example>
 506            public TBuilder AddHandler<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | Dyn
 507                routeScopeBuilder.AddHandler([verb /*will be null checked*/], pattern, handler);
 508
 509            /// <summary>
 510            /// Registers a typed handler that receives a request object and the next handler in the pipeline.
 511            /// </summary>
 512            /// <typeparam name="TRequestContext">
 513            /// The request-object type populated from the current route parameters, query bindings, services, and speci
 514            /// </typeparam>
 515            /// <param name="verbs">The HTTP verbs handled by the route.</param>
 516            /// <param name="pattern">The route pattern to register.</param>
 517            /// <param name="handler">The typed handler delegate.</param>
 518            /// <returns>The current <paramref name="routeScopeBuilder"/>.</returns>
 519            /// <remarks>
 520            /// <para>
 521            /// Writable public properties are bound from <see cref="RequestContext.Parameters"/> by default.
 522            /// </para>
 523            /// <para>
 524            /// A property of type <see cref="RequestContext"/> receives the current context, and a property of type
 525            /// <see cref="CancellationToken"/> receives the active request token.
 526            /// </para>
 527            /// <para>
 528            /// Apply <see cref="ValueSourceAttribute"/> to bind a property from a different parameter name
 529            /// or from the request service provider.
 530            /// </para>
 531            /// </remarks>
 532            /// <exception cref="ArgumentNullException">Thrown when <paramref name="routeScopeBuilder"/>, <paramref name
 533            /// <exception cref="ArgumentException">Thrown when an entry in <paramref name="verbs"/> is not supported or
 534            /// <exception cref="InvalidOperationException">Thrown for unsupported route-template features, missing valu
 535            /// <example>
 536            /// <code>
 537            /// builder.AddHandler&lt;UserRequest&gt;(["GET", "POST"], "/users/{id:int}/*", (request, next) =&gt; next()
 538            /// </code>
 539            /// </example>
 540            public TBuilder AddHandler<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | Dyn
 541                AddTypedHandlerCore(routeScopeBuilder, verbs, pattern, handler);
 542
 543            /// <summary>
 544            /// Registers a handler for all supported HTTP methods.
 545            /// </summary>
 546            /// <param name="pattern">
 547            /// The route pattern to match. Literal segments are matched case-insensitively, parameter segments use
 548            /// registered parsers in the form <c>{parameterName:parserName}</c>. Exact patterns must end with
 549            /// <c>/</c>, prefix patterns must end with <c>/*</c>, and repeated <c>/</c> separators are invalid.
 550            /// </param>
 551            /// <param name="handler">The handler to execute when the pattern matches.</param>
 552            /// <returns>The current router instance.</returns>
 553            /// <example>
 554            /// <code>
 555            /// builder.AddHandler("/health/", (context, next) =&gt; Results.Ok());
 556            /// </code>
 557            /// </example>
 558            /// <exception cref="ArgumentNullException">Thrown when <paramref name="routeScopeBuilder"/>, <paramref name
 559            /// <exception cref="ArgumentException">Thrown when <paramref name="pattern"/> has invalid route-template sy
 560            /// <exception cref="InvalidOperationException">Thrown when <paramref name="pattern"/> uses unsupported rout
 561            public TBuilder AddHandler(string pattern, RequestHandlerDelegate handler) => routeScopeBuilder.AddHandler(H
 562
 563            /// <summary>
 564            /// Registers the same handler for multiple HTTP methods.
 565            /// </summary>
 566            /// <param name="verbs">The HTTP methods that should use the handler.</param>
 567            /// <param name="pattern">
 568            /// The route pattern to match. Literal segments are matched case-insensitively, parameter segments use
 569            /// registered parsers in the form <c>{parameterName:parserName}</c>. Exact patterns must end with
 570            /// <c>/</c>, prefix patterns must end with <c>/*</c>, and repeated <c>/</c> separators are invalid.
 571            /// </param>
 572            /// <param name="handler">The handler to execute when the route matches.</param>
 573            /// <returns>The current router instance.</returns>
 574            /// <example>
 575            /// <code>
 576            /// builder.AddHandler(
 577            ///     ["GET", "POST"],
 578            ///     "/api/items/{id:int}/",
 579            ///     (context, next) =&gt; Results.Ok(context.Parameters["id"]));
 580            /// </code>
 581            /// </example>
 582            /// <exception cref="ArgumentNullException">Thrown when <paramref name="routeScopeBuilder"/>, <paramref name
 583            /// <exception cref="ArgumentException">Thrown when an entry in <paramref name="verbs"/> is not supported or
 584            /// <exception cref="InvalidOperationException">Thrown when <paramref name="pattern"/> uses unsupported rout
 585            public TBuilder AddHandler(IEnumerable<string> verbs, string pattern, RequestHandlerDelegate handler)
 586            {
 587                Ensure.NotNull(routeScopeBuilder);
 588                Ensure.NotNull(verbs);
 589
 590                foreach (string verb in verbs)
 591                    routeScopeBuilder.AddHandler(verb, pattern, handler);
 592
 593                return routeScopeBuilder;
 594            }
 595
 596            /// <summary>
 597            /// Registers the same handler for multiple HTTP methods at the current builder root.
 598            /// </summary>
 599            /// <param name="verbs">The HTTP methods that should use the handler.</param>
 600            /// <param name="handler">The handler to execute when a matching request enters this builder scope.</param>
 601            /// <returns>The current router instance.</returns>
 602            /// <remarks>
 603            /// This overload uses <see cref="RouteScopeBuilder.CurrentPrefix"/> as the route pattern, so the handler is
 604            /// bound to the whole current builder scope. If the handler calls <c>next</c>, routing continues with
 605            /// the next compatible handler on the selected branch.
 606            /// </remarks>
 607            /// <example>
 608            /// <code>
 609            /// builder.AddHandler(["GET", "POST"], (context, next) =&gt; next());
 610            /// </code>
 611            /// </example>
 612            /// <exception cref="ArgumentNullException">Thrown when <paramref name="routeScopeBuilder"/>, <paramref name
 613            /// <exception cref="ArgumentException">Thrown when an entry in <paramref name="verbs"/> is not a supported 
 614            public TBuilder AddHandler(IEnumerable<string> verbs, RequestHandlerDelegate handler) => routeScopeBuilder.A
 615        }
 616
 617        extension(EndpointBuilder endpointBuilder)
 618        {
 619            /// <summary>
 620            /// Registers a typed endpoint handler that receives a request object built from the current <see cref="Requ
 621            /// </summary>
 622            /// <typeparam name="TRequestContext">
 623            /// The request-object type populated from the current route parameters, query bindings, services, and speci
 624            /// </typeparam>
 625            /// <param name="handler">The typed endpoint handler delegate.</param>
 626            /// <returns>The current <paramref name="endpointBuilder"/> instance.</returns>
 627            /// <remarks>
 628            /// <para>
 629            /// Writable public properties are bound from <see cref="RequestContext.Parameters"/> by default.
 630            /// </para>
 631            /// <para>
 632            /// A property of type <see cref="RequestContext"/> receives the current context, and a property of type
 633            /// <see cref="CancellationToken"/> receives the active request token.
 634            /// </para>
 635            /// <para>
 636            /// Apply <see cref="ValueSourceAttribute"/> to bind a property from a different parameter name
 637            /// or from the request service provider.
 638            /// </para>
 639            /// </remarks>
 640            /// <exception cref="ArgumentNullException">Thrown when <paramref name="endpointBuilder"/> or <paramref name
 641            /// <exception cref="ArgumentException">Thrown when the endpoint's captured HTTP method is not supported.</e
 642            /// <exception cref="InvalidOperationException">Thrown when request-time typed binding cannot resolve a requ
 643            /// <example>
 644            /// <code>
 645            /// endpoint.WithHandler&lt;UserRequest&gt;(request =&gt; Results.Ok(request.Id));
 646            /// </code>
 647            /// </example>
 648            public EndpointBuilder WithHandler<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperti
 649            {
 650                Ensure.NotNull(handler);
 651                return WithTypedHandlerCore(endpointBuilder, (TRequestContext context, CallNextHandlerDelegate _) => han
 652            }
 653
 654            /// <summary>
 655            /// Registers a typed endpoint handler that receives a request object and the next handler in the pipeline.
 656            /// </summary>
 657            /// <typeparam name="TRequestContext">
 658            /// The request-object type populated from the current route parameters, query bindings, services, and speci
 659            /// </typeparam>
 660            /// <param name="handler">The typed endpoint handler delegate.</param>
 661            /// <returns>The current <paramref name="endpointBuilder"/> instance.</returns>
 662            /// <remarks>
 663            /// <para>
 664            /// Writable public properties are bound from <see cref="RequestContext.Parameters"/> by default.
 665            /// </para>
 666            /// <para>
 667            /// A property of type <see cref="RequestContext"/> receives the current context, and a property of type
 668            /// <see cref="CancellationToken"/> receives the active request token.
 669            /// </para>
 670            /// <para>
 671            /// Apply <see cref="ValueSourceAttribute"/> to bind a property from a different parameter name
 672            /// or from the request service provider.
 673            /// </para>
 674            /// </remarks>
 675            /// <exception cref="ArgumentNullException">Thrown when <paramref name="endpointBuilder"/> or <paramref name
 676            /// <exception cref="ArgumentException">Thrown when the endpoint's captured HTTP method is not supported.</e
 677            /// <exception cref="InvalidOperationException">Thrown when request-time typed binding cannot resolve a requ
 678            /// <example>
 679            /// <code>
 680            /// endpoint.WithHandler&lt;UserRequest&gt;((request, next) =&gt; next());
 681            /// </code>
 682            /// </example>
 683            public EndpointBuilder WithHandler<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperti
 684                WithTypedHandlerCore(endpointBuilder, handler);
 685        }
 686    }
 687}