Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ private void CreateServiceProvider()
services.AddSingleton(_BrowserHostBuilderContext);
services.AddSingleton<IWebAssemblyHost, WebAssemblyHost>();
services.AddSingleton<IJSRuntime>(WebAssemblyJSRuntime.Instance);

services.AddSingleton<IComponentContext, WebAssemblyComponentContext>();
services.AddSingleton<IUriHelper>(WebAssemblyUriHelper.Instance);
services.AddSingleton<HttpClient>(s =>
{
Expand Down
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
Expand Up @@ -836,6 +836,10 @@ public enum NavLinkMatch
}
namespace Microsoft.AspNetCore.Components.Services
{
public partial interface IComponentContext
Copy link
Member

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.

Copy link
Member Author

@SteveSandersonMS SteveSandersonMS Mar 29, 2019

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.md

What you see here is what it outputs. You can't make it output private members as they are not an aspect of reference assemblies.

Copy link
Member

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.

Copy link
Member Author

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.

Copy link
Member

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 😆

{
bool IsConnected { get; }
}
public partial interface IUriHelper
{
event System.EventHandler<string> OnLocationChanged;
Expand Down
19 changes: 19 additions & 0 deletions src/Components/Components/src/Services/IComponentContext.cs
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What would you think about putting this in Microsoft.AspNetCore.Components. I really think we'll end up removing .Services as a namespace when this gets reviewed in favor of some mix of:

  • moving these types into .Components
  • moving these types into more specific namespaces like routing/layouts
  • moving them into a more generic namespace like .Infrastructure for things that most people won't use

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Totally happy to merge the Components.Services namespace into .Components. That would also fix one of the other things that's been bothering me lately - that the templates imports don't include .Services.

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; }
Copy link
Member

Choose a reason for hiding this comment

The 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.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What kind of guarantees do you have in mind?

Copy link
Member

Choose a reason for hiding this comment

The 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?

Copy link
Member Author

Choose a reason for hiding this comment

The 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.

Copy link
Member

Choose a reason for hiding this comment

The 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.

}
}
2 changes: 2 additions & 0 deletions src/Components/Server/src/Circuits/DefaultCircuitFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ public override CircuitHost CreateCircuitHost(
var scope = _scopeFactory.CreateScope();
var encoder = scope.ServiceProvider.GetRequiredService<HtmlEncoder>();
var jsRuntime = (RemoteJSRuntime)scope.ServiceProvider.GetRequiredService<IJSRuntime>();
var componentContext = (RemoteComponentContext)scope.ServiceProvider.GetRequiredService<IComponentContext>();
jsRuntime.Initialize(client);
componentContext.Initialize(client);

var uriHelper = (RemoteUriHelper)scope.ServiceProvider.GetRequiredService<IUriHelper>();
if (client != CircuitClientProxy.OfflineClient)
Expand Down
20 changes: 20 additions & 0 deletions src/Components/Server/src/Circuits/RemoteComponentContext.cs
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
Expand Up @@ -44,6 +44,7 @@ public static IServiceCollection AddRazorComponents(this IServiceCollection serv
// Standard razor component services implementations
services.AddScoped<IUriHelper, RemoteUriHelper>();
services.AddScoped<IJSRuntime, RemoteJSRuntime>();
services.AddScoped<IComponentContext, RemoteComponentContext>();

return services;
}
Expand Down
46 changes: 46 additions & 0 deletions src/Components/Server/test/Circuits/RemoteComponentContextTest.cs
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>
14 changes: 14 additions & 0 deletions src/Components/test/testassets/TestServer/Startup.cs
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;
Expand Down Expand Up @@ -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");
});
});
Copy link
Member

Choose a reason for hiding this comment

The 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

Copy link
Member Author

Choose a reason for hiding this comment

The 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)
Expand Down
3 changes: 2 additions & 1 deletion src/Components/test/testassets/TestServer/TestServer.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
<AddRazorSupportForMvc>true</AddRazorSupportForMvc>
</PropertyGroup>

<ItemGroup>
Expand Down