Skip to content

Commit 30b31d7

Browse files
authored
Account for Layout ordering in Blazor (#15001)
The server requires that clients send descriptors in sequence. Since MVC executes Layouts in an inside-out manner, modify the client to explicitly order descriptors Fixes #14474
1 parent 579f30f commit 30b31d7

File tree

8 files changed

+136
-69
lines changed

8 files changed

+136
-69
lines changed

src/Components/Samples/BlazorServerApp/appsettings.Development.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"LogLevel": {
44
"Default": "Debug",
55
"System": "Information",
6-
"Microsoft": "Information"
6+
"Microsoft": "Debug"
77
}
88
}
99
}

src/Components/Web.JS/dist/Release/blazor.server.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Components/Web.JS/src/Platform/Circuits/CircuitManager.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ export function discoverComponents(document: Document): ComponentDescriptor[] {
101101
discoveredComponents.push(entry);
102102
}
103103

104-
return discoveredComponents;
104+
return discoveredComponents.sort((a, b) => a.sequence - b.sequence);
105105
}
106106

107107

src/Components/test/E2ETest/ServerExecutionTests/MultipleRootComponentsTest.cs

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,13 @@
1616

1717
namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
1818
{
19-
public class MultipleComponentsTest : ServerTestBase<BasicTestAppServerSiteFixture<PrerenderedStartup>>
19+
public class MultipleComponentsTest : ServerTestBase<BasicTestAppServerSiteFixture<MultipleComponents>>
2020
{
2121
private const string MarkerPattern = ".*?<!--Blazor:(.*?)-->.*?";
2222

2323
public MultipleComponentsTest(
2424
BrowserFixture browserFixture,
25-
BasicTestAppServerSiteFixture<PrerenderedStartup> serverFixture,
25+
BasicTestAppServerSiteFixture<MultipleComponents> serverFixture,
2626
ITestOutputHelper output)
2727
: base(browserFixture, serverFixture, output)
2828
{
@@ -46,7 +46,7 @@ public override async Task InitializeAsync()
4646
[Fact]
4747
public void DoesNotStartMultipleConnections()
4848
{
49-
Navigate("/prerendered/multiple-components");
49+
Navigate("/multiple-components");
5050

5151
BeginInteractivity();
5252
Browser.Exists(By.CssSelector("h3.interactive"));
@@ -60,17 +60,18 @@ public void DoesNotStartMultipleConnections()
6060
});
6161
}
6262

63-
6463
[Fact]
6564
public void CanRenderMultipleRootComponents()
6665
{
67-
Navigate("/prerendered/multiple-components");
66+
Navigate("/multiple-components");
6867

6968
var greets = Browser.FindElements(By.CssSelector(".greet-wrapper .greet")).Select(e => e.Text).ToArray();
7069

71-
Assert.Equal(5, greets.Length); // 1 statically rendered + 3 prerendered + 1 server prerendered
70+
Assert.Equal(7, greets.Length); // 1 statically rendered + 5 prerendered + 1 server prerendered
71+
Assert.DoesNotContain("Hello Red fish", greets);
7272
Assert.Single(greets, "Hello John");
7373
Assert.Single(greets, "Hello Abraham");
74+
Assert.Equal(2, greets.Where(g => g == "Hello Blue fish").Count());
7475
Assert.Equal(3, greets.Where(g => string.Equals("Hello", g)).Count()); // 3 server prerendered without parameters
7576
var content = Browser.FindElement(By.Id("test-container")).GetAttribute("innerHTML");
7677
var markers = ReadMarkers(content);
@@ -86,7 +87,11 @@ public void CanRenderMultipleRootComponents()
8687
false,
8788
true,
8889
false,
89-
true
90+
true,
91+
false,
92+
true,
93+
false,
94+
true,
9095
};
9196
Assert.Equal(expectedComponentSequence, componentSequence);
9297

@@ -96,6 +101,8 @@ public void CanRenderMultipleRootComponents()
96101
Browser.Exists(By.CssSelector("h3.interactive"));
97102
var updatedGreets = Browser.FindElements(By.CssSelector(".greet-wrapper .greet")).Select(e => e.Text).ToArray();
98103
Assert.Equal(7, updatedGreets.Where(g => string.Equals("Hello Alfred", g)).Count());
104+
Assert.Equal(2, updatedGreets.Where(g => g == "Hello Red fish").Count());
105+
Assert.Equal(2, updatedGreets.Where(g => g == "Hello Blue fish").Count());
99106
Assert.Single(updatedGreets.Where(g => string.Equals("Hello Albert", g)));
100107
Assert.Single(updatedGreets.Where(g => string.Equals("Hello Abraham", g)));
101108
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
using Microsoft.AspNetCore.Authentication.Cookies;
2+
using Microsoft.AspNetCore.Builder;
3+
using Microsoft.AspNetCore.Hosting;
4+
using Microsoft.Extensions.Configuration;
5+
using Microsoft.Extensions.DependencyInjection;
6+
using Microsoft.Extensions.Hosting;
7+
8+
namespace TestServer
9+
{
10+
public class MultipleComponents
11+
{
12+
public MultipleComponents(IConfiguration configuration)
13+
{
14+
Configuration = configuration;
15+
}
16+
17+
public IConfiguration Configuration { get; }
18+
19+
// This method gets called by the runtime. Use this method to add services to the container.
20+
public void ConfigureServices(IServiceCollection services)
21+
{
22+
services.AddMvc();
23+
services.AddServerSideBlazor();
24+
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie();
25+
}
26+
27+
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
28+
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
29+
{
30+
if (env.IsDevelopment())
31+
{
32+
app.UseDeveloperExceptionPage();
33+
}
34+
35+
app.Map("/multiple-components", app =>
36+
{
37+
app.UseStaticFiles();
38+
39+
app.UseAuthentication();
40+
41+
app.UseRouting();
42+
app.UseEndpoints(endpoints =>
43+
{
44+
endpoints.MapRazorPages();
45+
endpoints.MapFallbackToPage("/MultipleComponents");
46+
endpoints.MapBlazorHub();
47+
});
48+
});
49+
}
50+
}
51+
}
Lines changed: 24 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,29 @@
11
@page "/multiple-components"
22
@using BasicTestApp.MultipleComponents;
3-
<!DOCTYPE html>
4-
<html>
5-
<head>
6-
<title>Multiple component entry points</title>
7-
<base href="~/" />
8-
@* This page is used to validate the ability to render multiple root components in a blazor server-side application.
9-
*@
10-
</head>
11-
<body>
12-
<div id="test-container">
13-
@(await Html.RenderComponentAsync<GreeterComponent>(RenderMode.ServerPrerendered))
14-
@(await Html.RenderComponentAsync<GreeterComponent>(RenderMode.Server))
15-
<component type="typeof(GreeterComponent)" render-mode="Static" param-name='"John"' />
16-
<component type="typeof(GreeterComponent)" render-mode="Server"/>
17-
<div id="container">
18-
<p>Some content before</p>
19-
<component type="typeof(GreeterComponent)" render-mode="Server"/>
20-
<p>Some content between</p>
21-
<component type="typeof(GreeterComponent)" render-mode="ServerPrerendered"/>
22-
<p>Some content after</p>
23-
<div id="nested-an-extra-level">
24-
<p>Some content before</p>
25-
<component type="typeof(GreeterComponent)" render-mode="Server"/>
26-
<component type="typeof(GreeterComponent)" render-mode="ServerPrerendered"/>
27-
<p>Some content after</p>
28-
</div>
29-
</div>
30-
<div id="container">
31-
<component type="typeof(GreeterComponent)" render-mode="Server" param-name='"Albert"' />
32-
<component type="typeof(GreeterComponent)" render-mode="ServerPrerendered" param-name='"Abraham"' />
33-
</div>
34-
</div>
35-
36-
@*
37-
So that E2E tests can make assertions about both the prerendered and
38-
interactive states, we only load the .js file when told to.
39-
*@
40-
<hr />
413

42-
<button id="load-boot-script" onclick="start()">Load boot script</button>
4+
@{
5+
Layout = "./MultipleComponentsLayout.cshtml";
6+
}
437

44-
<script src="_framework/blazor.server.js" autostart="false"></script>
45-
<script>
46-
// Used by InteropOnInitializationComponent
47-
function setElementValue(element, newValue) {
48-
element.value = newValue;
49-
return element.value;
50-
}
518

52-
function start() {
53-
Blazor.start({
54-
logLevel: 1 // LogLevel.Debug
55-
});
56-
}
57-
</script>
58-
</body>
59-
</html>
9+
@(await Html.RenderComponentAsync<GreeterComponent>(RenderMode.ServerPrerendered))
10+
@(await Html.RenderComponentAsync<GreeterComponent>(RenderMode.Server))
11+
<component type="typeof(GreeterComponent)" render-mode="Static" param-name='"John"' />
12+
<component type="typeof(GreeterComponent)" render-mode="Server" />
13+
<div id="container">
14+
<p>Some content before</p>
15+
<component type="typeof(GreeterComponent)" render-mode="Server" />
16+
<p>Some content between</p>
17+
<component type="typeof(GreeterComponent)" render-mode="ServerPrerendered" />
18+
<p>Some content after</p>
19+
<div id="nested-an-extra-level">
20+
<p>Some content before</p>
21+
<component type="typeof(GreeterComponent)" render-mode="Server" />
22+
<component type="typeof(GreeterComponent)" render-mode="ServerPrerendered" />
23+
<p>Some content after</p>
24+
</div>
25+
</div>
26+
<div id="container">
27+
<component type="typeof(GreeterComponent)" render-mode="Server" param-name='"Albert"' />
28+
<component type="typeof(GreeterComponent)" render-mode="ServerPrerendered" param-name='"Abraham"' />
29+
</div>
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
@using BasicTestApp.MultipleComponents;
2+
3+
<!DOCTYPE html>
4+
<html>
5+
<head>
6+
<title>Multiple component entry points</title>
7+
<base href="~/" />
8+
@* This page is used to validate the ability to render multiple root components in a blazor server-side application.
9+
*@
10+
</head>
11+
<body>
12+
<div id="test-container">
13+
<component type="typeof(GreeterComponent)" render-mode="Server" param-name='"Red fish"' />
14+
<component type="typeof(GreeterComponent)" render-mode="ServerPrerendered" param-name='"Blue fish"' />
15+
@RenderBody()
16+
<component type="typeof(GreeterComponent)" render-mode="Server" param-name='"Red fish"' />
17+
<component type="typeof(GreeterComponent)" render-mode="ServerPrerendered" param-name='"Blue fish"' />
18+
</div>
19+
20+
@*
21+
So that E2E tests can make assertions about both the prerendered and
22+
interactive states, we only load the .js file when told to.
23+
*@
24+
<hr />
25+
26+
<button id="load-boot-script" onclick="start()">Load boot script</button>
27+
28+
<script src="_framework/blazor.server.js" autostart="false"></script>
29+
<script>
30+
// Used by InteropOnInitializationComponent
31+
function setElementValue(element, newValue) {
32+
element.value = newValue;
33+
return element.value;
34+
}
35+
36+
function start() {
37+
Blazor.start({
38+
logLevel: 1 // LogLevel.Debug
39+
});
40+
}
41+
</script>
42+
</body>
43+
</html>

src/Components/test/testassets/TestServer/Program.cs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,10 @@
1-
using System;
21
using System.Collections.Generic;
32
using System.Linq;
43
using System.Reflection;
5-
using System.Threading;
64
using System.Threading.Tasks;
7-
using Microsoft.AspNetCore;
85
using Microsoft.AspNetCore.Hosting;
96
using Microsoft.AspNetCore.Hosting.Server;
107
using Microsoft.AspNetCore.Hosting.Server.Features;
11-
using Microsoft.AspNetCore.Http.Features;
12-
using Microsoft.Extensions.Configuration;
138
using Microsoft.Extensions.DependencyInjection;
149
using Microsoft.Extensions.Hosting;
1510
using Microsoft.Extensions.Logging;
@@ -28,6 +23,7 @@ public static void Main(string[] args)
2823
["Server authentication"] = (BuildWebHost<ServerAuthenticationStartup>(CreateAdditionalArgs(args)), "/subdir"),
2924
["CORS (WASM)"] = (BuildWebHost<CorsStartup>(CreateAdditionalArgs(args)), "/subdir"),
3025
["Prerendering (Server-side)"] = (BuildWebHost<PrerenderedStartup>(CreateAdditionalArgs(args)), "/prerendered"),
26+
["Multiple components (Server-side)"] = (BuildWebHost<MultipleComponents>(CreateAdditionalArgs(args)), "/multiple-components"),
3127
["Globalization + Localization (Server-side)"] = (BuildWebHost<InternationalizationStartup>(CreateAdditionalArgs(args)), "/subdir"),
3228
["Server-side blazor"] = (BuildWebHost<ServerStartup>(CreateAdditionalArgs(args)), "/subdir"),
3329
["Hosted client-side blazor"] = (BuildWebHost<ClientStartup>(CreateAdditionalArgs(args)), "/subdir"),

0 commit comments

Comments
 (0)