Skip to content

Commit f078428

Browse files
committed
SSR streaming not started with custom router.
1 parent 82d75a0 commit f078428

File tree

11 files changed

+256
-26
lines changed

11 files changed

+256
-26
lines changed

src/Components/Components/src/NavigationManager.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ public event EventHandler<NotFoundEventArgs> OnNotFound
6161
// The URI. Always represented an absolute URI.
6262
private string? _uri;
6363
private bool _isInitialized;
64-
internal string NotFoundPageRoute { get; set; } = string.Empty;
64+
private readonly NotFoundEventArgs _notFoundEventArgs = new();
6565

6666
/// <summary>
6767
/// Gets or sets the current base URI. The <see cref="BaseUri" /> is always represented as an absolute URI in string form with trailing slash.
@@ -211,7 +211,7 @@ private void NotFoundCore()
211211
}
212212
else
213213
{
214-
_notFound.Invoke(this, new NotFoundEventArgs(NotFoundPageRoute));
214+
_notFound.Invoke(this, _notFoundEventArgs);
215215
}
216216
}
217217

src/Components/Components/src/PublicAPI.Unshipped.txt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@ Microsoft.AspNetCore.Components.NavigationManager.OnNotFound -> System.EventHand
88
Microsoft.AspNetCore.Components.NavigationManager.NotFound() -> void
99
Microsoft.AspNetCore.Components.Routing.IHostEnvironmentNavigationManager.Initialize(string! baseUri, string! uri, System.Func<string!, System.Threading.Tasks.Task!>! onNavigateTo) -> void
1010
Microsoft.AspNetCore.Components.Routing.NotFoundEventArgs
11-
Microsoft.AspNetCore.Components.Routing.NotFoundEventArgs.NotFoundEventArgs(string! url) -> void
12-
Microsoft.AspNetCore.Components.Routing.NotFoundEventArgs.Path.get -> string!
11+
Microsoft.AspNetCore.Components.Routing.NotFoundEventArgs.NotFoundEventArgs() -> void
12+
Microsoft.AspNetCore.Components.Routing.NotFoundEventArgs.Path.get -> string?
13+
Microsoft.AspNetCore.Components.Routing.NotFoundEventArgs.Path.set -> void
1314
Microsoft.AspNetCore.Components.Infrastructure.ComponentStatePersistenceManager.ComponentStatePersistenceManager(Microsoft.Extensions.Logging.ILogger<Microsoft.AspNetCore.Components.Infrastructure.ComponentStatePersistenceManager!>! logger, System.IServiceProvider! serviceProvider) -> void
1415
Microsoft.AspNetCore.Components.Infrastructure.ComponentStatePersistenceManager.SetPlatformRenderMode(Microsoft.AspNetCore.Components.IComponentRenderMode! renderMode) -> void
1516
Microsoft.AspNetCore.Components.Infrastructure.RegisterPersistentComponentStateServiceCollectionExtensions

src/Components/Components/src/Routing/NotFoundEventArgs.cs

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,7 @@ namespace Microsoft.AspNetCore.Components.Routing;
99
public sealed class NotFoundEventArgs : EventArgs
1010
{
1111
/// <summary>
12-
/// Gets the path of NotFoundPage.
12+
/// Gets the path of NotFoundPage. If the path is set, it indicates that the router has handled the rendering of the NotFound contents.
1313
/// </summary>
14-
public string Path { get; }
15-
16-
/// <summary>
17-
/// Initializes a new instance of <see cref="NotFoundEventArgs" />.
18-
/// </summary>
19-
public NotFoundEventArgs(string url)
20-
{
21-
Path = url;
22-
}
23-
14+
public string? Path { get; set; }
2415
}

src/Components/Components/src/Routing/Router.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ static readonly IReadOnlyDictionary<string, object> _emptyParametersDictionary
2929
string _locationAbsolute;
3030
bool _navigationInterceptionEnabled;
3131
ILogger<Router> _logger;
32+
string _notFoundPageRoute;
3233

3334
private string _updateScrollPositionForHashLastLocation;
3435
private bool _updateScrollPositionForHash;
@@ -159,7 +160,7 @@ public async Task SetParametersAsync(ParameterView parameters)
159160
var routeAttribute = (RouteAttribute)routeAttributes[0];
160161
if (routeAttribute.Template != null)
161162
{
162-
NavigationManager.NotFoundPageRoute = routeAttribute.Template;
163+
_notFoundPageRoute = routeAttribute.Template;
163164
}
164165
}
165166

@@ -381,12 +382,14 @@ private void OnLocationChanged(object sender, LocationChangedEventArgs args)
381382
}
382383
}
383384

384-
private void OnNotFound(object sender, EventArgs args)
385+
private void OnNotFound(object sender, NotFoundEventArgs args)
385386
{
386387
if (_renderHandle.IsInitialized)
387388
{
388389
Log.DisplayingNotFound(_logger);
389390
RenderNotFound();
391+
// setting the path signals to the endpoint renderer that router handled rendering
392+
args.Path = _notFoundPageRoute ?? "not-found-handled-by-router";
390393
}
391394
}
392395

src/Components/Endpoints/src/RazorComponentEndpointInvoker.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,17 @@ await _renderer.InitializeStandardComponentServicesAsync(
168168
componentStateHtmlContent.WriteTo(bufferWriter, HtmlEncoder.Default);
169169
}
170170

171+
if (context.Response.StatusCode == StatusCodes.Status404NotFound &&
172+
!isReExecuted &&
173+
string.IsNullOrEmpty(_renderer.NotFoundEventArgs?.Path))
174+
{
175+
// Router did not handle the NotFound event, otherwise this would not be empty.
176+
// Don't flush the response if we have an unhandled 404 rendering
177+
// This will allow the StatusCodePages middleware to re-execute the request
178+
context.Response.ContentType = null;
179+
return;
180+
}
181+
171182
// Invoke FlushAsync to ensure any buffered content is asynchronously written to the underlying
172183
// response asynchronously. In the absence of this line, the buffer gets synchronously written to the
173184
// response as part of the Dispose which has a perf impact.

src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.EventDispatch.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ internal async Task SetNotFoundResponseAsync(string baseUri, NotFoundEventArgs a
109109

110110
private string GetNotFoundUrl(string baseUri, NotFoundEventArgs args)
111111
{
112-
string path = args.Path;
112+
string? path = args.Path;
113113
if (string.IsNullOrEmpty(path))
114114
{
115115
var pathFormat = _httpContext.Items[nameof(StatusCodePagesOptions)] as string;

src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ public EndpointHtmlRenderer(IServiceProvider serviceProvider, ILoggerFactory log
6363
}
6464

6565
internal HttpContext? HttpContext => _httpContext;
66+
internal NotFoundEventArgs? NotFoundEventArgs { get; set; }
6667

6768
internal void SetHttpContext(HttpContext httpContext)
6869
{
@@ -87,6 +88,7 @@ internal async Task InitializeStandardComponentServicesAsync(
8788

8889
navigationManager?.OnNotFound += (sender, args) =>
8990
{
91+
NotFoundEventArgs = args;
9092
_ = GetErrorHandledTask(SetNotFoundResponseAsync(navigationManager.BaseUri, args));
9193
};
9294

src/Components/Endpoints/test/EndpointHtmlRendererTest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -944,7 +944,7 @@ public async Task Renderer_WhenNoNotFoundPathProvided_Throws()
944944
httpContext.Items[nameof(StatusCodePagesOptions)] = null; // simulate missing re-execution route
945945

946946
var exception = await Assert.ThrowsAsync<InvalidOperationException>(async () =>
947-
await renderer.SetNotFoundResponseAsync(httpContext, new NotFoundEventArgs(""))
947+
await renderer.SetNotFoundResponseAsync(httpContext, new NotFoundEventArgs())
948948
);
949949
string expectedError = "The NotFoundPage route must be specified or re-execution middleware has to be set to render NotFoundPage when the response has started.";
950950

src/Components/test/E2ETest/ServerRenderingTests/NoInteractivityTest.cs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,16 @@ public void BrowserNavigationToNotExistingPathReExecutesTo404(bool streaming)
117117
private void AssertReExecutionPageRendered() =>
118118
Browser.Equal("Welcome On Page Re-executed After Not Found Event", () => Browser.Exists(By.Id("test-info")).Text);
119119

120+
private void AssertBrowserDefaultNotFoundViewRendered()
121+
{
122+
var mainMessage = Browser.FindElement(By.Id("main-message"));
123+
124+
Browser.True(
125+
() => mainMessage.FindElement(By.CssSelector("p")).Text
126+
.Contains("No webpage was found for the web address:", StringComparison.OrdinalIgnoreCase)
127+
);
128+
}
129+
120130
private void AssertNotFoundPageRendered()
121131
{
122132
Browser.Equal("Welcome On Custom Not Found Page", () => Browser.FindElement(By.Id("test-info")).Text);
@@ -152,6 +162,30 @@ public void NotFoundSetOnInitialization_ResponseNotStarted_SSR(bool hasReExecuti
152162
AssertUrlNotChanged(testUrl);
153163
}
154164

165+
[Theory]
166+
[InlineData(true)]
167+
[InlineData(false)]
168+
// our custom router does not support NotFoundPage nor NotFound fragment to simulate most probable custom router behavior
169+
public void NotFoundSetOnInitialization_ResponseNotStarted_CustomRouter_SSR(bool hasReExecutionMiddleware)
170+
{
171+
string reexecution = hasReExecutionMiddleware ? "/reexecution" : "";
172+
string testUrl = $"{ServerPathBase}{reexecution}/set-not-found-ssr?useCustomRouter=true";
173+
Navigate(testUrl);
174+
175+
if (hasReExecutionMiddleware)
176+
{
177+
AssertReExecutionPageRendered();
178+
}
179+
else
180+
{
181+
// Apps that don't support re-execution and don't have blazor's router,
182+
// cannot render custom NotFound contents.
183+
// The browser will display default 404 page.
184+
AssertBrowserDefaultNotFoundViewRendered();
185+
}
186+
AssertUrlNotChanged(testUrl);
187+
}
188+
155189
[Theory]
156190
[InlineData(true, true)]
157191
[InlineData(true, false)]

src/Components/test/testassets/Components.TestServer/RazorComponents/App.razor

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
@using Components.TestServer.RazorComponents.Pages.Forms
22
@using Components.WasmMinimal.Pages.NotFound
33
@using TestContentPackage.NotFound
4+
@using Components.TestServer.RazorComponents
45

56
@code {
67
[Parameter]
78
[SupplyParameterFromQuery(Name = "useCustomNotFoundPage")]
89
public string? UseCustomNotFoundPage { get; set; }
910

11+
[Parameter]
12+
[SupplyParameterFromQuery(Name = "useCustomRouter")]
13+
public string? UseCustomRouter { get; set; }
14+
1015
private Type? NotFoundPageType { get; set; }
1116

1217
protected override void OnParametersSet()
@@ -30,13 +35,25 @@
3035
<HeadOutlet />
3136
</head>
3237
<body>
33-
<Router AppAssembly="@typeof(App).Assembly" AdditionalAssemblies="new[] { typeof(TestContentPackage.NotFound.NotFoundPage).Assembly }" NotFoundPage="NotFoundPageType">
34-
<Found Context="routeData">
35-
<RouteView RouteData="@routeData" />
36-
<FocusOnNavigate RouteData="@routeData" Selector="[data-focus-on-navigate]" />
37-
</Found>
38-
<NotFound><p id="not-found-fragment">There's nothing here</p></NotFound>
39-
</Router>
38+
@if(string.Equals(UseCustomRouter, "true", StringComparison.OrdinalIgnoreCase))
39+
{
40+
<CustomRouter AppAssembly="@typeof(App).Assembly">
41+
<Found Context="routeData">
42+
<RouteView RouteData="@routeData" />
43+
<FocusOnNavigate RouteData="@routeData" Selector="[data-focus-on-navigate]" />
44+
</Found>
45+
</CustomRouter>
46+
}
47+
else
48+
{
49+
<Router AppAssembly="@typeof(App).Assembly" AdditionalAssemblies="new[] { typeof(TestContentPackage.NotFound.NotFoundPage).Assembly }" NotFoundPage="NotFoundPageType">
50+
<Found Context="routeData">
51+
<RouteView RouteData="@routeData" />
52+
<FocusOnNavigate RouteData="@routeData" Selector="[data-focus-on-navigate]" />
53+
</Found>
54+
<NotFound><p id="not-found-fragment">There's nothing here</p></NotFound>
55+
</Router>
56+
}
4057
<script>
4158
// This script must come before blazor.web.js to test that
4259
// the framework does the right thing when an element is already focused.

0 commit comments

Comments
 (0)