* Make E2E prerendering test use static prerendering (we no longer need coverage for stateful prerendering) * Use authentication state during static prerendering. This replicates issue #11799 in the E2E test * Initialize the authentication state provider during static prerendering * Update ref assembly * Update unit test
This commit is contained in:
parent
c25a0d1bf5
commit
1950e59714
|
|
@ -359,6 +359,10 @@ namespace Microsoft.AspNetCore.Components
|
||||||
{
|
{
|
||||||
System.Threading.Tasks.Task HandleEventAsync(Microsoft.AspNetCore.Components.EventCallbackWorkItem item, object arg);
|
System.Threading.Tasks.Task HandleEventAsync(Microsoft.AspNetCore.Components.EventCallbackWorkItem item, object arg);
|
||||||
}
|
}
|
||||||
|
public partial interface IHostEnvironmentAuthenticationStateProvider
|
||||||
|
{
|
||||||
|
void SetAuthenticationState(System.Threading.Tasks.Task<Microsoft.AspNetCore.Components.AuthenticationState> authenticationStateTask);
|
||||||
|
}
|
||||||
[System.AttributeUsageAttribute(System.AttributeTargets.Property, AllowMultiple=false, Inherited=true)]
|
[System.AttributeUsageAttribute(System.AttributeTargets.Property, AllowMultiple=false, Inherited=true)]
|
||||||
public sealed partial class InjectAttribute : System.Attribute
|
public sealed partial class InjectAttribute : System.Attribute
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Components
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An interface implemented by <see cref="AuthenticationStateProvider"/> classes that can receive authentication
|
||||||
|
/// state information from the host environment.
|
||||||
|
/// </summary>
|
||||||
|
public interface IHostEnvironmentAuthenticationStateProvider
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Supplies updated authentication state data to the <see cref="AuthenticationStateProvider"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="authenticationStateTask">A task that resolves with the updated <see cref="AuthenticationState"/>.</param>
|
||||||
|
void SetAuthenticationState(Task<AuthenticationState> authenticationStateTask);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -14,6 +14,7 @@ using Microsoft.AspNetCore.Http.Features;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.JSInterop;
|
using Microsoft.JSInterop;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Components.Server.Circuits
|
namespace Microsoft.AspNetCore.Components.Server.Circuits
|
||||||
{
|
{
|
||||||
|
|
@ -50,9 +51,12 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
||||||
jsRuntime.Initialize(client);
|
jsRuntime.Initialize(client);
|
||||||
componentContext.Initialize(client);
|
componentContext.Initialize(client);
|
||||||
|
|
||||||
// You can replace the AuthenticationStateProvider with a custom one, but in that case initialization is up to you
|
var authenticationStateProvider = scope.ServiceProvider.GetService<AuthenticationStateProvider>() as IHostEnvironmentAuthenticationStateProvider;
|
||||||
var authenticationStateProvider = scope.ServiceProvider.GetService<AuthenticationStateProvider>();
|
if (authenticationStateProvider != null)
|
||||||
(authenticationStateProvider as FixedAuthenticationStateProvider)?.Initialize(httpContext.User);
|
{
|
||||||
|
var authenticationState = new AuthenticationState(httpContext.User); // TODO: Get this from the hub connection context instead
|
||||||
|
authenticationStateProvider.SetAuthenticationState(Task.FromResult(authenticationState));
|
||||||
|
}
|
||||||
|
|
||||||
var uriHelper = (RemoteUriHelper)scope.ServiceProvider.GetRequiredService<IUriHelper>();
|
var uriHelper = (RemoteUriHelper)scope.ServiceProvider.GetRequiredService<IUriHelper>();
|
||||||
var navigationInterception = (RemoteNavigationInterception)scope.ServiceProvider.GetRequiredService<INavigationInterception>();
|
var navigationInterception = (RemoteNavigationInterception)scope.ServiceProvider.GetRequiredService<INavigationInterception>();
|
||||||
|
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
// 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.Security.Claims;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.Http;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Components.Server.Circuits
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// An <see cref="AuthenticationStateProvider"/> intended for use in server-side
|
|
||||||
/// Blazor. The circuit factory will supply a <see cref="ClaimsPrincipal"/> from
|
|
||||||
/// the current <see cref="HttpContext.User"/>, which will stay fixed for the
|
|
||||||
/// lifetime of the circuit since <see cref="HttpContext.User"/> cannot change.
|
|
||||||
///
|
|
||||||
/// This can therefore only be used with redirect-style authentication flows,
|
|
||||||
/// since it requires a new HTTP request in order to become a different user.
|
|
||||||
/// </summary>
|
|
||||||
internal class FixedAuthenticationStateProvider : AuthenticationStateProvider
|
|
||||||
{
|
|
||||||
private Task<AuthenticationState> _authenticationStateTask;
|
|
||||||
|
|
||||||
public void Initialize(ClaimsPrincipal user)
|
|
||||||
{
|
|
||||||
_authenticationStateTask = Task.FromResult(new AuthenticationState(user));
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Task<AuthenticationState> GetAuthenticationStateAsync()
|
|
||||||
=> _authenticationStateTask
|
|
||||||
?? throw new InvalidOperationException($"{nameof(GetAuthenticationStateAsync)} was called before {nameof(Initialize)}.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
// 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.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Components.Server.Circuits
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An <see cref="AuthenticationStateProvider"/> intended for use in server-side Blazor.
|
||||||
|
/// </summary>
|
||||||
|
internal class ServerAuthenticationStateProvider : AuthenticationStateProvider, IHostEnvironmentAuthenticationStateProvider
|
||||||
|
{
|
||||||
|
private Task<AuthenticationState> _authenticationStateTask;
|
||||||
|
|
||||||
|
public override Task<AuthenticationState> GetAuthenticationStateAsync()
|
||||||
|
=> _authenticationStateTask
|
||||||
|
?? throw new InvalidOperationException($"{nameof(GetAuthenticationStateAsync)} was called before {nameof(SetAuthenticationState)}.");
|
||||||
|
|
||||||
|
public void SetAuthenticationState(Task<AuthenticationState> authenticationStateTask)
|
||||||
|
{
|
||||||
|
_authenticationStateTask = authenticationStateTask ?? throw new ArgumentNullException(nameof(authenticationStateTask));
|
||||||
|
NotifyAuthenticationStateChanged(_authenticationStateTask);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -76,7 +76,7 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||||
services.AddScoped<IJSRuntime, RemoteJSRuntime>();
|
services.AddScoped<IJSRuntime, RemoteJSRuntime>();
|
||||||
services.AddScoped<INavigationInterception, RemoteNavigationInterception>();
|
services.AddScoped<INavigationInterception, RemoteNavigationInterception>();
|
||||||
services.AddScoped<IComponentContext, RemoteComponentContext>();
|
services.AddScoped<IComponentContext, RemoteComponentContext>();
|
||||||
services.AddScoped<AuthenticationStateProvider, FixedAuthenticationStateProvider>();
|
services.AddScoped<AuthenticationStateProvider, ServerAuthenticationStateProvider>();
|
||||||
|
|
||||||
services.TryAddEnumerable(ServiceDescriptor.Singleton<IConfigureOptions<CircuitOptions>, CircuitOptionsJSInteropDetailedErrorsConfiguration>());
|
services.TryAddEnumerable(ServiceDescriptor.Singleton<IConfigureOptions<CircuitOptions>, CircuitOptionsJSInteropDetailedErrorsConfiguration>());
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
// 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.Security.Claims;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.Components.Server.Circuits;
|
|
||||||
using Microsoft.AspNetCore.Http;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Components.Server.Tests.Circuits
|
|
||||||
{
|
|
||||||
public class FixedAuthenticationStateProviderTest
|
|
||||||
{
|
|
||||||
[Fact]
|
|
||||||
public async Task CannotProvideAuthenticationStateBeforeInitialization()
|
|
||||||
{
|
|
||||||
await Assert.ThrowsAsync<InvalidOperationException>(() =>
|
|
||||||
new FixedAuthenticationStateProvider()
|
|
||||||
.GetAuthenticationStateAsync());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task SuppliesAuthenticationStateWithFixedUser()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var user = new ClaimsPrincipal();
|
|
||||||
var provider = new FixedAuthenticationStateProvider();
|
|
||||||
provider.Initialize(user);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var authenticationState = await provider.GetAuthenticationStateAsync();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.NotNull(authenticationState);
|
|
||||||
Assert.Same(user, authenticationState.User);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
// 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.Security.Claims;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Components.Server.Circuits;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Components.Server.Tests.Circuits
|
||||||
|
{
|
||||||
|
public class ServerAuthenticationStateProviderTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public async Task CannotProvideAuthenticationStateBeforeInitialization()
|
||||||
|
{
|
||||||
|
await Assert.ThrowsAsync<InvalidOperationException>(() =>
|
||||||
|
new ServerAuthenticationStateProvider()
|
||||||
|
.GetAuthenticationStateAsync());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task SuppliesAuthenticationStateWithFixedUser()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var user = new ClaimsPrincipal();
|
||||||
|
var provider = new ServerAuthenticationStateProvider();
|
||||||
|
|
||||||
|
// Act 1
|
||||||
|
var expectedAuthenticationState1 = new AuthenticationState(user);
|
||||||
|
provider.SetAuthenticationState(Task.FromResult(expectedAuthenticationState1));
|
||||||
|
|
||||||
|
// Assert 1
|
||||||
|
var actualAuthenticationState1 = await provider.GetAuthenticationStateAsync();
|
||||||
|
Assert.NotNull(actualAuthenticationState1);
|
||||||
|
Assert.Same(expectedAuthenticationState1, actualAuthenticationState1);
|
||||||
|
|
||||||
|
// Act 2: Show we can update it further
|
||||||
|
var expectedAuthenticationState2 = new AuthenticationState(user);
|
||||||
|
provider.SetAuthenticationState(Task.FromResult(expectedAuthenticationState2));
|
||||||
|
|
||||||
|
// Assert 2
|
||||||
|
var actualAuthenticationState2 = await provider.GetAuthenticationStateAsync();
|
||||||
|
Assert.NotNull(actualAuthenticationState2);
|
||||||
|
Assert.NotSame(actualAuthenticationState1, actualAuthenticationState2);
|
||||||
|
Assert.Same(expectedAuthenticationState2, actualAuthenticationState2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,18 +2,20 @@
|
||||||
@using Microsoft.AspNetCore.Components
|
@using Microsoft.AspNetCore.Components
|
||||||
@inject IComponentContext ComponentContext
|
@inject IComponentContext ComponentContext
|
||||||
|
|
||||||
<h1>Hello</h1>
|
<CascadingAuthenticationState>
|
||||||
|
<h1>Hello</h1>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
Current state:
|
Current state:
|
||||||
<strong id="connected-state">@(ComponentContext.IsConnected ? "connected" : "not connected")</strong>
|
<strong id="connected-state">@(ComponentContext.IsConnected ? "connected" : "not connected")</strong>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
Clicks:
|
Clicks:
|
||||||
<strong id="count">@count</strong>
|
<strong id="count">@count</strong>
|
||||||
<button id="increment-count" @onclick="@(() => count++)">Click me</button>
|
<button id="increment-count" @onclick="@(() => count++)">Click me</button>
|
||||||
</p>
|
</p>
|
||||||
|
</CascadingAuthenticationState>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
int count;
|
int count;
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
<base href="~/" />
|
<base href="~/" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<app>@(await Html.RenderComponentAsync<TestRouter>())</app>
|
<app>@(await Html.RenderStaticComponentAsync<TestRouter>())</app>
|
||||||
|
|
||||||
@*
|
@*
|
||||||
So that E2E tests can make assertions about both the prerendered and
|
So that E2E tests can make assertions about both the prerendered and
|
||||||
|
|
@ -19,17 +19,17 @@
|
||||||
|
|
||||||
<script src="_framework/blazor.server.js" autostart="false"></script>
|
<script src="_framework/blazor.server.js" autostart="false"></script>
|
||||||
<script>
|
<script>
|
||||||
// Used by InteropOnInitializationComponent
|
// Used by InteropOnInitializationComponent
|
||||||
function setElementValue(element, newValue) {
|
function setElementValue(element, newValue) {
|
||||||
element.value = newValue;
|
element.value = newValue;
|
||||||
return element.value;
|
return element.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
function start() {
|
function start() {
|
||||||
Blazor.start({
|
Blazor.start({
|
||||||
logLevel: 1 // LogLevel.Debug
|
logLevel: 1 // LogLevel.Debug
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BasicTestApp;
|
using BasicTestApp;
|
||||||
|
using BasicTestApp.RouterTest;
|
||||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
|
@ -96,7 +97,7 @@ namespace TestServer
|
||||||
app.UseEndpoints(endpoints =>
|
app.UseEndpoints(endpoints =>
|
||||||
{
|
{
|
||||||
endpoints.MapFallbackToPage("/PrerenderedHost");
|
endpoints.MapFallbackToPage("/PrerenderedHost");
|
||||||
endpoints.MapBlazorHub();
|
endpoints.MapBlazorHub<TestRouter>(selector: "app");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponents
|
||||||
HttpContext httpContext,
|
HttpContext httpContext,
|
||||||
Type componentType)
|
Type componentType)
|
||||||
{
|
{
|
||||||
InitializeUriHelper(httpContext);
|
InitializeStandardComponentServices(httpContext);
|
||||||
var loggerFactory = (ILoggerFactory)httpContext.RequestServices.GetService(typeof (ILoggerFactory));
|
var loggerFactory = (ILoggerFactory)httpContext.RequestServices.GetService(typeof (ILoggerFactory));
|
||||||
using (var htmlRenderer = new HtmlRenderer(httpContext.RequestServices, loggerFactory, _encoder.Encode))
|
using (var htmlRenderer = new HtmlRenderer(httpContext.RequestServices, loggerFactory, _encoder.Encode))
|
||||||
{
|
{
|
||||||
|
|
@ -62,15 +62,21 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponents
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InitializeUriHelper(HttpContext httpContext)
|
private void InitializeStandardComponentServices(HttpContext httpContext)
|
||||||
{
|
{
|
||||||
// We don't know here if we are dealing with the default HttpUriHelper registered
|
|
||||||
// by MVC or with the RemoteUriHelper registered by AddComponents.
|
|
||||||
// This might not be the first component in the request we are rendering, so
|
// This might not be the first component in the request we are rendering, so
|
||||||
// we need to check if we already initialized the uri helper in this request.
|
// we need to check if we already initialized the services in this request.
|
||||||
if (!_initialized)
|
if (!_initialized)
|
||||||
{
|
{
|
||||||
_initialized = true;
|
_initialized = true;
|
||||||
|
|
||||||
|
var authenticationStateProvider = httpContext.RequestServices.GetService<AuthenticationStateProvider>() as IHostEnvironmentAuthenticationStateProvider;
|
||||||
|
if (authenticationStateProvider != null)
|
||||||
|
{
|
||||||
|
var authenticationState = new AuthenticationState(httpContext.User);
|
||||||
|
authenticationStateProvider.SetAuthenticationState(Task.FromResult(authenticationState));
|
||||||
|
}
|
||||||
|
|
||||||
var helper = (UriHelperBase)httpContext.RequestServices.GetRequiredService<IUriHelper>();
|
var helper = (UriHelperBase)httpContext.RequestServices.GetRequiredService<IUriHelper>();
|
||||||
helper.InitializeState(GetFullUri(httpContext.Request), GetContextBaseUri(httpContext.Request));
|
helper.InitializeState(GetFullUri(httpContext.Request), GetContextBaseUri(httpContext.Request));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue