* 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);
|
||||
}
|
||||
public partial interface IHostEnvironmentAuthenticationStateProvider
|
||||
{
|
||||
void SetAuthenticationState(System.Threading.Tasks.Task<Microsoft.AspNetCore.Components.AuthenticationState> authenticationStateTask);
|
||||
}
|
||||
[System.AttributeUsageAttribute(System.AttributeTargets.Property, AllowMultiple=false, Inherited=true)]
|
||||
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.Logging;
|
||||
using Microsoft.JSInterop;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Server.Circuits
|
||||
{
|
||||
|
|
@ -50,9 +51,12 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
|||
jsRuntime.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>();
|
||||
(authenticationStateProvider as FixedAuthenticationStateProvider)?.Initialize(httpContext.User);
|
||||
var authenticationStateProvider = scope.ServiceProvider.GetService<AuthenticationStateProvider>() as IHostEnvironmentAuthenticationStateProvider;
|
||||
if (authenticationStateProvider != null)
|
||||
{
|
||||
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 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<INavigationInterception, RemoteNavigationInterception>();
|
||||
services.AddScoped<IComponentContext, RemoteComponentContext>();
|
||||
services.AddScoped<AuthenticationStateProvider, FixedAuthenticationStateProvider>();
|
||||
services.AddScoped<AuthenticationStateProvider, ServerAuthenticationStateProvider>();
|
||||
|
||||
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
|
||||
@inject IComponentContext ComponentContext
|
||||
|
||||
<h1>Hello</h1>
|
||||
<CascadingAuthenticationState>
|
||||
<h1>Hello</h1>
|
||||
|
||||
<p>
|
||||
Current state:
|
||||
<strong id="connected-state">@(ComponentContext.IsConnected ? "connected" : "not connected")</strong>
|
||||
</p>
|
||||
<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>
|
||||
<p>
|
||||
Clicks:
|
||||
<strong id="count">@count</strong>
|
||||
<button id="increment-count" @onclick="@(() => count++)">Click me</button>
|
||||
</p>
|
||||
</CascadingAuthenticationState>
|
||||
|
||||
@code {
|
||||
int count;
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
<base href="~/" />
|
||||
</head>
|
||||
<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
|
||||
|
|
@ -19,17 +19,17 @@
|
|||
|
||||
<script src="_framework/blazor.server.js" autostart="false"></script>
|
||||
<script>
|
||||
// Used by InteropOnInitializationComponent
|
||||
function setElementValue(element, newValue) {
|
||||
element.value = newValue;
|
||||
return element.value;
|
||||
}
|
||||
// Used by InteropOnInitializationComponent
|
||||
function setElementValue(element, newValue) {
|
||||
element.value = newValue;
|
||||
return element.value;
|
||||
}
|
||||
|
||||
function start() {
|
||||
Blazor.start({
|
||||
logLevel: 1 // LogLevel.Debug
|
||||
});
|
||||
}
|
||||
function start() {
|
||||
Blazor.start({
|
||||
logLevel: 1 // LogLevel.Debug
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using System.Threading.Tasks;
|
||||
using BasicTestApp;
|
||||
using BasicTestApp.RouterTest;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
|
|
@ -96,7 +97,7 @@ namespace TestServer
|
|||
app.UseEndpoints(endpoints =>
|
||||
{
|
||||
endpoints.MapFallbackToPage("/PrerenderedHost");
|
||||
endpoints.MapBlazorHub();
|
||||
endpoints.MapBlazorHub<TestRouter>(selector: "app");
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponents
|
|||
HttpContext httpContext,
|
||||
Type componentType)
|
||||
{
|
||||
InitializeUriHelper(httpContext);
|
||||
InitializeStandardComponentServices(httpContext);
|
||||
var loggerFactory = (ILoggerFactory)httpContext.RequestServices.GetService(typeof (ILoggerFactory));
|
||||
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
|
||||
// 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)
|
||||
{
|
||||
_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>();
|
||||
helper.InitializeState(GetFullUri(httpContext.Request), GetContextBaseUri(httpContext.Request));
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue