Skip to content

Commit d622921

Browse files
authored
[Blazor] Update discovery APIs and automatically configure the endpoints based on the discovered RenderModes (#48283)
* Removes IRazorComponentApplication<TRootComponent> * Adds APIs for component discovery across assemblies. * Adds support for wiring up server endpoints. * Adds support for automatic render mode detection.
1 parent d9b636c commit d622921

36 files changed

+1878
-177
lines changed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using Microsoft.AspNetCore.Components.Discovery;
5+
6+
namespace Microsoft.AspNetCore.Components.Infrastructure;
7+
8+
/// <summary>
9+
/// Indicates how to collect the components that are part of a razor components
10+
/// application.
11+
/// </summary>
12+
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false)]
13+
public abstract class RazorComponentApplicationAttribute : Attribute, IRazorComponentApplication
14+
{
15+
/// <summary>
16+
/// Creates a builder that can be used to customize the definition of the application.
17+
/// For example, to add or remove pages, change routes, etc.
18+
/// </summary>
19+
/// <returns>
20+
/// The <see cref="ComponentApplicationBuilder"/> associated with the application definition.
21+
/// </returns>
22+
public abstract ComponentApplicationBuilder GetBuilder();
23+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using Microsoft.AspNetCore.Builder;
5+
6+
namespace Microsoft.AspNetCore.Components.Endpoints;
7+
8+
/// <summary>
9+
/// Options associated with the endpoints defined by the components in the
10+
/// given <see cref="RazorComponentsEndpointRouteBuilderExtensions.MapRazorComponents{TRootComponent}(Microsoft.AspNetCore.Routing.IEndpointRouteBuilder)"/>
11+
/// invocation.
12+
/// </summary>
13+
public class RazorComponentDataSourceOptions
14+
{
15+
/// <summary>
16+
/// Gets or sets whether to automatically wire up the necessary endpoints
17+
/// based on the declared render modes of the components that are
18+
/// part of this set of endpoints.
19+
/// </summary>
20+
/// <remarks>
21+
/// The default value is <c>true</c>.
22+
/// </remarks>
23+
public bool UseDeclaredRenderModes { get; set; } = true;
24+
25+
internal IList<IComponentRenderMode> ConfiguredRenderModes { get; } = new List<IComponentRenderMode>();
26+
}

src/Components/Endpoints/src/Builder/RazorComponentEndpointConventionBuilder.cs

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4-
using Microsoft.AspNetCore.Components;
4+
using Microsoft.AspNetCore.Components.Discovery;
5+
using Microsoft.AspNetCore.Components.Endpoints;
6+
using Microsoft.AspNetCore.Components.Web;
57

68
namespace Microsoft.AspNetCore.Builder;
79

@@ -15,21 +17,69 @@ public class RazorComponentEndpointConventionBuilder : IEndpointConventionBuilde
1517
{
1618
private readonly object _lock;
1719
private readonly ComponentApplicationBuilder _builder;
20+
private readonly RazorComponentDataSourceOptions _options;
1821
private readonly List<Action<EndpointBuilder>> _conventions;
1922
private readonly List<Action<EndpointBuilder>> _finallyConventions;
2023

2124
internal RazorComponentEndpointConventionBuilder(
2225
object @lock,
2326
ComponentApplicationBuilder builder,
27+
RazorComponentDataSourceOptions options,
2428
List<Action<EndpointBuilder>> conventions,
2529
List<Action<EndpointBuilder>> finallyConventions)
2630
{
2731
_lock = @lock;
2832
_builder = builder;
33+
_options = options;
2934
_conventions = conventions;
3035
_finallyConventions = finallyConventions;
3136
}
3237

38+
/// <summary>
39+
/// Gets the <see cref="ComponentApplicationBuilder"/> that is used to build the endpoints.
40+
/// </summary>
41+
public ComponentApplicationBuilder ApplicationBuilder => _builder;
42+
43+
/// <summary>
44+
/// Configures the <see cref="RenderMode.WebAssembly"/> for this application.
45+
/// </summary>
46+
/// <returns>The <see cref="RazorComponentEndpointConventionBuilder"/>.</returns>
47+
public RazorComponentEndpointConventionBuilder AddWebAssemblyRenderMode()
48+
{
49+
for (var i = 0; i < _options.ConfiguredRenderModes.Count; i++)
50+
{
51+
var mode = _options.ConfiguredRenderModes[i];
52+
if (mode is WebAssemblyRenderMode)
53+
{
54+
return this;
55+
}
56+
}
57+
58+
_options.ConfiguredRenderModes.Add(RenderMode.WebAssembly);
59+
60+
return this;
61+
}
62+
63+
/// <summary>
64+
/// Configures the <see cref="RenderMode.Server"/> for this application.
65+
/// </summary>
66+
/// <returns>The <see cref="RazorComponentEndpointConventionBuilder"/>.</returns>
67+
public RazorComponentEndpointConventionBuilder AddServerRenderMode()
68+
{
69+
for (var i = 0; i < _options.ConfiguredRenderModes.Count; i++)
70+
{
71+
var mode = _options.ConfiguredRenderModes[i];
72+
if (mode is ServerRenderMode)
73+
{
74+
return this;
75+
}
76+
}
77+
78+
_options.ConfiguredRenderModes.Add(RenderMode.Server);
79+
80+
return this;
81+
}
82+
3383
/// <inheritdoc/>
3484
public void Add(Action<EndpointBuilder> convention)
3585
{

src/Components/Endpoints/src/Builder/RazorComponentEndpointDataSource.cs

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System.Diagnostics;
5+
using System.Linq;
56
using Microsoft.AspNetCore.Builder;
7+
using Microsoft.AspNetCore.Components.Discovery;
68
using Microsoft.AspNetCore.Http;
79
using Microsoft.AspNetCore.Routing;
810
using Microsoft.Extensions.Primitives;
@@ -14,6 +16,11 @@ internal class RazorComponentEndpointDataSource<TRootComponent> : EndpointDataSo
1416
private readonly object _lock = new();
1517
private readonly List<Action<EndpointBuilder>> _conventions = new();
1618
private readonly List<Action<EndpointBuilder>> _finallyConventions = new();
19+
private readonly RazorComponentDataSourceOptions _options = new();
20+
private readonly ComponentApplicationBuilder _builder;
21+
private readonly IApplicationBuilder _applicationBuilder;
22+
private readonly RenderModeEndpointProvider[] _renderModeEndpointProviders;
23+
private readonly RazorComponentEndpointFactory _factory;
1724

1825
private List<Endpoint>? _endpoints;
1926
// TODO: Implement endpoint data source updates https://github.com/dotnet/aspnetcore/issues/47026
@@ -22,23 +29,25 @@ internal class RazorComponentEndpointDataSource<TRootComponent> : EndpointDataSo
2229

2330
public RazorComponentEndpointDataSource(
2431
ComponentApplicationBuilder builder,
32+
IEnumerable<RenderModeEndpointProvider> renderModeEndpointProviders,
33+
IApplicationBuilder applicationBuilder,
2534
RazorComponentEndpointFactory factory)
2635
{
2736
_builder = builder;
37+
_applicationBuilder = applicationBuilder;
38+
_renderModeEndpointProviders = renderModeEndpointProviders.ToArray();
2839
_factory = factory;
2940
DefaultBuilder = new RazorComponentEndpointConventionBuilder(
3041
_lock,
3142
builder,
43+
_options,
3244
_conventions,
3345
_finallyConventions);
3446

3547
_cancellationTokenSource = new CancellationTokenSource();
3648
_changeToken = new CancellationChangeToken(_cancellationTokenSource.Token);
3749
}
3850

39-
private readonly ComponentApplicationBuilder _builder;
40-
private readonly RazorComponentEndpointFactory _factory;
41-
4251
internal RazorComponentEndpointConventionBuilder DefaultBuilder { get; }
4352

4453
public override IReadOnlyList<Endpoint> Endpoints
@@ -59,6 +68,8 @@ public override IReadOnlyList<Endpoint> Endpoints
5968
}
6069
}
6170

71+
internal RazorComponentDataSourceOptions Options => _options;
72+
6273
private void Initialize()
6374
{
6475
if (_endpoints == null)
@@ -77,14 +88,49 @@ private void UpdateEndpoints()
7788
{
7889
var endpoints = new List<Endpoint>();
7990
var context = _builder.Build();
91+
8092
foreach (var definition in context.Pages)
8193
{
8294
_factory.AddEndpoints(endpoints, typeof(TRootComponent), definition, _conventions, _finallyConventions);
8395
}
8496

97+
ICollection<IComponentRenderMode> renderModes = Options.ConfiguredRenderModes;
98+
99+
if (Options.UseDeclaredRenderModes)
100+
{
101+
var componentRenderModes = context.GetDeclaredRenderModesByDiscoveredComponents();
102+
componentRenderModes.UnionWith(Options.ConfiguredRenderModes);
103+
renderModes = componentRenderModes;
104+
}
105+
106+
foreach (var renderMode in renderModes)
107+
{
108+
var found = false;
109+
foreach (var provider in _renderModeEndpointProviders)
110+
{
111+
if (provider.Supports(renderMode))
112+
{
113+
found = true;
114+
RenderModeEndpointProvider.AddEndpoints(
115+
endpoints,
116+
typeof(TRootComponent),
117+
provider.GetEndpointBuilders(renderMode, _applicationBuilder.New()),
118+
renderMode,
119+
_conventions,
120+
_finallyConventions);
121+
}
122+
}
123+
124+
if (!found)
125+
{
126+
throw new InvalidOperationException($"Unable to find a provider for the render mode: {renderMode.GetType().FullName}. This generally" +
127+
$"means that a call to 'AddWebAssemblyComponents' or 'AddServerComponents' is missing. " +
128+
$"Alternatively call 'AddWebAssemblyRenderMode', 'AddServerRenderMode' might be missing if you have set UseDeclaredRenderModes = false.");
129+
}
130+
}
131+
85132
_endpoints = endpoints;
86133
}
87-
88134
public override IChangeToken GetChangeToken()
89135
{
90136
// TODO: Handle updates if necessary (for hot reload).
Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,30 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using Microsoft.AspNetCore.Components.Discovery;
45
using Microsoft.AspNetCore.Components.Endpoints;
6+
using Microsoft.AspNetCore.Routing;
57

68
namespace Microsoft.AspNetCore.Components.Infrastructure;
79

810
internal class RazorComponentEndpointDataSourceFactory
911
{
1012
private readonly RazorComponentEndpointFactory _factory;
13+
private readonly IEnumerable<RenderModeEndpointProvider> _providers;
1114

12-
public RazorComponentEndpointDataSourceFactory(RazorComponentEndpointFactory factory)
15+
public RazorComponentEndpointDataSourceFactory(
16+
RazorComponentEndpointFactory factory,
17+
IEnumerable<RenderModeEndpointProvider> providers)
1318
{
1419
_factory = factory;
20+
_providers = providers;
1521
}
1622

17-
public RazorComponentEndpointDataSource<TRootComponent> CreateDataSource<TRootComponent>()
18-
where TRootComponent : IRazorComponentApplication<TRootComponent>
23+
public RazorComponentEndpointDataSource<TRootComponent> CreateDataSource<TRootComponent>(IEndpointRouteBuilder endpoints)
1924
{
20-
var builder = TRootComponent.GetBuilder();
21-
return new RazorComponentEndpointDataSource<TRootComponent>(builder, _factory);
25+
var builder = ComponentApplicationBuilder.GetBuilder<TRootComponent>() ??
26+
DefaultRazorComponentApplication<TRootComponent>.Instance.GetBuilder();
27+
28+
return new RazorComponentEndpointDataSource<TRootComponent>(builder, _providers, endpoints.CreateApplicationBuilder(), _factory);
2229
}
2330
}

src/Components/Endpoints/src/Builder/RazorComponentEndpointFactory.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using Microsoft.AspNetCore.Builder;
5+
using Microsoft.AspNetCore.Components.Discovery;
56
using Microsoft.AspNetCore.Http;
67
using Microsoft.AspNetCore.Routing;
78
using Microsoft.AspNetCore.Routing.Patterns;
@@ -17,7 +18,7 @@ internal void AddEndpoints(
1718
#pragma warning restore CA1822 // It's a singleton
1819
List<Endpoint> endpoints,
1920
Type rootComponent,
20-
PageDefinition pageDefinition,
21+
PageComponentInfo pageDefinition,
2122
IReadOnlyList<Action<EndpointBuilder>> conventions,
2223
IReadOnlyList<Action<EndpointBuilder>> finallyConventions)
2324
{

src/Components/Endpoints/src/Builder/RazorComponentsEndpointRouteBuilderExtensions.cs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System.Linq;
5-
using Microsoft.AspNetCore.Components;
65
using Microsoft.AspNetCore.Components.Endpoints;
76
using Microsoft.AspNetCore.Components.Infrastructure;
87
using Microsoft.AspNetCore.Http;
@@ -24,7 +23,6 @@ public static class RazorComponentsEndpointRouteBuilderExtensions
2423
/// <param name="endpoints"></param>
2524
/// <returns></returns>
2625
public static RazorComponentEndpointConventionBuilder MapRazorComponents<TRootComponent>(this IEndpointRouteBuilder endpoints)
27-
where TRootComponent : IRazorComponentApplication<TRootComponent>
2826
{
2927
ArgumentNullException.ThrowIfNull(endpoints);
3028

@@ -66,7 +64,6 @@ private static void AddBlazorWebJsEndpoint(IEndpointRouteBuilder endpoints)
6664
}
6765

6866
private static RazorComponentEndpointDataSource<TRootComponent> GetOrCreateDataSource<TRootComponent>(IEndpointRouteBuilder endpoints)
69-
where TRootComponent : IRazorComponentApplication<TRootComponent>
7067
{
7168
var dataSource = endpoints.DataSources.OfType<RazorComponentEndpointDataSource<TRootComponent>>().FirstOrDefault();
7269
if (dataSource == null)
@@ -75,7 +72,7 @@ private static RazorComponentEndpointDataSource<TRootComponent> GetOrCreateDataS
7572
// sources, once we figure out the exact scenarios for
7673
// https://github.com/dotnet/aspnetcore/issues/46992
7774
var factory = endpoints.ServiceProvider.GetRequiredService<RazorComponentEndpointDataSourceFactory>();
78-
dataSource = factory.CreateDataSource<TRootComponent>();
75+
dataSource = factory.CreateDataSource<TRootComponent>(endpoints);
7976
endpoints.DataSources.Add(dataSource);
8077
}
8178

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using Microsoft.AspNetCore.Builder;
5+
using Microsoft.AspNetCore.Http;
6+
using Microsoft.AspNetCore.Routing;
7+
8+
namespace Microsoft.AspNetCore.Components.Endpoints;
9+
10+
/// <summary>
11+
/// A provider that can register endpoints to support a specific <see cref="IComponentRenderMode"/>.
12+
/// </summary>
13+
public abstract class RenderModeEndpointProvider
14+
{
15+
/// <summary>
16+
/// Determines whether this <see cref="RenderModeEndpointProvider"/> supports the specified <paramref name="renderMode"/>.
17+
/// </summary>
18+
/// <param name="renderMode">The <see cref="IComponentRenderMode"/>.</param>
19+
/// <returns><c>true</c> if the <see cref="IComponentRenderMode"/> is supported; <c>false</c> otherwise.</returns>
20+
public abstract bool Supports(IComponentRenderMode renderMode);
21+
22+
/// <summary>
23+
/// Gets the endpoints for the specified <paramref name="renderMode"/>.
24+
/// </summary>
25+
/// <param name="renderMode">The <see cref="IComponentRenderMode"/>.</param>
26+
/// <param name="applicationBuilder">The <see cref="IApplicationBuilder"/> used to configure non endpoint aware endpoints.</param>
27+
/// <returns>The list of endpoints this provider is registering.</returns>
28+
public abstract IEnumerable<RouteEndpointBuilder> GetEndpointBuilders(
29+
IComponentRenderMode renderMode,
30+
IApplicationBuilder applicationBuilder);
31+
32+
internal static void AddEndpoints(
33+
List<Endpoint> endpoints,
34+
Type rootComponent,
35+
IEnumerable<RouteEndpointBuilder> renderModeEndpoints,
36+
IComponentRenderMode renderMode,
37+
List<Action<EndpointBuilder>> conventions,
38+
List<Action<EndpointBuilder>> finallyConventions)
39+
{
40+
foreach (var builder in renderModeEndpoints)
41+
{
42+
builder.Metadata.Add(new RootComponentMetadata(rootComponent));
43+
builder.Metadata.Add(renderMode);
44+
45+
foreach (var convention in conventions)
46+
{
47+
convention(builder);
48+
}
49+
50+
foreach (var finallyConvention in finallyConventions)
51+
{
52+
finallyConvention(builder);
53+
}
54+
55+
endpoints.Add(builder.Build());
56+
}
57+
}
58+
}

0 commit comments

Comments
 (0)