Skip to content

[Blazor] Add IRazorComponentsBuilder.AddWebAssemblyComponents() #48664

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

Merged
merged 13 commits into from
Jun 15, 2023
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
19 changes: 19 additions & 0 deletions AspNetCore.sln
Original file line number Diff line number Diff line change
Expand Up @@ -1786,6 +1786,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Authen
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IdentitySample.ApiEndpoints", "src\Identity\samples\IdentitySample.ApiEndpoints\IdentitySample.ApiEndpoints.csproj", "{37FC77EA-AC44-4D08-B002-8EFF415C424A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Components.WasmMinimal", "src\Components\test\testassets\Components.WasmMinimal\Components.WasmMinimal.csproj", "{87D58D50-20D1-4091-88C5-8D88DCCC2DE3}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -10739,6 +10741,22 @@ Global
{37FC77EA-AC44-4D08-B002-8EFF415C424A}.Release|x64.Build.0 = Release|Any CPU
{37FC77EA-AC44-4D08-B002-8EFF415C424A}.Release|x86.ActiveCfg = Release|Any CPU
{37FC77EA-AC44-4D08-B002-8EFF415C424A}.Release|x86.Build.0 = Release|Any CPU
{87D58D50-20D1-4091-88C5-8D88DCCC2DE3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{87D58D50-20D1-4091-88C5-8D88DCCC2DE3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{87D58D50-20D1-4091-88C5-8D88DCCC2DE3}.Debug|arm64.ActiveCfg = Debug|Any CPU
{87D58D50-20D1-4091-88C5-8D88DCCC2DE3}.Debug|arm64.Build.0 = Debug|Any CPU
{87D58D50-20D1-4091-88C5-8D88DCCC2DE3}.Debug|x64.ActiveCfg = Debug|Any CPU
{87D58D50-20D1-4091-88C5-8D88DCCC2DE3}.Debug|x64.Build.0 = Debug|Any CPU
{87D58D50-20D1-4091-88C5-8D88DCCC2DE3}.Debug|x86.ActiveCfg = Debug|Any CPU
{87D58D50-20D1-4091-88C5-8D88DCCC2DE3}.Debug|x86.Build.0 = Debug|Any CPU
{87D58D50-20D1-4091-88C5-8D88DCCC2DE3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{87D58D50-20D1-4091-88C5-8D88DCCC2DE3}.Release|Any CPU.Build.0 = Release|Any CPU
{87D58D50-20D1-4091-88C5-8D88DCCC2DE3}.Release|arm64.ActiveCfg = Release|Any CPU
{87D58D50-20D1-4091-88C5-8D88DCCC2DE3}.Release|arm64.Build.0 = Release|Any CPU
{87D58D50-20D1-4091-88C5-8D88DCCC2DE3}.Release|x64.ActiveCfg = Release|Any CPU
{87D58D50-20D1-4091-88C5-8D88DCCC2DE3}.Release|x64.Build.0 = Release|Any CPU
{87D58D50-20D1-4091-88C5-8D88DCCC2DE3}.Release|x86.ActiveCfg = Release|Any CPU
{87D58D50-20D1-4091-88C5-8D88DCCC2DE3}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -11621,6 +11639,7 @@ Global
{56291265-B7BF-4756-92AB-FC30F09381D1} = {822D1519-77F0-484A-B9AB-F694C2CC25F1}
{66FA1041-5556-43A0-9CA3-F9937F085F6E} = {56291265-B7BF-4756-92AB-FC30F09381D1}
{37FC77EA-AC44-4D08-B002-8EFF415C424A} = {64B2A28F-6D82-4F2B-B0BB-88DE5216DD2C}
{87D58D50-20D1-4091-88C5-8D88DCCC2DE3} = {6126DCE4-9692-4EE2-B240-C65743572995}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3E8720B3-DBDD-498C-B383-2CC32A054E8F}
Expand Down
3 changes: 2 additions & 1 deletion src/Components/Components.slnf
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
"src\\Components\\test\\E2ETest\\Microsoft.AspNetCore.Components.E2ETests.csproj",
"src\\Components\\test\\testassets\\BasicTestApp\\BasicTestApp.csproj",
"src\\Components\\test\\testassets\\Components.TestServer\\Components.TestServer.csproj",
"src\\Components\\test\\testassets\\Components.WasmMinimal\\Components.WasmMinimal.csproj",
"src\\Components\\test\\testassets\\ComponentsApp.App\\ComponentsApp.App.csproj",
"src\\Components\\test\\testassets\\ComponentsApp.Server\\ComponentsApp.Server.csproj",
"src\\Components\\test\\testassets\\GlobalizationWasmApp\\GlobalizationWasmApp.csproj",
Expand Down Expand Up @@ -148,4 +149,4 @@
"src\\WebEncoders\\src\\Microsoft.Extensions.WebEncoders.csproj"
]
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.AspNetCore.Http;

namespace Microsoft.AspNetCore.Components.Endpoints;

/// <summary>
/// Options to configure interactive WebAssembly components.
/// </summary>
public sealed class WebAssemblyComponentsEndpointOptions
{
/// <summary>
/// Gets or sets the <see cref="PathString"/> that indicates the prefix for Blazor WebAssembly assets.
/// This path must correspond to a referenced Blazor WebAssembly application project.
/// </summary>
public PathString PathPrefix { get; set; }
}
6 changes: 5 additions & 1 deletion src/Components/Endpoints/src/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ Microsoft.AspNetCore.Components.Endpoints.RenderModeEndpointProvider.RenderModeE
Microsoft.AspNetCore.Components.Endpoints.RootComponentMetadata
Microsoft.AspNetCore.Components.Endpoints.RootComponentMetadata.RootComponentMetadata(System.Type! rootComponentType) -> void
Microsoft.AspNetCore.Components.Endpoints.RootComponentMetadata.Type.get -> System.Type!
Microsoft.AspNetCore.Components.Endpoints.WebAssemblyComponentsEndpointOptions
Microsoft.AspNetCore.Components.Endpoints.WebAssemblyComponentsEndpointOptions.PathPrefix.get -> Microsoft.AspNetCore.Http.PathString
Microsoft.AspNetCore.Components.Endpoints.WebAssemblyComponentsEndpointOptions.PathPrefix.set -> void
Microsoft.AspNetCore.Components.Endpoints.WebAssemblyComponentsEndpointOptions.WebAssemblyComponentsEndpointOptions() -> void
Microsoft.AspNetCore.Components.Infrastructure.RazorComponentApplicationAttribute
Microsoft.AspNetCore.Components.Infrastructure.RazorComponentApplicationAttribute.RazorComponentApplicationAttribute() -> void
Microsoft.AspNetCore.Components.PersistedStateSerializationMode
Expand All @@ -91,4 +95,4 @@ static Microsoft.AspNetCore.Builder.RazorComponentsEndpointRouteBuilderExtension
static Microsoft.AspNetCore.Components.Discovery.ComponentApplicationBuilder.GetBuilder<TComponent>() -> Microsoft.AspNetCore.Components.Discovery.ComponentApplicationBuilder?
static Microsoft.Extensions.DependencyInjection.RazorComponentsServiceCollectionExtensions.AddRazorComponents(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services) -> Microsoft.AspNetCore.Components.Endpoints.IRazorComponentsBuilder!
static readonly Microsoft.AspNetCore.Components.Endpoints.RazorComponentResultExecutor.DefaultContentType -> string!
virtual Microsoft.AspNetCore.Components.Endpoints.RazorComponentResultExecutor.ExecuteAsync(Microsoft.AspNetCore.Http.HttpContext! httpContext, Microsoft.AspNetCore.Components.Endpoints.RazorComponentResult! result) -> System.Threading.Tasks.Task!
virtual Microsoft.AspNetCore.Components.Endpoints.RazorComponentResultExecutor.ExecuteAsync(Microsoft.AspNetCore.Http.HttpContext! httpContext, Microsoft.AspNetCore.Components.Endpoints.RazorComponentResult! result) -> System.Threading.Tasks.Task!
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
</PropertyGroup>

<ItemGroup>
<Reference Include="Microsoft.AspNetCore.Components.Endpoints" />
<Reference Include="Microsoft.AspNetCore.StaticFiles" />
<Reference Include="Microsoft.NETCore.BrowserDebugHost.Transport" GeneratePathProperty="true" PrivateAssets="All" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
#nullable enable
Microsoft.AspNetCore.Components.WebAssembly.Server.TargetPickerUi.DisplayFirefox(Microsoft.AspNetCore.Http.HttpContext! context) -> System.Threading.Tasks.Task!
Microsoft.Extensions.DependencyInjection.RazorComponentsBuilderExtensions
static Microsoft.Extensions.DependencyInjection.RazorComponentsBuilderExtensions.AddWebAssemblyComponents(this Microsoft.AspNetCore.Components.Endpoints.IRazorComponentsBuilder! builder, System.Action<Microsoft.AspNetCore.Components.Endpoints.WebAssemblyComponentsEndpointOptions!>? configure = null) -> Microsoft.AspNetCore.Components.Endpoints.IRazorComponentsBuilder!
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Endpoints;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;

namespace Microsoft.Extensions.DependencyInjection;

/// <summary>
/// Extension methods to configure an <see cref="IServiceCollection"/> for WebAssembly components.
/// </summary>
public static class RazorComponentsBuilderExtensions
{
/// <summary>
/// Adds services to support rendering interactive WebAssembly components.
/// </summary>
/// <param name="builder">The <see cref="IRazorComponentsBuilder"/>.</param>
/// <param name="configure">A callback to configure <see cref="WebAssemblyComponentsEndpointOptions"/>.</param>
/// <returns>An <see cref="IRazorComponentsBuilder"/> that can be used to further customize the configuration.</returns>
public static IRazorComponentsBuilder AddWebAssemblyComponents(this IRazorComponentsBuilder builder, Action<WebAssemblyComponentsEndpointOptions>? configure = null)
{
ArgumentNullException.ThrowIfNull(builder, nameof(builder));

builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<RenderModeEndpointProvider, WebAssemblyEndpointProvider>());

if (configure is not null)
{
builder.Services.Configure(configure);
}

return builder;
}

private class WebAssemblyEndpointProvider : RenderModeEndpointProvider
{
private readonly IServiceProvider _services;
private readonly WebAssemblyComponentsEndpointOptions _options;

public WebAssemblyEndpointProvider(IServiceProvider services, IOptions<WebAssemblyComponentsEndpointOptions> options)
{
_services = services;
_options = options.Value;
}

public override IEnumerable<RouteEndpointBuilder> GetEndpointBuilders(IComponentRenderMode renderMode, IApplicationBuilder applicationBuilder)
{
var endpointRouteBuilder = new EndpointRouteBuilder(_services, applicationBuilder);
var pathPrefix = _options.PathPrefix;

applicationBuilder.UseBlazorFrameworkFiles(pathPrefix);
var app = applicationBuilder.Build();

endpointRouteBuilder.Map($"{pathPrefix}/_framework/{{*path}}", context =>
{
// Set endpoint to null so the static files middleware will handle the request.
context.SetEndpoint(null);

return app(context);
});

return endpointRouteBuilder.GetEndpoints();
}

public override bool Supports(IComponentRenderMode renderMode)
=> renderMode is WebAssemblyRenderMode or AutoRenderMode;

private class EndpointRouteBuilder : IEndpointRouteBuilder
{
private readonly IApplicationBuilder _applicationBuilder;

public EndpointRouteBuilder(IServiceProvider serviceProvider, IApplicationBuilder applicationBuilder)
{
ServiceProvider = serviceProvider;
_applicationBuilder = applicationBuilder;
}

public IServiceProvider ServiceProvider { get; }

public ICollection<EndpointDataSource> DataSources { get; } = new List<EndpointDataSource>() { };

public IApplicationBuilder CreateApplicationBuilder()
{
return _applicationBuilder.New();
}

internal IEnumerable<RouteEndpointBuilder> GetEndpoints()
{
foreach (var ds in DataSources)
{
foreach (var endpoint in ds.Endpoints)
{
var routeEndpoint = (RouteEndpoint)endpoint;
var builder = new RouteEndpointBuilder(endpoint.RequestDelegate, routeEndpoint.RoutePattern, routeEndpoint.Order);
for (var i = 0; i < routeEndpoint.Metadata.Count; i++)
{
var metadata = routeEndpoint.Metadata[i];
builder.Metadata.Add(metadata);
}

yield return builder;
}
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Components.TestServer.RazorComponents;
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure;
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
using Microsoft.AspNetCore.E2ETesting;
using OpenQA.Selenium;
using TestServer;
using Xunit.Abstractions;

namespace Microsoft.AspNetCore.Components.E2ETests.ServerRenderingTests;

public class InteractivityTest : ServerTestBase<BasicTestAppServerSiteFixture<RazorComponentEndpointsStartup<App>>>
{
public InteractivityTest(
BrowserFixture browserFixture,
BasicTestAppServerSiteFixture<RazorComponentEndpointsStartup<App>> serverFixture,
ITestOutputHelper output)
: base(browserFixture, serverFixture, output)
{
}

public override Task InitializeAsync()
=> InitializeAsync(BrowserFixture.StreamingContext);

[Fact]
public void CanRenderInteractiveServerComponent()
{
// '2' configures the increment amount.
Navigate($"{ServerPathBase}/interactive?server=2");

Browser.Equal("0", () => Browser.FindElement(By.Id("count-server")).Text);
Browser.Equal("True", () => Browser.FindElement(By.Id("is-interactive-server")).Text);

Browser.Click(By.Id("increment-server"));

Browser.Equal("2", () => Browser.FindElement(By.Id("count-server")).Text);
}

[Fact]
public void CanRenderInteractiveServerComponentFromRazorClassLibrary()
{
// '3' configures the increment amount.
Navigate($"{ServerPathBase}/interactive?server-shared=3");

Browser.Equal("0", () => Browser.FindElement(By.Id("count-server-shared")).Text);
Browser.Equal("True", () => Browser.FindElement(By.Id("is-interactive-server-shared")).Text);

Browser.Click(By.Id("increment-server-shared"));

Browser.Equal("3", () => Browser.FindElement(By.Id("count-server-shared")).Text);
}

[Fact]
public void CanRenderInteractiveWebAssemblyComponentFromRazorClassLibrary()
{
// '4' configures the increment amount.
Navigate($"{ServerPathBase}/interactive?wasm-shared=4");

Browser.Equal("0", () => Browser.FindElement(By.Id("count-wasm-shared")).Text);
Browser.Equal("True", () => Browser.FindElement(By.Id("is-interactive-wasm-shared")).Text);

Browser.Click(By.Id("increment-wasm-shared"));

Browser.Equal("4", () => Browser.FindElement(By.Id("count-wasm-shared")).Text);
}

[Fact]
public void CanRenderInteractiveServerAndWebAssemblyComponentsAtTheSameTime()
{
// '3' and '5' configure the increment amounts.
Navigate($"{ServerPathBase}/interactive?server-shared=3&wasm-shared=5");

Browser.Equal("0", () => Browser.FindElement(By.Id("count-server-shared")).Text);
Browser.Equal("0", () => Browser.FindElement(By.Id("count-wasm-shared")).Text);
Browser.Equal("True", () => Browser.FindElement(By.Id("is-interactive-server-shared")).Text);
Browser.Equal("True", () => Browser.FindElement(By.Id("is-interactive-wasm-shared")).Text);

Browser.Click(By.Id("increment-server-shared"));
Browser.Click(By.Id("increment-wasm-shared"));

Browser.Equal("3", () => Browser.FindElement(By.Id("count-server-shared")).Text);
Browser.Equal("5", () => Browser.FindElement(By.Id("count-wasm-shared")).Text);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

<ItemGroup>
<ProjectReference Include="..\BasicTestApp\BasicTestApp.csproj" />
<ProjectReference Include="..\Components.WasmMinimal\Components.WasmMinimal.csproj" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,12 @@ public RazorComponentEndpointsStartup(IConfiguration configuration)
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorComponents();
services.AddRazorComponents()
.AddServerComponents()
.AddWebAssemblyComponents(options =>
{
options.PathPrefix = "/WasmMinimal";
});
services.AddHttpContextAccessor();
services.AddSingleton<AsyncOperationService>();
}
Expand All @@ -41,10 +46,13 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)

app.Map("/subdir", app =>
{
app.UseStaticFiles();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorComponents<TRootComponent>();
endpoints.MapRazorComponents<TRootComponent>()
.AddServerRenderMode()
.AddWebAssemblyRenderMode();

StreamingRendering.MapEndpoints(endpoints);
StreamingRenderingForm.MapEndpoints(endpoints);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,15 @@
</LayoutView>
</NotFound>
</Router>
<script src="_framework/blazor.web.js" suppress-error="BL9992"></script>
<script src="_framework/blazor.web.js" autostart="false" suppress-error="BL9992"></script>
<script suppress-error="BL9992">
Blazor.start({
webAssembly: {
loadBootResource: function (type, name, defaultUri, integrity) {
return `WasmMinimal/_framework/${name}`;
},
},
});
</script>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
@attribute [RenderModeServer]

<strong>Server counter</strong>
<TestContentPackage.Counter IncrementAmount="IncrementAmount" IdSuffix="server" />

@code {
[Parameter]
public int IncrementAmount { get; set; } = 1;
}
Loading