-
Notifications
You must be signed in to change notification settings - Fork 10.4k
Supply "IsConnected" state info to components #8888
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
b83b110
b66364e
5b59ea9
edf0347
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
// Copyright (c) .NET Foundation. All rights reserved. | ||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
||
using Microsoft.AspNetCore.Components.Services; | ||
|
||
namespace Microsoft.AspNetCore.Blazor.Services | ||
{ | ||
internal class WebAssemblyComponentContext : IComponentContext | ||
{ | ||
public bool IsConnected => true; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
// Copyright (c) .NET Foundation. All rights reserved. | ||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
||
using Xunit; | ||
|
||
namespace Microsoft.AspNetCore.Blazor.Services.Test | ||
{ | ||
public class WebAssemblyComponentContextTest | ||
{ | ||
[Fact] | ||
public void IsConnected() | ||
{ | ||
Assert.True(new WebAssemblyComponentContext().IsConnected); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
// Copyright (c) .NET Foundation. All rights reserved. | ||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
||
namespace Microsoft.AspNetCore.Components.Services | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What would you think about putting this in
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Totally happy to merge the Filed #8892 |
||
{ | ||
/// <summary> | ||
/// Provides information about the environment in which components are executing. | ||
/// </summary> | ||
public interface IComponentContext | ||
{ | ||
/// <summary> | ||
/// Gets a flag to indicate whether there is an active connection to the user's display. | ||
/// </summary> | ||
/// <example>During prerendering, the value will always be false.</example> | ||
/// <example>During server-side execution, the value can be true or false depending on whether there is an active SignalR connection.</example> | ||
/// <example>During client-side execution, the value will always be true.</example> | ||
bool IsConnected { get; } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we document some guarantees about what happens when transition between states? Or is this not the right place for that. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What kind of guarantees do you have in mind? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Like, if you weren't connected when you last rendered - does that mean that you get called again when you are connected? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, I see. Since we're not proposing to do that, I'm not sure what there would be to say about it here. I expect you know already, but the intent to deal with the mainstream "transition from prerendered to interactive" case is via lifecycle methods. For a more general "notify me when connection state changes", I'd expect that to be a separate API, which someone could wrap into a cascading value if they wanted. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok cool. I guess what I was thinking about is where are we going to document to the pattern for using this property effectively. That's probably not a good fit for the in-code docs, so feel free to ignore. |
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
// Copyright (c) .NET Foundation. All rights reserved. | ||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
||
using System; | ||
using Microsoft.AspNetCore.Components.Services; | ||
|
||
namespace Microsoft.AspNetCore.Components.Server.Circuits | ||
{ | ||
internal class RemoteComponentContext : IComponentContext | ||
{ | ||
private CircuitClientProxy _clientProxy; | ||
|
||
public bool IsConnected => _clientProxy != null && _clientProxy.Connected; | ||
|
||
internal void Initialize(CircuitClientProxy clientProxy) | ||
{ | ||
_clientProxy = clientProxy ?? throw new ArgumentNullException(nameof(clientProxy)); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
// Copyright (c) .NET Foundation. All rights reserved. | ||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
||
using System; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using Microsoft.AspNetCore.Components.Server.Circuits; | ||
using Microsoft.AspNetCore.SignalR; | ||
using Xunit; | ||
|
||
namespace Microsoft.AspNetCore.Components.Browser.Rendering | ||
{ | ||
public class RemoteComponentContextTest | ||
{ | ||
[Fact] | ||
public void IfNotInitialized_IsConnectedReturnsFalse() | ||
{ | ||
Assert.False(new RemoteComponentContext().IsConnected); | ||
} | ||
|
||
[Fact] | ||
public void IfInitialized_IsConnectedValueDeterminedByCircuitProxy() | ||
{ | ||
// Arrange | ||
var clientProxy = new FakeClientProxy(); | ||
var circuitProxy = new CircuitClientProxy(clientProxy, "test connection"); | ||
var remoteComponentContext = new RemoteComponentContext(); | ||
|
||
// Act/Assert: Can observe connected state | ||
remoteComponentContext.Initialize(circuitProxy); | ||
Assert.True(remoteComponentContext.IsConnected); | ||
|
||
// Act/Assert: Can observe disconnected state | ||
circuitProxy.SetDisconnected(); | ||
Assert.False(remoteComponentContext.IsConnected); | ||
} | ||
|
||
private class FakeClientProxy : IClientProxy | ||
{ | ||
public Task SendCoreAsync(string method, object[] args, CancellationToken cancellationToken = default) | ||
{ | ||
throw new NotImplementedException(); | ||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
// Copyright (c) .NET Foundation. All rights reserved. | ||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
||
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure; | ||
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures; | ||
using Microsoft.AspNetCore.E2ETesting; | ||
using OpenQA.Selenium; | ||
using Xunit; | ||
using Xunit.Abstractions; | ||
|
||
namespace Microsoft.AspNetCore.Components.E2ETests.ServerExecutionTests | ||
{ | ||
public class PrerenderingTest : ServerTestBase<AspNetSiteServerFixture> | ||
{ | ||
public PrerenderingTest( | ||
BrowserFixture browserFixture, | ||
AspNetSiteServerFixture serverFixture, | ||
ITestOutputHelper output) | ||
: base(browserFixture, serverFixture, output) | ||
{ | ||
_serverFixture.Environment = AspNetEnvironment.Development; | ||
_serverFixture.BuildWebHostMethod = TestServer.Program.BuildWebHost; | ||
} | ||
|
||
[Fact] | ||
public void CanTransitionFromPrerenderedToInteractiveMode() | ||
{ | ||
Navigate("/prerendered/prerendered-transition"); | ||
|
||
// Prerendered output shows "not connected" | ||
Browser.Equal("not connected", () => Browser.FindElement(By.Id("connected-state")).Text); | ||
|
||
// Once connected, output changes | ||
BeginInteractivity(); | ||
Browser.Equal("connected", () => Browser.FindElement(By.Id("connected-state")).Text); | ||
|
||
// ... and now the counter works | ||
Browser.FindElement(By.Id("increment-count")).Click(); | ||
Browser.Equal("1", () => Browser.FindElement(By.Id("count")).Text); | ||
} | ||
|
||
private void BeginInteractivity() | ||
{ | ||
Browser.FindElement(By.Id("load-boot-script")).Click(); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
@page "/prerendered-transition" | ||
@using Microsoft.AspNetCore.Components.Services | ||
@inject IComponentContext ComponentContext | ||
|
||
<h1>Hello</h1> | ||
|
||
<p> | ||
Current state: | ||
<strong id="connected-state">@(ComponentContext.IsConnected ? "connected" : "not connected")</strong> | ||
</p> | ||
|
||
<p> | ||
Clicks: | ||
<strong id="count">@count</strong> | ||
<button id="increment-count" onclick="@(() => count++)">Click me</button> | ||
</p> | ||
|
||
@functions { | ||
int count; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
@page | ||
@using BasicTestApp.RouterTest | ||
<!DOCTYPE html> | ||
<html> | ||
<head> | ||
<title>Prerendering tests</title> | ||
<base href="~/" /> | ||
</head> | ||
<body> | ||
<app>@(await Html.RenderComponentAsync<TestRouter>())</app> | ||
|
||
@* | ||
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="loadBootScript(event)">Load boot script</button> | ||
<script> | ||
function loadBootScript(event) { | ||
event.srcElement.disabled = true; | ||
var scriptElem = document.createElement('script'); | ||
scriptElem.src = '_framework/components.server.js'; | ||
document.body.appendChild(scriptElem); | ||
} | ||
</script> | ||
</body> | ||
</html> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
using BasicTestApp; | ||
using BasicTestApp.RouterTest; | ||
using Microsoft.AspNetCore.Builder; | ||
using Microsoft.AspNetCore.Components.Server; | ||
using Microsoft.AspNetCore.Hosting; | ||
|
@@ -68,6 +69,19 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) | |
{ | ||
endpoints.MapControllers(); | ||
}); | ||
|
||
// Separately, mount a prerendered server-side Blazor app on /prerendered | ||
app.Map("/prerendered", subdirApp => | ||
{ | ||
subdirApp.UsePathBase("/prerendered"); | ||
subdirApp.UseStaticFiles(); | ||
subdirApp.UseRouting(); | ||
subdirApp.UseEndpoints(endpoints => | ||
{ | ||
endpoints.MapFallbackToPage("/PrerenderedHost"); | ||
endpoints.MapComponentHub<TestRouter>("app"); | ||
}); | ||
}); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should not be our experience for having multiple entry points as part of an app. But that requires a separate discussion. See #8913 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure, this is just what you have to do today, not a claim that it's the ideal dev experience or that we shouldn't change it. |
||
} | ||
|
||
private static void AllowCorsForAnyLocalhostPort(IApplicationBuilder app) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How did you update this? Can you try and get the private members of the components that we ship to show up? That breaks the current E2E test in ProjectTemplates. It would be incredibly dope if we updated and enable that.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I ran
GenerateReferenceSource
as per https://github.com/aspnet/AspNetCore/blob/master/docs/ReferenceAssemblies.mdWhat you see here is what it outputs. You can't make it output private members as they are not an aspect of reference assemblies.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do believe we did this for some other APIs in the assembly. This is an ongoing problem on the templates. Things like
Assembly="typeof(Blah).Assembly"
won't work because it won't be able to find the Assembly property on the component and it will treat it as a string instead when compiling.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, @rynowak fixed that by telling the tool not to generate anything for those types, and he wrote those ref types manually. I don't think we need to do
IComponentContext
manually though.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For now we need to manually generate the reference source of components because non-public methods do not show up in reference assemblies. This should not apply to anything else we need to do. If the contagion starts spreading I would love to learn more about that 😆