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 https://github.com/aspnet/AspNetCore/issues/14474
This commit is contained in:
parent
579f30f591
commit
30b31d7086
|
|
@ -3,7 +3,7 @@
|
||||||
"LogLevel": {
|
"LogLevel": {
|
||||||
"Default": "Debug",
|
"Default": "Debug",
|
||||||
"System": "Information",
|
"System": "Information",
|
||||||
"Microsoft": "Information"
|
"Microsoft": "Debug"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -101,7 +101,7 @@ export function discoverComponents(document: Document): ComponentDescriptor[] {
|
||||||
discoveredComponents.push(entry);
|
discoveredComponents.push(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
return discoveredComponents;
|
return discoveredComponents.sort((a, b) => a.sequence - b.sequence);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,13 +16,13 @@ using Xunit.Abstractions;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
||||||
{
|
{
|
||||||
public class MultipleComponentsTest : ServerTestBase<BasicTestAppServerSiteFixture<PrerenderedStartup>>
|
public class MultipleComponentsTest : ServerTestBase<BasicTestAppServerSiteFixture<MultipleComponents>>
|
||||||
{
|
{
|
||||||
private const string MarkerPattern = ".*?<!--Blazor:(.*?)-->.*?";
|
private const string MarkerPattern = ".*?<!--Blazor:(.*?)-->.*?";
|
||||||
|
|
||||||
public MultipleComponentsTest(
|
public MultipleComponentsTest(
|
||||||
BrowserFixture browserFixture,
|
BrowserFixture browserFixture,
|
||||||
BasicTestAppServerSiteFixture<PrerenderedStartup> serverFixture,
|
BasicTestAppServerSiteFixture<MultipleComponents> serverFixture,
|
||||||
ITestOutputHelper output)
|
ITestOutputHelper output)
|
||||||
: base(browserFixture, serverFixture, output)
|
: base(browserFixture, serverFixture, output)
|
||||||
{
|
{
|
||||||
|
|
@ -46,7 +46,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
||||||
[Fact]
|
[Fact]
|
||||||
public void DoesNotStartMultipleConnections()
|
public void DoesNotStartMultipleConnections()
|
||||||
{
|
{
|
||||||
Navigate("/prerendered/multiple-components");
|
Navigate("/multiple-components");
|
||||||
|
|
||||||
BeginInteractivity();
|
BeginInteractivity();
|
||||||
Browser.Exists(By.CssSelector("h3.interactive"));
|
Browser.Exists(By.CssSelector("h3.interactive"));
|
||||||
|
|
@ -60,17 +60,18 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CanRenderMultipleRootComponents()
|
public void CanRenderMultipleRootComponents()
|
||||||
{
|
{
|
||||||
Navigate("/prerendered/multiple-components");
|
Navigate("/multiple-components");
|
||||||
|
|
||||||
var greets = Browser.FindElements(By.CssSelector(".greet-wrapper .greet")).Select(e => e.Text).ToArray();
|
var greets = Browser.FindElements(By.CssSelector(".greet-wrapper .greet")).Select(e => e.Text).ToArray();
|
||||||
|
|
||||||
Assert.Equal(5, greets.Length); // 1 statically rendered + 3 prerendered + 1 server prerendered
|
Assert.Equal(7, greets.Length); // 1 statically rendered + 5 prerendered + 1 server prerendered
|
||||||
|
Assert.DoesNotContain("Hello Red fish", greets);
|
||||||
Assert.Single(greets, "Hello John");
|
Assert.Single(greets, "Hello John");
|
||||||
Assert.Single(greets, "Hello Abraham");
|
Assert.Single(greets, "Hello Abraham");
|
||||||
|
Assert.Equal(2, greets.Where(g => g == "Hello Blue fish").Count());
|
||||||
Assert.Equal(3, greets.Where(g => string.Equals("Hello", g)).Count()); // 3 server prerendered without parameters
|
Assert.Equal(3, greets.Where(g => string.Equals("Hello", g)).Count()); // 3 server prerendered without parameters
|
||||||
var content = Browser.FindElement(By.Id("test-container")).GetAttribute("innerHTML");
|
var content = Browser.FindElement(By.Id("test-container")).GetAttribute("innerHTML");
|
||||||
var markers = ReadMarkers(content);
|
var markers = ReadMarkers(content);
|
||||||
|
|
@ -86,7 +87,11 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
||||||
false,
|
false,
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
true
|
true,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
};
|
};
|
||||||
Assert.Equal(expectedComponentSequence, componentSequence);
|
Assert.Equal(expectedComponentSequence, componentSequence);
|
||||||
|
|
||||||
|
|
@ -96,6 +101,8 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
||||||
Browser.Exists(By.CssSelector("h3.interactive"));
|
Browser.Exists(By.CssSelector("h3.interactive"));
|
||||||
var updatedGreets = Browser.FindElements(By.CssSelector(".greet-wrapper .greet")).Select(e => e.Text).ToArray();
|
var updatedGreets = Browser.FindElements(By.CssSelector(".greet-wrapper .greet")).Select(e => e.Text).ToArray();
|
||||||
Assert.Equal(7, updatedGreets.Where(g => string.Equals("Hello Alfred", g)).Count());
|
Assert.Equal(7, updatedGreets.Where(g => string.Equals("Hello Alfred", g)).Count());
|
||||||
|
Assert.Equal(2, updatedGreets.Where(g => g == "Hello Red fish").Count());
|
||||||
|
Assert.Equal(2, updatedGreets.Where(g => g == "Hello Blue fish").Count());
|
||||||
Assert.Single(updatedGreets.Where(g => string.Equals("Hello Albert", g)));
|
Assert.Single(updatedGreets.Where(g => string.Equals("Hello Albert", g)));
|
||||||
Assert.Single(updatedGreets.Where(g => string.Equals("Hello Abraham", g)));
|
Assert.Single(updatedGreets.Where(g => string.Equals("Hello Abraham", g)));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
|
||||||
|
namespace TestServer
|
||||||
|
{
|
||||||
|
public class MultipleComponents
|
||||||
|
{
|
||||||
|
public MultipleComponents(IConfiguration configuration)
|
||||||
|
{
|
||||||
|
Configuration = configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IConfiguration Configuration { get; }
|
||||||
|
|
||||||
|
// This method gets called by the runtime. Use this method to add services to the container.
|
||||||
|
public void ConfigureServices(IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddMvc();
|
||||||
|
services.AddServerSideBlazor();
|
||||||
|
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie();
|
||||||
|
}
|
||||||
|
|
||||||
|
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||||
|
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
||||||
|
{
|
||||||
|
if (env.IsDevelopment())
|
||||||
|
{
|
||||||
|
app.UseDeveloperExceptionPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
app.Map("/multiple-components", app =>
|
||||||
|
{
|
||||||
|
app.UseStaticFiles();
|
||||||
|
|
||||||
|
app.UseAuthentication();
|
||||||
|
|
||||||
|
app.UseRouting();
|
||||||
|
app.UseEndpoints(endpoints =>
|
||||||
|
{
|
||||||
|
endpoints.MapRazorPages();
|
||||||
|
endpoints.MapFallbackToPage("/MultipleComponents");
|
||||||
|
endpoints.MapBlazorHub();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,59 +1,29 @@
|
||||||
@page "/multiple-components"
|
@page "/multiple-components"
|
||||||
@using BasicTestApp.MultipleComponents;
|
@using BasicTestApp.MultipleComponents;
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
@{
|
||||||
<head>
|
Layout = "./MultipleComponentsLayout.cshtml";
|
||||||
<title>Multiple component entry points</title>
|
}
|
||||||
<base href="~/" />
|
|
||||||
@* This page is used to validate the ability to render multiple root components in a blazor server-side application.
|
|
||||||
*@
|
@(await Html.RenderComponentAsync<GreeterComponent>(RenderMode.ServerPrerendered))
|
||||||
</head>
|
@(await Html.RenderComponentAsync<GreeterComponent>(RenderMode.Server))
|
||||||
<body>
|
<component type="typeof(GreeterComponent)" render-mode="Static" param-name='"John"' />
|
||||||
<div id="test-container">
|
<component type="typeof(GreeterComponent)" render-mode="Server" />
|
||||||
@(await Html.RenderComponentAsync<GreeterComponent>(RenderMode.ServerPrerendered))
|
<div id="container">
|
||||||
@(await Html.RenderComponentAsync<GreeterComponent>(RenderMode.Server))
|
<p>Some content before</p>
|
||||||
<component type="typeof(GreeterComponent)" render-mode="Static" param-name='"John"' />
|
<component type="typeof(GreeterComponent)" render-mode="Server" />
|
||||||
<component type="typeof(GreeterComponent)" render-mode="Server"/>
|
<p>Some content between</p>
|
||||||
<div id="container">
|
<component type="typeof(GreeterComponent)" render-mode="ServerPrerendered" />
|
||||||
<p>Some content before</p>
|
<p>Some content after</p>
|
||||||
<component type="typeof(GreeterComponent)" render-mode="Server"/>
|
<div id="nested-an-extra-level">
|
||||||
<p>Some content between</p>
|
<p>Some content before</p>
|
||||||
<component type="typeof(GreeterComponent)" render-mode="ServerPrerendered"/>
|
<component type="typeof(GreeterComponent)" render-mode="Server" />
|
||||||
<p>Some content after</p>
|
<component type="typeof(GreeterComponent)" render-mode="ServerPrerendered" />
|
||||||
<div id="nested-an-extra-level">
|
<p>Some content after</p>
|
||||||
<p>Some content before</p>
|
|
||||||
<component type="typeof(GreeterComponent)" render-mode="Server"/>
|
|
||||||
<component type="typeof(GreeterComponent)" render-mode="ServerPrerendered"/>
|
|
||||||
<p>Some content after</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div id="container">
|
|
||||||
<component type="typeof(GreeterComponent)" render-mode="Server" param-name='"Albert"' />
|
|
||||||
<component type="typeof(GreeterComponent)" render-mode="ServerPrerendered" param-name='"Abraham"' />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
@*
|
<div id="container">
|
||||||
So that E2E tests can make assertions about both the prerendered and
|
<component type="typeof(GreeterComponent)" render-mode="Server" param-name='"Albert"' />
|
||||||
interactive states, we only load the .js file when told to.
|
<component type="typeof(GreeterComponent)" render-mode="ServerPrerendered" param-name='"Abraham"' />
|
||||||
*@
|
</div>
|
||||||
<hr />
|
|
||||||
|
|
||||||
<button id="load-boot-script" onclick="start()">Load boot script</button>
|
|
||||||
|
|
||||||
<script src="_framework/blazor.server.js" autostart="false"></script>
|
|
||||||
<script>
|
|
||||||
// Used by InteropOnInitializationComponent
|
|
||||||
function setElementValue(element, newValue) {
|
|
||||||
element.value = newValue;
|
|
||||||
return element.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
function start() {
|
|
||||||
Blazor.start({
|
|
||||||
logLevel: 1 // LogLevel.Debug
|
|
||||||
});
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
@using BasicTestApp.MultipleComponents;
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Multiple component entry points</title>
|
||||||
|
<base href="~/" />
|
||||||
|
@* This page is used to validate the ability to render multiple root components in a blazor server-side application.
|
||||||
|
*@
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="test-container">
|
||||||
|
<component type="typeof(GreeterComponent)" render-mode="Server" param-name='"Red fish"' />
|
||||||
|
<component type="typeof(GreeterComponent)" render-mode="ServerPrerendered" param-name='"Blue fish"' />
|
||||||
|
@RenderBody()
|
||||||
|
<component type="typeof(GreeterComponent)" render-mode="Server" param-name='"Red fish"' />
|
||||||
|
<component type="typeof(GreeterComponent)" render-mode="ServerPrerendered" param-name='"Blue fish"' />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@*
|
||||||
|
So that E2E tests can make assertions about both the prerendered and
|
||||||
|
interactive states, we only load the .js file when told to.
|
||||||
|
*@
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<button id="load-boot-script" onclick="start()">Load boot script</button>
|
||||||
|
|
||||||
|
<script src="_framework/blazor.server.js" autostart="false"></script>
|
||||||
|
<script>
|
||||||
|
// Used by InteropOnInitializationComponent
|
||||||
|
function setElementValue(element, newValue) {
|
||||||
|
element.value = newValue;
|
||||||
|
return element.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function start() {
|
||||||
|
Blazor.start({
|
||||||
|
logLevel: 1 // LogLevel.Debug
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -1,15 +1,10 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore;
|
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.AspNetCore.Hosting.Server;
|
using Microsoft.AspNetCore.Hosting.Server;
|
||||||
using Microsoft.AspNetCore.Hosting.Server.Features;
|
using Microsoft.AspNetCore.Hosting.Server.Features;
|
||||||
using Microsoft.AspNetCore.Http.Features;
|
|
||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
@ -28,6 +23,7 @@ namespace TestServer
|
||||||
["Server authentication"] = (BuildWebHost<ServerAuthenticationStartup>(CreateAdditionalArgs(args)), "/subdir"),
|
["Server authentication"] = (BuildWebHost<ServerAuthenticationStartup>(CreateAdditionalArgs(args)), "/subdir"),
|
||||||
["CORS (WASM)"] = (BuildWebHost<CorsStartup>(CreateAdditionalArgs(args)), "/subdir"),
|
["CORS (WASM)"] = (BuildWebHost<CorsStartup>(CreateAdditionalArgs(args)), "/subdir"),
|
||||||
["Prerendering (Server-side)"] = (BuildWebHost<PrerenderedStartup>(CreateAdditionalArgs(args)), "/prerendered"),
|
["Prerendering (Server-side)"] = (BuildWebHost<PrerenderedStartup>(CreateAdditionalArgs(args)), "/prerendered"),
|
||||||
|
["Multiple components (Server-side)"] = (BuildWebHost<MultipleComponents>(CreateAdditionalArgs(args)), "/multiple-components"),
|
||||||
["Globalization + Localization (Server-side)"] = (BuildWebHost<InternationalizationStartup>(CreateAdditionalArgs(args)), "/subdir"),
|
["Globalization + Localization (Server-side)"] = (BuildWebHost<InternationalizationStartup>(CreateAdditionalArgs(args)), "/subdir"),
|
||||||
["Server-side blazor"] = (BuildWebHost<ServerStartup>(CreateAdditionalArgs(args)), "/subdir"),
|
["Server-side blazor"] = (BuildWebHost<ServerStartup>(CreateAdditionalArgs(args)), "/subdir"),
|
||||||
["Hosted client-side blazor"] = (BuildWebHost<ClientStartup>(CreateAdditionalArgs(args)), "/subdir"),
|
["Hosted client-side blazor"] = (BuildWebHost<ClientStartup>(CreateAdditionalArgs(args)), "/subdir"),
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue