< Summary - Kestrun — Combined Coverage

Information
Class: Kestrun.Hosting.KestrunHostMapExtensions
Assembly: Kestrun
File(s): /home/runner/work/Kestrun/Kestrun/src/CSharp/Kestrun/Hosting/KestrunHostMapExtensions.cs
Tag: Kestrun/Kestrun@9d3a582b2d63930269564a7591aa77ef297cadeb
Line coverage
54%
Covered lines: 140
Uncovered lines: 116
Coverable lines: 256
Total lines: 722
Line coverage: 54.6%
Branch coverage
60%
Covered branches: 66
Total branches: 109
Branch coverage: 60.5%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
AddMapRoute(...)100%11100%
AddMapRoute(...)100%22100%
AddMapRoute(...)66.66%8663.15%
AddMapRoute(...)0%4260%
AddMapRoute(...)0%2040%
AddMapRoute(...)64%422570%
AddMapOptions(...)100%11100%
ApplyShortCircuit(...)25%10428.57%
ApplyAnonymous(...)50%2260%
ApplyAntiforgery(...)50%3240%
ApplyRateLimiting(...)50%3240%
ApplyAuthSchemes(...)100%88100%
ApplyPolicies(...)100%88100%
ApplyCors(...)50%2260%
ApplyOpenApiMetadata(...)50%341037.5%
AddHtmlTemplateRoute(...)0%620%
AddHtmlTemplateRoute(...)92.85%161477.27%
AddStaticMapOverride(...)0%620%
AddStaticMapOverride(...)0%2040%
AddStaticMapOverride(...)100%44100%
AddStaticMapOverride(...)100%210%
MapExists(...)100%11100%
MapExists(...)100%11100%
GetMapRouteOptions(...)50%22100%

File(s)

/home/runner/work/Kestrun/Kestrun/src/CSharp/Kestrun/Hosting/KestrunHostMapExtensions.cs

#LineLine coverage
 1
 2using Kestrun.Hosting.Options;
 3using Kestrun.Languages;
 4using Kestrun.Scripting;
 5using Kestrun.Utilities;
 6using Microsoft.AspNetCore.Authorization;
 7using Serilog.Events;
 8using Kestrun.Models;
 9
 10namespace Kestrun.Hosting;
 11
 12/// <summary>
 13/// Provides extension methods for mapping routes and handlers to the KestrunHost.
 14/// </summary>
 15public static class KestrunHostMapExtensions
 16{
 17    /// <summary>
 18    /// Represents a delegate that handles a Kestrun request with the provided context.
 19    /// </summary>
 20    /// <param name="Context">The context for the Kestrun request.</param>
 21    /// <returns>A task representing the asynchronous operation.</returns>
 22    public delegate Task KestrunHandler(KestrunContext Context);
 23
 24    /// <summary>
 25    /// Adds a native route to the KestrunHost for the specified pattern and HTTP verb.
 26    /// </summary>
 27    /// <param name="host">The KestrunHost instance.</param>
 28    /// <param name="pattern">The route pattern.</param>
 29    /// <param name="httpVerb">The HTTP verb for the route.</param>
 30    /// <param name="handler">The handler to execute for the route.</param>
 31    /// <param name="requireSchemes">Optional array of authorization schemes required for the route.</param>
 32    /// <returns>An IEndpointConventionBuilder for further configuration.</returns>
 233    public static IEndpointConventionBuilder AddMapRoute(this KestrunHost host, string pattern, HttpVerb httpVerb, Kestr
 34
 35    /// <summary>
 36    /// Adds a native route to the KestrunHost for the specified pattern and HTTP verbs.
 37    /// </summary>
 38    /// <param name="host">The KestrunHost instance.</param>
 39    /// <param name="pattern">The route pattern.</param>
 40    /// <param name="httpVerbs">The HTTP verbs for the route.</param>
 41    /// <param name="handler">The handler to execute for the route.</param>
 42    /// <param name="requireSchemes">Optional array of authorization schemes required for the route.</param>
 43    /// <returns>An IEndpointConventionBuilder for further configuration.</returns>
 44    public static IEndpointConventionBuilder AddMapRoute(this KestrunHost host, string pattern, IEnumerable<HttpVerb> ht
 45    {
 246        return host.AddMapRoute(new MapRouteOptions
 247        {
 248            Pattern = pattern,
 249            HttpVerbs = [.. httpVerbs],
 250            Language = ScriptLanguage.Native,
 251            RequireSchemes = requireSchemes ?? [] // No authorization by default
 252        }, handler);
 53    }
 54
 55    /// <summary>
 56    /// Adds a native route to the KestrunHost using the specified MapRouteOptions and handler.
 57    /// </summary>
 58    /// <param name="host">The KestrunHost instance.</param>
 59    /// <param name="options">The MapRouteOptions containing route configuration.</param>
 60    /// <param name="handler">The handler to execute for the route.</param>
 61    /// <returns>An IEndpointConventionBuilder for further configuration.</returns>
 62    public static IEndpointConventionBuilder AddMapRoute(this KestrunHost host, MapRouteOptions options, KestrunHandler 
 63    {
 264        if (host.HostLogger.IsEnabled(LogEventLevel.Debug))
 65        {
 166            host.HostLogger.Debug("AddMapRoute called with options={Options}", options);
 67        }
 68        // Ensure the WebApplication is initialized
 269        if (host.App is null)
 70        {
 071            throw new InvalidOperationException("WebApplication is not initialized. Call EnableConfiguration first.");
 72        }
 73
 74        // Validate options
 175        if (string.IsNullOrWhiteSpace(options.Pattern))
 76        {
 077            throw new ArgumentException("Pattern cannot be null or empty.", nameof(options.Pattern));
 78        }
 79
 280        string[] methods = [.. options.HttpVerbs.Select(v => v.ToMethodString())];
 181        var map = host.App.MapMethods(options.Pattern, methods, async context =>
 182           {
 083               var req = await KestrunRequest.NewRequest(context);
 084               var res = new KestrunResponse(req);
 085               KestrunContext kestrunContext = new(req, res, context);
 086               await handler(kestrunContext);
 087               await res.ApplyTo(context.Response);
 188           });
 89
 190        host.AddMapOptions(map, options);
 91
 192        host.HostLogger.Information("Added native route: {Pattern} with methods: {Methods}", options.Pattern, string.Joi
 93        // Add to the feature queue for later processing
 194        host.FeatureQueue.Add(host => host.AddMapRoute(options));
 195        return map;
 96    }
 97
 98
 99    /// <summary>
 100    /// Adds a route to the KestrunHost that executes a script block for the specified HTTP verb and pattern.
 101    /// </summary>
 102    /// <param name="host">The KestrunHost instance.</param>
 103    /// <param name="pattern">The route pattern.</param>
 104    /// <param name="httpVerbs">The HTTP verb for the route.</param>
 105    /// <param name="scriptBlock">The script block to execute.</param>
 106    /// <param name="language">The scripting language to use (default is PowerShell).</param>
 107    /// <param name="requireSchemes">Optional array of authorization schemes required for the route.</param>
 108    /// <param name="arguments">Optional dictionary of arguments to pass to the script.</param>
 109    /// <returns>An IEndpointConventionBuilder for further configuration.</returns>
 110    public static IEndpointConventionBuilder AddMapRoute(this KestrunHost host, string pattern, HttpVerb httpVerbs, stri
 111                                     string[]? requireSchemes = null,
 112                                 Dictionary<string, object?>? arguments = null)
 113    {
 0114        arguments ??= [];
 0115        return host.AddMapRoute(new MapRouteOptions
 0116        {
 0117            Pattern = pattern,
 0118            HttpVerbs = [httpVerbs],
 0119            Code = scriptBlock,
 0120            Language = language,
 0121            RequireSchemes = requireSchemes ?? [], // No authorization by default
 0122            Arguments = arguments ?? [] // No additional arguments by default
 0123        });
 124    }
 125
 126    /// <summary>
 127    /// Adds a route to the KestrunHost that executes a script block for the specified HTTP verbs and pattern.
 128    /// </summary>
 129    /// <param name="host">The KestrunHost instance.</param>
 130    /// <param name="pattern">The route pattern.</param>
 131    /// <param name="httpVerbs">The HTTP verbs for the route.</param>
 132    /// <param name="scriptBlock">The script block to execute.</param>
 133    /// <param name="language">The scripting language to use (default is PowerShell).</param>
 134    /// <param name="requireSchemes">Optional array of authorization schemes required for the route.</param>
 135    /// <param name="arguments">Optional dictionary of arguments to pass to the script.</param>
 136    /// <returns>An IEndpointConventionBuilder for further configuration.</returns>
 137    public static IEndpointConventionBuilder AddMapRoute(this KestrunHost host, string pattern,
 138                                IEnumerable<HttpVerb> httpVerbs,
 139                                string scriptBlock,
 140                                ScriptLanguage language = ScriptLanguage.PowerShell,
 141                                string[]? requireSchemes = null,
 142                                 Dictionary<string, object?>? arguments = null)
 143    {
 0144        return host.AddMapRoute(new MapRouteOptions
 0145        {
 0146            Pattern = pattern,
 0147            HttpVerbs = [.. httpVerbs],
 0148            Code = scriptBlock,
 0149            Language = language,
 0150            RequireSchemes = requireSchemes ?? [], // No authorization by default
 0151            Arguments = arguments ?? [] // No additional arguments by default
 0152        });
 153    }
 154    /// <summary>
 155    /// Adds a route to the KestrunHost using the specified MapRouteOptions.
 156    /// </summary>
 157    /// <param name="host">The KestrunHost instance.</param>
 158    /// <param name="options">The MapRouteOptions containing route configuration.</param>
 159    /// <returns>An IEndpointConventionBuilder for further configuration.</returns>
 160    public static IEndpointConventionBuilder AddMapRoute(this KestrunHost host, MapRouteOptions options)
 161    {
 13162        if (host.HostLogger.IsEnabled(LogEventLevel.Debug))
 163        {
 12164            host.HostLogger.Debug("AddMapRoute called with pattern={Pattern}, language={Language}, method={Methods}", op
 165        }
 166
 167        // Ensure the WebApplication is initialized
 13168        if (host.App is null)
 169        {
 0170            throw new InvalidOperationException("WebApplication is not initialized. Call EnableConfiguration first.");
 171        }
 172
 173        // Validate options
 12174        if (string.IsNullOrWhiteSpace(options.Pattern))
 175        {
 0176            throw new ArgumentException("Pattern cannot be null or empty.", nameof(options.Pattern));
 177        }
 178
 179        // Validate code
 12180        if (string.IsNullOrWhiteSpace(options.Code))
 181        {
 0182            throw new ArgumentException("ScriptBlock cannot be null or empty.", nameof(options.Code));
 183        }
 184
 12185        var routeOptions = options;
 12186        if (options.HttpVerbs.Count == 0)
 187        {
 188            // Create a new RouteOptions with HttpVerbs set to [HttpVerb.Get]
 1189            routeOptions = options with { HttpVerbs = [HttpVerb.Get] };
 190        }
 191        try
 192        {
 12193            if (MapExists(host, routeOptions.Pattern, routeOptions.HttpVerbs))
 194            {
 2195                var msg = $"Route '{routeOptions.Pattern}' with method(s) {string.Join(", ", routeOptions.HttpVerbs)} al
 2196                if (options.ThrowOnDuplicate)
 197                {
 1198                    throw new InvalidOperationException(msg);
 199                }
 200
 1201                host.HostLogger.Warning(msg);
 1202                return null!;
 203            }
 204
 10205            var logger = host.HostLogger.ForContext("Route", routeOptions.Pattern);
 206            // compile once – return an HttpContext->Task delegate
 10207            var handler = options.Language switch
 10208            {
 10209
 0210                ScriptLanguage.PowerShell => PowerShellDelegateBuilder.Build(options.Code, logger, options.Arguments),
 10211                ScriptLanguage.CSharp => CSharpDelegateBuilder.Build(options.Code, logger, options.Arguments, options.Ex
 0212                ScriptLanguage.VBNet => VBNetDelegateBuilder.Build(options.Code, logger, options.Arguments, options.Extr
 0213                ScriptLanguage.FSharp => FSharpDelegateBuilder.Build(options.Code, logger), // F# scripting not implemen
 0214                ScriptLanguage.Python => PyDelegateBuilder.Build(options.Code, logger),
 0215                ScriptLanguage.JavaScript => JScriptDelegateBuilder.Build(options.Code, logger),
 10216
 0217                _ => throw new NotSupportedException(options.Language.ToString())
 10218            };
 21219            string[] methods = [.. routeOptions.HttpVerbs.Select(v => v.ToMethodString())];
 10220            var map = host.App.MapMethods(routeOptions.Pattern, methods, handler).WithLanguage(options.Language);
 10221            if (host.HostLogger.IsEnabled(LogEventLevel.Debug))
 222            {
 10223                host.HostLogger.Debug("Mapped route: {Pattern} with methods: {Methods}", routeOptions.Pattern, string.Jo
 224            }
 225
 10226            host.AddMapOptions(map, options);
 227
 43228            foreach (var method in routeOptions.HttpVerbs.Select(v => v.ToMethodString()))
 229            {
 9230                host._registeredRoutes[(routeOptions.Pattern, method)] = routeOptions;
 231            }
 232
 8233            host.HostLogger.Information("Added route: {Pattern} with methods: {Methods}", routeOptions.Pattern, string.J
 8234            return map;
 235            // Add to the feature queue for later processing
 236        }
 0237        catch (CompilationErrorException ex)
 238        {
 239            // Log the detailed compilation errors
 0240            host.HostLogger.Error($"Failed to add route '{options.Pattern}' due to compilation errors:");
 0241            host.HostLogger.Error(ex.GetDetailedErrorMessage());
 242
 243            // Re-throw with additional context
 0244            throw new InvalidOperationException(
 0245                $"Failed to compile {options.Language} script for route '{options.Pattern}'. {ex.GetErrors().Count()} er
 0246                ex);
 247        }
 3248        catch (Exception ex)
 249        {
 3250            throw new InvalidOperationException(
 3251                $"Failed to add route '{options.Pattern}' with method '{string.Join(", ", options.HttpVerbs)}' using {op
 3252                ex);
 253        }
 9254    }
 255
 256    /// <summary>
 257    /// Adds additional mapping options to the route.
 258    /// </summary>
 259    /// <param name="host">The Kestrun host.</param>
 260    /// <param name="map">The endpoint convention builder.</param>
 261    /// <param name="options">The mapping options.</param>
 262    private static void AddMapOptions(this KestrunHost host, IEndpointConventionBuilder map, MapRouteOptions options)
 263    {
 12264        ApplyShortCircuit(host, map, options);
 12265        ApplyAnonymous(host, map, options);
 12266        ApplyAntiforgery(host, map, options);
 12267        ApplyRateLimiting(host, map, options);
 12268        ApplyAuthSchemes(host, map, options);
 11269        ApplyPolicies(host, map, options);
 10270        ApplyCors(host, map, options);
 10271        ApplyOpenApiMetadata(host, map, options);
 10272    }
 273
 274    /// <summary>
 275    /// Applies short-circuiting behavior to the route.
 276    /// </summary>
 277    /// <param name="host">The Kestrun host.</param>
 278    /// <param name="map">The endpoint convention builder.</param>
 279    /// <param name="options">The mapping options.</param>
 280    private static void ApplyShortCircuit(KestrunHost host, IEndpointConventionBuilder map, MapRouteOptions options)
 281    {
 12282        if (!options.ShortCircuit)
 283        {
 12284            return;
 285        }
 286
 0287        host.HostLogger.Verbose("Short-circuiting route: {Pattern} with status code: {StatusCode}", options.Pattern, opt
 0288        if (options.ShortCircuitStatusCode is null)
 289        {
 0290            throw new ArgumentException("ShortCircuitStatusCode must be set if ShortCircuit is true.", nameof(options.Sh
 291        }
 292
 0293        _ = map.ShortCircuit(options.ShortCircuitStatusCode);
 0294    }
 295
 296    /// <summary>
 297    /// Applies anonymous access behavior to the route.
 298    /// </summary>
 299    /// <param name="host">The Kestrun host.</param>
 300    /// <param name="map">The endpoint convention builder.</param>
 301    /// <param name="options">The mapping options.</param>
 302    private static void ApplyAnonymous(KestrunHost host, IEndpointConventionBuilder map, MapRouteOptions options)
 303    {
 12304        if (options.AllowAnonymous)
 305        {
 0306            host.HostLogger.Verbose("Allowing anonymous access for route: {Pattern}", options.Pattern);
 0307            _ = map.AllowAnonymous();
 308        }
 309        else
 310        {
 12311            host.HostLogger.Debug("No anonymous access allowed for route: {Pattern}", options.Pattern);
 312        }
 12313    }
 314
 315    /// <summary>
 316    /// Applies anti-forgery behavior to the route.
 317    /// </summary>
 318    /// <param name="host">The Kestrun host.</param>
 319    /// <param name="map">The endpoint convention builder.</param>
 320    /// <param name="options">The mapping options.</param>
 321    private static void ApplyAntiforgery(KestrunHost host, IEndpointConventionBuilder map, MapRouteOptions options)
 322    {
 12323        if (!options.DisableAntiforgery)
 324        {
 12325            return;
 326        }
 327
 0328        _ = map.DisableAntiforgery();
 0329        host.HostLogger.Verbose("CSRF protection disabled for route: {Pattern}", options.Pattern);
 0330    }
 331
 332    /// <summary>
 333    /// Applies rate limiting behavior to the route.
 334    /// </summary>
 335    /// <param name="host">The Kestrun host.</param>
 336    /// <param name="map">The endpoint convention builder.</param>
 337    /// <param name="options">The mapping options.</param>
 338    private static void ApplyRateLimiting(KestrunHost host, IEndpointConventionBuilder map, MapRouteOptions options)
 339    {
 12340        if (string.IsNullOrWhiteSpace(options.RateLimitPolicyName))
 341        {
 12342            return;
 343        }
 344
 0345        host.HostLogger.Verbose("Applying rate limit policy: {RateLimitPolicyName} to route: {Pattern}", options.RateLim
 0346        _ = map.RequireRateLimiting(options.RateLimitPolicyName);
 0347    }
 348
 349    /// <summary>
 350    /// Applies authentication schemes to the route.
 351    /// </summary>
 352    /// <param name="host">The Kestrun host.</param>
 353    /// <param name="map">The endpoint convention builder.</param>
 354    /// <param name="options">The mapping options.</param>
 355    private static void ApplyAuthSchemes(KestrunHost host, IEndpointConventionBuilder map, MapRouteOptions options)
 356    {
 12357        if (options.RequireSchemes is { Length: > 0 })
 358        {
 7359            foreach (var schema in options.RequireSchemes)
 360            {
 2361                if (!host.HasAuthScheme(schema))
 362                {
 1363                    throw new ArgumentException($"Authentication scheme '{schema}' is not registered.", nameof(options.R
 364                }
 365            }
 1366            host.HostLogger.Verbose("Requiring authorization for route: {Pattern} with policies: {Policies}", options.Pa
 1367            _ = map.RequireAuthorization(new AuthorizeAttribute
 1368            {
 1369                AuthenticationSchemes = string.Join(',', options.RequireSchemes)
 1370            });
 371        }
 372        else
 373        {
 10374            host.HostLogger.Debug("No authorization required for route: {Pattern}", options.Pattern);
 375        }
 10376    }
 377
 378    /// <summary>
 379    /// Applies authorization policies to the route.
 380    /// </summary>
 381    /// <param name="host">The Kestrun host.</param>
 382    /// <param name="map">The endpoint convention builder.</param>
 383    /// <param name="options">The mapping options.</param>
 384    private static void ApplyPolicies(KestrunHost host, IEndpointConventionBuilder map, MapRouteOptions options)
 385    {
 11386        if (options.RequirePolicies is { Length: > 0 })
 387        {
 7388            foreach (var policy in options.RequirePolicies)
 389            {
 2390                if (!host.HasAuthPolicy(policy))
 391                {
 1392                    throw new ArgumentException($"Authorization policy '{policy}' is not registered.", nameof(options.Re
 393                }
 394            }
 1395            _ = map.RequireAuthorization(options.RequirePolicies);
 396        }
 397        else
 398        {
 9399            host.HostLogger.Debug("No authorization policies required for route: {Pattern}", options.Pattern);
 400        }
 9401    }
 402    /// <summary>
 403    /// Applies CORS behavior to the route.
 404    /// </summary>
 405    /// <param name="host">The Kestrun host.</param>
 406    /// <param name="map">The endpoint convention builder.</param>
 407    /// <param name="options">The mapping options.</param>
 408    private static void ApplyCors(KestrunHost host, IEndpointConventionBuilder map, MapRouteOptions options)
 409    {
 10410        if (!string.IsNullOrWhiteSpace(options.CorsPolicyName))
 411        {
 0412            host.HostLogger.Verbose("Applying CORS policy: {CorsPolicyName} to route: {Pattern}", options.CorsPolicyName
 0413            _ = map.RequireCors(options.CorsPolicyName);
 414        }
 415        else
 416        {
 10417            host.HostLogger.Debug("No CORS policy applied for route: {Pattern}", options.Pattern);
 418        }
 10419    }
 420
 421    /// <summary>
 422    /// Applies OpenAPI metadata to the route.
 423    /// </summary>
 424    /// <param name="host">The Kestrun host.</param>
 425    /// <param name="map">The endpoint convention builder.</param>
 426    /// <param name="options">The mapping options.</param>
 427    private static void ApplyOpenApiMetadata(KestrunHost host, IEndpointConventionBuilder map, MapRouteOptions options)
 428    {
 10429        if (!string.IsNullOrEmpty(options.OpenAPI.OperationId))
 430        {
 0431            host.HostLogger.Verbose("Adding OpenAPI metadata for route: {Pattern} with OperationId: {OperationId}", opti
 0432            _ = map.WithName(options.OpenAPI.OperationId);
 433        }
 434
 10435        if (!string.IsNullOrWhiteSpace(options.OpenAPI.Summary))
 436        {
 0437            host.HostLogger.Verbose("Adding OpenAPI summary for route: {Pattern} with Summary: {Summary}", options.Patte
 0438            _ = map.WithSummary(options.OpenAPI.Summary);
 439        }
 440
 10441        if (!string.IsNullOrWhiteSpace(options.OpenAPI.Description))
 442        {
 0443            host.HostLogger.Verbose("Adding OpenAPI description for route: {Pattern} with Description: {Description}", o
 0444            _ = map.WithDescription(options.OpenAPI.Description);
 445        }
 446
 10447        if (options.OpenAPI.Tags.Length > 0)
 448        {
 0449            host.HostLogger.Verbose("Adding OpenAPI tags for route: {Pattern} with Tags: {Tags}", options.Pattern, strin
 0450            _ = map.WithTags(options.OpenAPI.Tags);
 451        }
 452
 10453        if (!string.IsNullOrWhiteSpace(options.OpenAPI.GroupName))
 454        {
 0455            host.HostLogger.Verbose("Adding OpenAPI group name for route: {Pattern} with GroupName: {GroupName}", option
 0456            _ = map.WithGroupName(options.OpenAPI.GroupName);
 457        }
 10458    }
 459
 460    /// <summary>
 461    /// Adds an HTML template route to the KestrunHost for the specified pattern and HTML file path.
 462    /// </summary>
 463    /// <param name="host">The KestrunHost instance.</param>
 464    /// <param name="pattern">The route pattern.</param>
 465    /// <param name="htmlFilePath">The path to the HTML template file.</param>
 466    /// <param name="requireSchemes">Optional array of authorization schemes required for the route.</param>
 467    /// <returns>An IEndpointConventionBuilder for further configuration.</returns>
 468    public static IEndpointConventionBuilder AddHtmlTemplateRoute(this KestrunHost host, string pattern, string htmlFile
 469    {
 0470        return host.AddHtmlTemplateRoute(new MapRouteOptions
 0471        {
 0472            Pattern = pattern,
 0473            HttpVerbs = [HttpVerb.Get],
 0474            RequireSchemes = requireSchemes ?? [] // No authorization by default
 0475        }, htmlFilePath);
 476    }
 477
 478    /// <summary>
 479    /// Adds an HTML template route to the KestrunHost using the specified MapRouteOptions and HTML file path.
 480    /// </summary>
 481    /// <param name="host">The KestrunHost instance.</param>
 482    /// <param name="options">The MapRouteOptions containing route configuration.</param>
 483    /// <param name="htmlFilePath">The path to the HTML template file.</param>
 484    /// <returns>An IEndpointConventionBuilder for further configuration.</returns>
 485    public static IEndpointConventionBuilder AddHtmlTemplateRoute(this KestrunHost host, MapRouteOptions options, string
 486    {
 3487        if (host.HostLogger.IsEnabled(LogEventLevel.Debug))
 488        {
 2489            host.HostLogger.Debug("Adding HTML template route: {Pattern}", options.Pattern);
 490        }
 491
 3492        if (options.HttpVerbs.Count() != 0 &&
 3493            (options.HttpVerbs.Count() > 1 || options.HttpVerbs.First() != HttpVerb.Get))
 494        {
 1495            host.HostLogger.Error("HTML template routes only support GET requests. Provided HTTP verbs: {HttpVerbs}", st
 1496            throw new ArgumentException("HTML template routes only support GET requests.", nameof(options.HttpVerbs));
 497        }
 2498        if (string.IsNullOrWhiteSpace(htmlFilePath) || !File.Exists(htmlFilePath))
 499        {
 1500            host.HostLogger.Error("HTML file path is null, empty, or does not exist: {HtmlFilePath}", htmlFilePath);
 1501            throw new FileNotFoundException("HTML file not found.", htmlFilePath);
 502        }
 503
 1504        if (string.IsNullOrWhiteSpace(options.Pattern))
 505        {
 0506            host.HostLogger.Error("Pattern cannot be null or empty.");
 0507            throw new ArgumentException("Pattern cannot be null or empty.", nameof(options.Pattern));
 508        }
 509
 1510        var map = host.AddMapRoute(options.Pattern, HttpVerb.Get, async (ctx) =>
 1511          {
 1512              // ② Build your variables map
 0513              var vars = new Dictionary<string, object?>();
 0514              _ = VariablesMap.GetVariablesMap(ctx, ref vars);
 1515
 0516              await ctx.Response.WriteHtmlResponseFromFileAsync(htmlFilePath, vars, ctx.Response.StatusCode);
 1517          });
 518
 1519        AddMapOptions(host, map, options);
 1520        return map;
 521    }
 522
 523    /// <summary>
 524    /// Adds a static override route to the KestrunHost for the specified pattern and handler.
 525    /// This allows you to override static file serving with dynamic content.
 526    /// Call this method before adding static file components to ensure it takes precedence.
 527    /// </summary>
 528    /// <param name="host">The KestrunHost instance.</param>
 529    /// <param name="pattern">The route pattern to match.</param>
 530    /// <param name="handler">The handler to execute for the route.</param>
 531    /// <param name="requireSchemes">Optional array of authorization schemes required for the route.</param>
 532    /// <returns>The KestrunHost instance for method chaining.</returns>
 533    /// <remarks>
 534    /// This method allows you to override static file serving with dynamic content by providing a handler
 535    /// that will be executed for the specified route pattern.
 536    /// </remarks>
 537    public static KestrunHost AddStaticMapOverride(
 538        this KestrunHost host,
 539        string pattern,
 540        KestrunHandler handler,
 541        string[]? requireSchemes = null)         // same delegate you already use
 542    {
 0543        ArgumentException.ThrowIfNullOrWhiteSpace(pattern);
 544
 0545        return host.AddStaticMapOverride(new MapRouteOptions
 0546        {
 0547            Pattern = pattern,
 0548            HttpVerbs = [HttpVerb.Get], // GET-only
 0549            Language = ScriptLanguage.Native,
 0550            RequireSchemes = requireSchemes ?? [] // No authorization by default
 0551        }, handler);
 552    }
 553
 554    /// <summary>
 555    /// Adds a static override route to the KestrunHost using the specified MapRouteOptions and handler.
 556    /// This allows you to override static file serving with dynamic content by providing a handler
 557    /// that will be executed for the specified route pattern.
 558    /// </summary>
 559    /// <param name="host">The KestrunHost instance.</param>
 560    /// <param name="options">The MapRouteOptions containing route configuration.</param>
 561    /// <param name="handler">The handler to execute for the route.</param>
 562    /// <returns>The KestrunHost instance for method chaining.</returns>
 563    public static KestrunHost AddStaticMapOverride(
 564        this KestrunHost host,
 565        MapRouteOptions options,
 566        KestrunHandler handler)
 567    {
 0568        ArgumentException.ThrowIfNullOrWhiteSpace(options.Pattern);
 569
 570        // Check if the host is already configured
 0571        if (host.IsConfigured)
 572        {
 0573            throw new InvalidOperationException("Kestrun host is already configured.");
 574        }
 575
 576        // queue before static files – defer until WebApplication is being built
 0577        return host.Use(app =>
 0578        {
 0579            var endpoints = (IEndpointRouteBuilder)app;        // ← key change
 0580
 0581            // you said: static-file override should always be GET
 0582            var map = endpoints.MapMethods(
 0583                options.Pattern,
 0584                [HttpMethods.Get],
 0585                async ctx =>
 0586                {
 0587                    // wrap ASP.NET types in Kestrun abstractions
 0588                    var req = await KestrunRequest.NewRequest(ctx);
 0589                    var res = new KestrunResponse(req);
 0590                    var kestrun = new KestrunContext(req, res, ctx);
 0591
 0592                    await handler(kestrun);        // your logic
 0593                    await res.ApplyTo(ctx.Response);
 0594                });
 0595
 0596            // apply ShortCircuit / CORS / auth etc.
 0597            host.AddMapOptions(map, options);
 0598        });
 599    }
 600
 601    /// <summary>
 602    /// Adds a static override route to the KestrunHost for the specified pattern and script code.
 603    /// This allows you to override static file serving with dynamic content.
 604    /// /// Call this method before adding static file components to ensure it takes precedence.
 605    /// </summary>
 606    /// <param name="host">The KestrunHost instance.</param>
 607    /// <param name="pattern">The route pattern to match.</param>
 608    /// <param name="code">The script code to execute.</param>
 609    /// <param name="language">The scripting language to use.</param>
 610    /// <param name="requireSchemes">Optional array of authorization schemes required for the route.</param>
 611    /// <param name="arguments">Optional dictionary of arguments to pass to the script.</param>
 612    /// <returns>The KestrunHost instance for method chaining.</returns>
 613    /// <remarks>
 614    /// This method allows you to override static file serving with dynamic content by providing a script
 615    /// that will be executed for the specified route pattern.
 616    /// </remarks>
 617    public static KestrunHost AddStaticMapOverride(
 618    this KestrunHost host,
 619         string pattern,
 620         string code,
 621         ScriptLanguage language = ScriptLanguage.PowerShell,
 622         string[]? requireSchemes = null,
 623         Dictionary<string, object?>? arguments = null
 624           )
 625    {
 626        {
 1627            ArgumentException.ThrowIfNullOrWhiteSpace(pattern);
 1628            ArgumentException.ThrowIfNullOrWhiteSpace(code);
 629
 1630            var options = new MapRouteOptions
 1631            {
 1632                Pattern = pattern,
 1633                HttpVerbs = [HttpVerb.Get], // GET-only
 1634                Code = code,
 1635                Language = language,
 1636                RequireSchemes = requireSchemes ?? [], // No authorization by default
 1637                Arguments = arguments ?? [], // No additional arguments by default
 1638            };
 639            // queue before static files
 1640            return host.Use(app =>
 1641            {
 1642                _ = AddMapRoute(host, options);
 2643            });
 644        }
 645    }
 646
 647
 648    /// <summary>
 649    /// Adds a static override route to the KestrunHost using the specified MapRouteOptions.
 650    /// This allows you to override static file serving with dynamic content.
 651    /// Call this method before adding static file components to ensure it takes precedence.
 652    /// </summary>
 653    /// <param name="host">The KestrunHost instance.</param>
 654    /// <param name="options">The MapRouteOptions containing route configuration.</param>
 655    /// <returns>The KestrunHost instance for method chaining.</returns>
 656    public static KestrunHost AddStaticMapOverride(this KestrunHost host, MapRouteOptions options)
 657    {
 0658        ArgumentException.ThrowIfNullOrWhiteSpace(options.Pattern);
 0659        ArgumentException.ThrowIfNullOrWhiteSpace(options.Code);
 660        // queue before static files
 0661        return host.Use(app =>
 0662        {
 0663            _ = AddMapRoute(host, options);
 0664        });
 665    }
 666
 667    /// <summary>
 668    /// Checks if a route with the specified pattern and optional HTTP method exists in the KestrunHost.
 669    /// </summary>
 670    /// <param name="host">The KestrunHost instance.</param>
 671    /// <param name="pattern">The route pattern to check.</param>
 672    /// <param name="verbs">The optional HTTP method to check for the route.</param>
 673    /// <returns>True if the route exists; otherwise, false.</returns>
 674    public static bool MapExists(this KestrunHost host, string pattern, IEnumerable<HttpVerb> verbs)
 675    {
 34676        var methodSet = verbs.Select(v => v.ToMethodString()).ToHashSet(StringComparer.OrdinalIgnoreCase);
 16677        return host._registeredRoutes.Keys
 8678            .Where(k => string.Equals(k.Pattern, pattern, StringComparison.OrdinalIgnoreCase))
 24679            .Any(k => methodSet.Contains(k.Method));
 680    }
 681
 682    /// <summary>
 683    /// Checks if a route with the specified pattern and optional HTTP method exists in the KestrunHost.
 684    /// </summary>
 685    /// <param name="host">The KestrunHost instance.</param>
 686    /// <param name="pattern">The route pattern to check.</param>
 687    /// <param name="verb">The optional HTTP method to check for the route.</param>
 688    /// <returns>True if the route exists; otherwise, false.</returns>
 689    public static bool MapExists(this KestrunHost host, string pattern, HttpVerb verb) =>
 4690        host._registeredRoutes.ContainsKey((pattern, verb.ToMethodString()));
 691
 692
 693    /// <summary>
 694    /// Retrieves the <see cref="MapRouteOptions"/> associated with a given route pattern and HTTP verb, if registered.
 695    /// </summary>
 696    /// <param name="host">The <see cref="KestrunHost"/> instance to search for registered routes.</param>
 697    /// <param name="pattern">The route pattern to look up (e.g. <c>"/hello"</c>).</param>
 698    /// <param name="verb">The HTTP verb to match (e.g. <see cref="HttpVerb.Get"/>).</param>
 699    /// <returns>
 700    /// The <see cref="MapRouteOptions"/> instance for the specified route if found; otherwise, <c>null</c>.
 701    /// </returns>
 702    /// <remarks>
 703    /// This method checks the internal route registry and returns the route options if the pattern and verb
 704    /// combination was previously added via <c>AddMapRoute</c>.
 705    /// This lookup is case-insensitive for both the pattern and method.
 706    /// </remarks>
 707    /// <example>
 708    /// <code>
 709    /// var options = host.GetMapRouteOptions("/hello", HttpVerb.Get);
 710    /// if (options != null)
 711    /// {
 712    ///     Console.WriteLine($"Route language: {options.Language}");
 713    /// }
 714    /// </code>
 715    /// </example>
 716    public static MapRouteOptions? GetMapRouteOptions(this KestrunHost host, string pattern, HttpVerb verb)
 717    {
 3718        return host._registeredRoutes.TryGetValue((pattern, verb.ToMethodString()), out var options)
 3719            ? options
 3720            : null;
 721    }
 722}

Methods/Properties

AddMapRoute(Kestrun.Hosting.KestrunHost,System.String,Kestrun.Utilities.HttpVerb,Kestrun.Hosting.KestrunHostMapExtensions/KestrunHandler,System.String[])
AddMapRoute(Kestrun.Hosting.KestrunHost,System.String,System.Collections.Generic.IEnumerable`1<Kestrun.Utilities.HttpVerb>,Kestrun.Hosting.KestrunHostMapExtensions/KestrunHandler,System.String[])
AddMapRoute(Kestrun.Hosting.KestrunHost,Kestrun.Hosting.Options.MapRouteOptions,Kestrun.Hosting.KestrunHostMapExtensions/KestrunHandler)
AddMapRoute(Kestrun.Hosting.KestrunHost,System.String,Kestrun.Utilities.HttpVerb,System.String,Kestrun.Scripting.ScriptLanguage,System.String[],System.Collections.Generic.Dictionary`2<System.String,System.Object>)
AddMapRoute(Kestrun.Hosting.KestrunHost,System.String,System.Collections.Generic.IEnumerable`1<Kestrun.Utilities.HttpVerb>,System.String,Kestrun.Scripting.ScriptLanguage,System.String[],System.Collections.Generic.Dictionary`2<System.String,System.Object>)
AddMapRoute(Kestrun.Hosting.KestrunHost,Kestrun.Hosting.Options.MapRouteOptions)
AddMapOptions(Kestrun.Hosting.KestrunHost,Microsoft.AspNetCore.Builder.IEndpointConventionBuilder,Kestrun.Hosting.Options.MapRouteOptions)
ApplyShortCircuit(Kestrun.Hosting.KestrunHost,Microsoft.AspNetCore.Builder.IEndpointConventionBuilder,Kestrun.Hosting.Options.MapRouteOptions)
ApplyAnonymous(Kestrun.Hosting.KestrunHost,Microsoft.AspNetCore.Builder.IEndpointConventionBuilder,Kestrun.Hosting.Options.MapRouteOptions)
ApplyAntiforgery(Kestrun.Hosting.KestrunHost,Microsoft.AspNetCore.Builder.IEndpointConventionBuilder,Kestrun.Hosting.Options.MapRouteOptions)
ApplyRateLimiting(Kestrun.Hosting.KestrunHost,Microsoft.AspNetCore.Builder.IEndpointConventionBuilder,Kestrun.Hosting.Options.MapRouteOptions)
ApplyAuthSchemes(Kestrun.Hosting.KestrunHost,Microsoft.AspNetCore.Builder.IEndpointConventionBuilder,Kestrun.Hosting.Options.MapRouteOptions)
ApplyPolicies(Kestrun.Hosting.KestrunHost,Microsoft.AspNetCore.Builder.IEndpointConventionBuilder,Kestrun.Hosting.Options.MapRouteOptions)
ApplyCors(Kestrun.Hosting.KestrunHost,Microsoft.AspNetCore.Builder.IEndpointConventionBuilder,Kestrun.Hosting.Options.MapRouteOptions)
ApplyOpenApiMetadata(Kestrun.Hosting.KestrunHost,Microsoft.AspNetCore.Builder.IEndpointConventionBuilder,Kestrun.Hosting.Options.MapRouteOptions)
AddHtmlTemplateRoute(Kestrun.Hosting.KestrunHost,System.String,System.String,System.String[])
AddHtmlTemplateRoute(Kestrun.Hosting.KestrunHost,Kestrun.Hosting.Options.MapRouteOptions,System.String)
AddStaticMapOverride(Kestrun.Hosting.KestrunHost,System.String,Kestrun.Hosting.KestrunHostMapExtensions/KestrunHandler,System.String[])
AddStaticMapOverride(Kestrun.Hosting.KestrunHost,Kestrun.Hosting.Options.MapRouteOptions,Kestrun.Hosting.KestrunHostMapExtensions/KestrunHandler)
AddStaticMapOverride(Kestrun.Hosting.KestrunHost,System.String,System.String,Kestrun.Scripting.ScriptLanguage,System.String[],System.Collections.Generic.Dictionary`2<System.String,System.Object>)
AddStaticMapOverride(Kestrun.Hosting.KestrunHost,Kestrun.Hosting.Options.MapRouteOptions)
MapExists(Kestrun.Hosting.KestrunHost,System.String,System.Collections.Generic.IEnumerable`1<Kestrun.Utilities.HttpVerb>)
MapExists(Kestrun.Hosting.KestrunHost,System.String,Kestrun.Utilities.HttpVerb)
GetMapRouteOptions(Kestrun.Hosting.KestrunHost,System.String,Kestrun.Utilities.HttpVerb)