Skip to content

Add AdditionalEndpointDefinitions to DefineStaticWebAssetEndpoints#53593

Merged
javiercn merged 2 commits intomainfrom
javiercn/fallback-endpoints
Apr 5, 2026
Merged

Add AdditionalEndpointDefinitions to DefineStaticWebAssetEndpoints#53593
javiercn merged 2 commits intomainfrom
javiercn/fallback-endpoints

Conversation

@javiercn
Copy link
Copy Markdown
Member

@javiercn javiercn commented Mar 25, 2026

  • Understand reviewer comments and plan changes
  • Fix StaticWebAssetGlobMatcher to track StemEndIndex and expose CapturedStem in GlobMatch
    • Added StemEndIndex to MatchState struct
    • Set StemEndIndex in MatchRecursiveWildCard alongside StemStartIndex
    • Propagate StemEndIndex through all NextSegment, NextStage, and NextExtension calls
    • Added ComputeCapturedStem method (returns only the **-captured portion)
    • Added CapturedStem property to GlobMatch
  • Simplify DefineStaticWebAssetEndpoints
    • Removed manual suffix computation from pattern string (was parsing **/index.html → suffix index.html)
    • Removed Suffix from AdditionalEndpointDefinition
    • Use match.CapturedStem from matcher instead of suffix stripping logic
    • Moved MatchContext creation outside CreateAdditionalEndpoints, passed as parameter from CreateAnAddEndpoints
  • Test improvements
    • Combined DefaultDocument_CreatesEndpointWithoutSuffix and DefaultDocument_NestedPath_CapturesStem into a single Theory with InlineData for root-level and nested paths
    • Added route assertions to all test cases
    • Added assertion for "/" route in BothRules test
  • Validate changes with tests (173 tests pass)

⚡ Quickly spin up Copilot coding agent tasks from anywhere on your macOS or Windows machine with Raycast.

@github-actions github-actions Bot added the Area-AspNetCore RazorSDK, BlazorWebAssemblySDK, StaticWebAssetsSDK label Mar 25, 2026
@dotnet-policy-service
Copy link
Copy Markdown
Contributor

Thanks for your PR, @@javiercn.
To learn about the PR process and branching schedule of this repo, please take a look at the SDK PR Guide.

Comment on lines +125 to +139
// Compute the suffix after the recursive wildcard so we can strip it
// from the matched route to get the portion captured by **.
// For "**/index.html" the suffix is "index.html".
// For "index.html" (no **) the suffix is empty.
var suffix = "";
var rwcIndex = pattern.IndexOf("**", StringComparison.Ordinal);
if (rwcIndex >= 0)
{
var afterRwc = pattern.AsSpan().Slice(rwcIndex + 2);
if (afterRwc.Length > 0 && (afterRwc[0] == '/' || afterRwc[0] == '\\'))
{
afterRwc = afterRwc.Slice(1);
}
suffix = afterRwc.ToString();
}
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a bug that should be fixed in the matcher instead?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 535c050. Added StemEndIndex tracking to MatchState in the glob matcher and a new ComputeCapturedStem method that returns only the portion matched exclusively by ** (without trailing literal segments). This is exposed as GlobMatch.CapturedStem. The suffix computation and string-manipulation workaround in DefineStaticWebAssetEndpoints have been removed.


private void CreateAdditionalEndpoints(StaticWebAssetEndpoint sourceEndpoint)
{
var matchContext = StaticWebAssetGlobMatcher.CreateMatchContext();
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should move outside of this method and the enclosing loop and be passed as a parameter

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 535c050. CreateMatchContext() is now no longer called inside CreateAdditionalEndpoints — the method accepts a StaticWebAssetGlobMatcher.MatchContext parameter and reuses the one created in Process and passed through CreateAnAddEndpoints.

Comment on lines +277 to +281
// The glob matcher's Stem captures everything from the ** start to the
// end of the path, including the literal suffix of the pattern.
// For example, **/index.html matching admin/index.html produces
// stem="admin/index.html". We need to strip the suffix ("index.html")
// to get the actual ** captured portion ("admin").
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check if this is a bug or if it's some additional value we could compute as part of a match.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was missing functionality in the matcher. The matcher's Stem included trailing literal segments (e.g., for **/index.html matching admin/index.html, Stem was admin/index.html). Fixed in 535c050 by adding CapturedStem to GlobMatch which returns only what ** captured (admin). CreateAdditionalEndpoints now uses match.CapturedStem directly instead of manual suffix stripping.

Comment on lines +705 to +707
var additional = endpoints.First(e => e.Route != "index.html");
additional.AssetFile.Should().Be(original.AssetFile);
additional.ResponseHeaders.Should().BeEquivalentTo(original.ResponseHeaders);
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

assert on the route too

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 535c050. Both DefaultDocument tests are now merged into a single Theory with [InlineData("index.html", "index.html", "/")] and [InlineData("admin/index.html", "admin/index.html", "admin")], with route assertions on the additional endpoint in both cases.

Comment on lines +748 to +751
// The stem is "admin", replacement is empty -> new route is "admin"
var additional = endpoints.First(e => e.Route != "admin/index.html");
additional.Route.Should().Be("admin");
additional.AssetFile.Should().Be(original.AssetFile);
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assert on the route, combine it with the test above into a theory

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 535c050. The two DefaultDocument tests are now combined into a single Theory (AdditionalEndpointDefinitions_DefaultDocument_CreatesEndpointWithCapturedStem) with both root-level and nested path cases. Both cases assert on the route.

Copy link
Copy Markdown
Member Author

@javiercn javiercn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot address some of the feedback. Your goal is to reduce the boilerplate on the PR, while keeping tests readable. Avoid doing one-off work to accommodate for bugs or limitations on the matcher. Determine if there is a bug that needs to be fixed or missing functionality and add it there.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends Static Web Assets endpoint generation to support configurable “additional endpoints” (e.g., default document and SPA fallback) by introducing AdditionalEndpointDefinitions into DefineStaticWebAssetEndpoints, enhancing glob matching to expose the **-captured stem, and flowing a new optional endpoint Order through manifests and tasks.

Changes:

  • Add MSBuild-driven AdditionalEndpointDefinitions to generate extra routes (default document "/" and SPA fallback "{**fallback:nonfile}") during endpoint creation.
  • Enhance glob matching with CapturedStem so patterns like **/index.html can map to routes like admin (instead of admin/index.html).
  • Introduce optional StaticWebAssetEndpoint.Order and propagate it through manifest comparison and compression negotiation; update integration baseline/tests.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
test/Microsoft.NET.Sdk.StaticWebAssets.Tests/StaticWebAssetsBaselines/Build_DefaultDocumentAndSpaFallback_CreatesAdditionalEndpoints.Build.staticwebassets.json Adds/updates build-manifest baseline to include generated additional endpoints and fallback order.
test/Microsoft.NET.Sdk.StaticWebAssets.Tests/StaticWebAssetsBaselineComparer.cs Compares endpoint Order when validating baselines.
test/Microsoft.NET.Sdk.StaticWebAssets.Tests/StaticWebAssets/DefineStaticWebAssetEndpointsTest.cs Adds unit tests for additional endpoint definitions (default document + SPA fallback).
test/Microsoft.NET.Sdk.StaticWebAssets.Tests/StaticWebAssetEndpointsIntegrationTest.cs Adds integration coverage verifying additional endpoints appear in the build manifest when enabled.
src/StaticWebAssetsSdk/Tasks/Utils/Globbing/StaticWebAssetGlobMatcher.cs Tracks recursive-wildcard capture boundaries and computes CapturedStem.
src/StaticWebAssetsSdk/Tasks/Utils/Globbing/GlobMatch.cs Adds CapturedStem to glob match results.
src/StaticWebAssetsSdk/Tasks/DefineStaticWebAssetEndpoints.cs Adds AdditionalEndpointDefinitions parsing + generation of extra endpoints.
src/StaticWebAssetsSdk/Tasks/Data/StaticWebAssetEndpoint.cs Adds optional Order metadata and includes it in canonical metadata cloning.
src/StaticWebAssetsSdk/Tasks/ApplyCompressionNegotiation.cs Propagates Order when creating negotiated compressed endpoints.
src/StaticWebAssetsSdk/Targets/Microsoft.NET.Sdk.StaticWebAssets.targets Adds MSBuild toggles and item definitions for default document + SPA fallback endpoint generation; passes definitions into the task.
Comments suppressed due to low confidence (1)

src/StaticWebAssetsSdk/Tasks/Utils/Globbing/StaticWebAssetGlobMatcher.cs:523

  • MatchState.NextComplex() does not propagate StemStartIndex/StemEndIndex. When a glob includes ** and also uses complex segments, advancing to the next complex segment will reset the stem tracking to the default (-1), which can make GlobMatch.CapturedStem incorrect/empty. Consider copying both stem indices into the state returned by NextComplex() (similar to NextSegment / NextStage / NextExtension).
        internal readonly MatchState NextExtension(int extensionIndex) => new(Node, MatchStage.Extension, SegmentIndex, extensionIndex, ComplexSegmentIndex)
        {
            StemStartIndex = StemStartIndex,
            StemEndIndex = StemEndIndex
        };

        internal readonly MatchState NextComplex() => new(Node, MatchStage.Complex, SegmentIndex, ExtensionSegmentIndex, ComplexSegmentIndex + 1);

Comment on lines +50 to +71
// Optional order for the endpoint in the routing table.
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string Order
{
get
{
if (!_orderRead && _order == null && _originalItem != null)
{
var value = _originalItem.GetMetadata(nameof(Order));
_order = string.IsNullOrEmpty(value) ? null : value;
_orderRead = true;
}
return _order;
}

set
{
_order = value;
_orderRead = true;
_modified = true;
}
}
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that StaticWebAssetEndpoint has an Order property, Equals(...) / GetHashCode() / CompareTo(...) still ignore it. This can cause endpoints that differ only by order to be treated as equal (e.g., baseline comparisons use Except/HashSet on endpoints), potentially hiding real differences or producing incorrect set operations. Include Order in the equality/hash/sort logic (and keep RouteAndAssetComparer as-is if route+asset uniqueness is still desired).

Copilot uses AI. Check for mistakes.
javiercn and others added 2 commits March 28, 2026 16:11
Add support for defining additional endpoints based on pattern matching
against existing endpoint routes. This enables:

- Default document support: **/index.html pattern strips the suffix,
  creating endpoints like / and /admin for index.html files.
- SPA fallback support: index.html pattern creates a {**fallback:nonfile}
  catch-all endpoint with configurable Order (int.MaxValue for fallbacks).

Changes:
- Add Order property to StaticWebAssetEndpoint with JSON conditional
  serialization (omitted when null).
- Add AdditionalEndpointDefinitions ITaskItem[] parameter to
  DefineStaticWebAssetEndpoints task with Pattern, Replacement, and
  Order metadata.
- Add StaticWebAssetDefaultDocumentEnabled and
  StaticWebAssetSpaFallbackEnabled MSBuild properties.
- Fix ApplyCompressionNegotiation to copy Order when creating compressed
  endpoint variants.
- Add Order to endpoint canonical metadata list.
- Add unit tests and integration test with baseline.
…neStaticWebAssetEndpoints, improve tests

Co-authored-by: javiercn <6995051+javiercn@users.noreply.github.com>
Agent-Logs-Url: https://github.com/dotnet/sdk/sessions/be827c71-2f55-48ec-80f6-cdaf2841582d
@javiercn javiercn force-pushed the javiercn/fallback-endpoints branch from 535c050 to df31db5 Compare March 28, 2026 15:11
@javiercn javiercn requested a review from a team March 30, 2026 10:16
Copy link
Copy Markdown
Member

@maraf maraf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me!

@javiercn javiercn merged commit bbf4a33 into main Apr 5, 2026
24 checks passed
@javiercn javiercn deleted the javiercn/fallback-endpoints branch April 5, 2026 15:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area-AspNetCore RazorSDK, BlazorWebAssemblySDK, StaticWebAssetsSDK

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants