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:
Pranav K 2019-10-15 13:17:58 -07:00 committed by GitHub
parent 579f30f591
commit 30b31d7086
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 138 additions and 71 deletions

View File

@ -3,7 +3,7 @@
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Microsoft": "Information"
"Microsoft": "Debug"
}
}
}

File diff suppressed because one or more lines are too long

View File

@ -101,7 +101,7 @@ export function discoverComponents(document: Document): ComponentDescriptor[] {
discoveredComponents.push(entry);
}
return discoveredComponents;
return discoveredComponents.sort((a, b) => a.sequence - b.sequence);
}

View File

@ -16,13 +16,13 @@ using Xunit.Abstractions;
namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
{
public class MultipleComponentsTest : ServerTestBase<BasicTestAppServerSiteFixture<PrerenderedStartup>>
public class MultipleComponentsTest : ServerTestBase<BasicTestAppServerSiteFixture<MultipleComponents>>
{
private const string MarkerPattern = ".*?<!--Blazor:(.*?)-->.*?";
public MultipleComponentsTest(
BrowserFixture browserFixture,
BasicTestAppServerSiteFixture<PrerenderedStartup> serverFixture,
BasicTestAppServerSiteFixture<MultipleComponents> serverFixture,
ITestOutputHelper output)
: base(browserFixture, serverFixture, output)
{
@ -46,7 +46,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
[Fact]
public void DoesNotStartMultipleConnections()
{
Navigate("/prerendered/multiple-components");
Navigate("/multiple-components");
BeginInteractivity();
Browser.Exists(By.CssSelector("h3.interactive"));
@ -60,17 +60,18 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
});
}
[Fact]
public void CanRenderMultipleRootComponents()
{
Navigate("/prerendered/multiple-components");
Navigate("/multiple-components");
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 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
var content = Browser.FindElement(By.Id("test-container")).GetAttribute("innerHTML");
var markers = ReadMarkers(content);
@ -86,7 +87,11 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
false,
true,
false,
true
true,
false,
true,
false,
true,
};
Assert.Equal(expectedComponentSequence, componentSequence);
@ -96,6 +101,8 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
Browser.Exists(By.CssSelector("h3.interactive"));
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(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 Abraham", g)));
}

View File

@ -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();
});
});
}
}
}

View File

@ -1,59 +1,29 @@
@page "/multiple-components"
@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">
@(await Html.RenderComponentAsync<GreeterComponent>(RenderMode.ServerPrerendered))
@(await Html.RenderComponentAsync<GreeterComponent>(RenderMode.Server))
<component type="typeof(GreeterComponent)" render-mode="Static" param-name='"John"' />
<component type="typeof(GreeterComponent)" render-mode="Server"/>
<div id="container">
<p>Some content before</p>
<component type="typeof(GreeterComponent)" render-mode="Server"/>
<p>Some content between</p>
<component type="typeof(GreeterComponent)" render-mode="ServerPrerendered"/>
<p>Some content after</p>
<div id="nested-an-extra-level">
<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>
@{
Layout = "./MultipleComponentsLayout.cshtml";
}
@(await Html.RenderComponentAsync<GreeterComponent>(RenderMode.ServerPrerendered))
@(await Html.RenderComponentAsync<GreeterComponent>(RenderMode.Server))
<component type="typeof(GreeterComponent)" render-mode="Static" param-name='"John"' />
<component type="typeof(GreeterComponent)" render-mode="Server" />
<div id="container">
<p>Some content before</p>
<component type="typeof(GreeterComponent)" render-mode="Server" />
<p>Some content between</p>
<component type="typeof(GreeterComponent)" render-mode="ServerPrerendered" />
<p>Some content after</p>
<div id="nested-an-extra-level">
<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>
@*
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>
</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>

View File

@ -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>

View File

@ -1,15 +1,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Hosting.Server.Features;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
@ -28,6 +23,7 @@ namespace TestServer
["Server authentication"] = (BuildWebHost<ServerAuthenticationStartup>(CreateAdditionalArgs(args)), "/subdir"),
["CORS (WASM)"] = (BuildWebHost<CorsStartup>(CreateAdditionalArgs(args)), "/subdir"),
["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"),
["Server-side blazor"] = (BuildWebHost<ServerStartup>(CreateAdditionalArgs(args)), "/subdir"),
["Hosted client-side blazor"] = (BuildWebHost<ClientStartup>(CreateAdditionalArgs(args)), "/subdir"),