Skip to content

[Blazor] Manual reflection based page discovery #49757

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 15 commits into from
Aug 12, 2023
5 changes: 3 additions & 2 deletions src/Components/ComponentsNoDeps.slnf
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,13 @@
"src\\Components\\benchmarkapps\\Wasm.Performance\\TestApp\\Wasm.Performance.TestApp.csproj",
"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",
"src\\Components\\test\\testassets\\LazyTestContentPackage\\LazyTestContentPackage.csproj",
"src\\Components\\test\\testassets\\TestContentPackage\\TestContentPackage.csproj",
"src\\Components\\test\\testassets\\Components.TestServer\\Components.TestServer.csproj"
"src\\Components\\test\\testassets\\TestContentPackage\\TestContentPackage.csproj"
]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace Microsoft.AspNetCore.Components.Endpoints;
/// <summary>
/// Metadata that represents the component associated with an endpoint.
/// </summary>
public class ComponentTypeMetadata
public sealed class ComponentTypeMetadata
{
/// <summary>
/// Initializes a new instance of <see cref="ComponentTypeMetadata"/>.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace Microsoft.AspNetCore.Components.Infrastructure;
/// application.
/// </summary>
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false)]
public abstract class RazorComponentApplicationAttribute : Attribute, IRazorComponentApplication
internal abstract class RazorComponentApplicationAttribute : Attribute, IRazorComponentApplication
{
/// <summary>
/// Creates a builder that can be used to customize the definition of the application.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Components.Web;

namespace Microsoft.AspNetCore.Components.Endpoints;

Expand All @@ -10,17 +11,22 @@ namespace Microsoft.AspNetCore.Components.Endpoints;
/// given <see cref="RazorComponentsEndpointRouteBuilderExtensions.MapRazorComponents{TRootComponent}(Microsoft.AspNetCore.Routing.IEndpointRouteBuilder)"/>
/// invocation.
/// </summary>
public class RazorComponentDataSourceOptions
internal class RazorComponentDataSourceOptions
{
/// <summary>
/// Gets or sets whether to automatically wire up the necessary endpoints
/// based on the declared render modes of the components that are
/// part of this set of endpoints.
/// </summary>
/// <remarks>
/// The default value is <c>true</c>.
/// </remarks>
public bool UseDeclaredRenderModes { get; set; } = true;
internal static readonly EqualityComparer<IComponentRenderMode> RenderModeComparer = EqualityComparer<IComponentRenderMode>
.Create(
equals: (x, y) => (x,y) switch
{
(ServerRenderMode, ServerRenderMode) => true,
(WebAssemblyRenderMode, WebAssemblyRenderMode) => true,
_ => false,
},
getHashCode: obj => obj switch
{
ServerRenderMode => 1,
WebAssemblyRenderMode => 2,
_ => throw new InvalidOperationException($"Unknown render mode: {obj}"),
});

internal IList<IComponentRenderMode> ConfiguredRenderModes { get; } = new List<IComponentRenderMode>();
internal ISet<IComponentRenderMode> ConfiguredRenderModes { get; } = new HashSet<IComponentRenderMode>(RenderModeComparer);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Linq;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Components.Discovery;
using Microsoft.AspNetCore.Components.Endpoints.Infrastructure;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Primitives;
Expand Down Expand Up @@ -39,7 +40,7 @@ public RazorComponentEndpointDataSource(
_applicationBuilder = applicationBuilder;
_renderModeEndpointProviders = renderModeEndpointProviders.ToArray();
_factory = factory;
DefaultBuilder = new RazorComponentEndpointConventionBuilder(
DefaultBuilder = new RazorComponentsEndpointConventionBuilder(
_lock,
builder,
_options,
Expand All @@ -50,7 +51,7 @@ public RazorComponentEndpointDataSource(
_changeToken = new CancellationChangeToken(_cancellationTokenSource.Token);
}

internal RazorComponentEndpointConventionBuilder DefaultBuilder { get; }
internal RazorComponentsEndpointConventionBuilder DefaultBuilder { get; }

public override IReadOnlyList<Endpoint> Endpoints
{
Expand Down Expand Up @@ -98,13 +99,6 @@ private void UpdateEndpoints()

ICollection<IComponentRenderMode> renderModes = Options.ConfiguredRenderModes;

if (Options.UseDeclaredRenderModes)
{
var componentRenderModes = context.GetDeclaredRenderModesByDiscoveredComponents();
componentRenderModes.UnionWith(Options.ConfiguredRenderModes);
renderModes = componentRenderModes;
}

foreach (var renderMode in renderModes)
{
var found = false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Diagnostics.CodeAnalysis;
using Microsoft.AspNetCore.Components.Discovery;
using Microsoft.AspNetCore.Components.Endpoints;
using Microsoft.AspNetCore.Components.Endpoints.Infrastructure;
using Microsoft.AspNetCore.Routing;
using static Microsoft.AspNetCore.Internal.LinkerFlags;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// 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.Components;
using Microsoft.AspNetCore.Components.Discovery;
using Microsoft.AspNetCore.Components.Endpoints;
using Microsoft.AspNetCore.Components.Web;
Expand All @@ -10,18 +11,15 @@ namespace Microsoft.AspNetCore.Builder;
/// <summary>
/// Builds conventions that will be used for customization of <see cref="EndpointBuilder"/> instances.
/// </summary>

// TODO: This will have APIs to add and remove entire assemblies from the list of considered endpoints
// as well as adding/removing individual pages as endpoints.
public class RazorComponentEndpointConventionBuilder : IEndpointConventionBuilder
public sealed class RazorComponentsEndpointConventionBuilder : IEndpointConventionBuilder
{
private readonly object _lock;
private readonly ComponentApplicationBuilder _builder;
private readonly RazorComponentDataSourceOptions _options;
private readonly List<Action<EndpointBuilder>> _conventions;
private readonly List<Action<EndpointBuilder>> _finallyConventions;

internal RazorComponentEndpointConventionBuilder(
internal RazorComponentsEndpointConventionBuilder(
object @lock,
ComponentApplicationBuilder builder,
RazorComponentDataSourceOptions options,
Expand All @@ -38,43 +36,14 @@ internal RazorComponentEndpointConventionBuilder(
/// <summary>
/// Gets the <see cref="ComponentApplicationBuilder"/> that is used to build the endpoints.
/// </summary>
public ComponentApplicationBuilder ApplicationBuilder => _builder;

/// <summary>
/// Configures the <see cref="RenderMode.WebAssembly"/> for this application.
/// </summary>
/// <returns>The <see cref="RazorComponentEndpointConventionBuilder"/>.</returns>
public RazorComponentEndpointConventionBuilder AddWebAssemblyRenderMode()
{
for (var i = 0; i < _options.ConfiguredRenderModes.Count; i++)
{
var mode = _options.ConfiguredRenderModes[i];
if (mode is WebAssemblyRenderMode)
{
return this;
}
}

_options.ConfiguredRenderModes.Add(RenderMode.WebAssembly);

return this;
}
internal ComponentApplicationBuilder ApplicationBuilder => _builder;

/// <summary>
/// Configures the <see cref="RenderMode.Server"/> for this application.
/// </summary>
/// <returns>The <see cref="RazorComponentEndpointConventionBuilder"/>.</returns>
public RazorComponentEndpointConventionBuilder AddServerRenderMode()
/// <returns>The <see cref="RazorComponentsEndpointConventionBuilder"/>.</returns>
public RazorComponentsEndpointConventionBuilder AddServerRenderMode()
{
for (var i = 0; i < _options.ConfiguredRenderModes.Count; i++)
{
var mode = _options.ConfiguredRenderModes[i];
if (mode is ServerRenderMode)
{
return this;
}
}

_options.ConfiguredRenderModes.Add(RenderMode.Server);

return this;
Expand Down Expand Up @@ -103,4 +72,13 @@ public void Finally(Action<EndpointBuilder> finallyConvention)
_finallyConventions.Add(finallyConvention);
}
}

/// <summary>
/// Adds the given <paramref name="renderMode"/> to the list of configured render modes if not present.
/// </summary>
/// <param name="renderMode">The <see cref="IComponentRenderMode"/> to add.</param>
public void AddRenderMode(IComponentRenderMode renderMode)
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
public void AddRenderMode(IComponentRenderMode renderMode)
public RazorComponentsEndpointConventionBuilder AddRenderMode(IComponentRenderMode renderMode)

Wasn't there supposed to be AddWebAssemblyRenderMode() directly on the RazorComponentsEndpointConventionBuilder according to the approved API proposal at #49754?

I don't think we approved any IComponentRenderMode APIs other than RenderModeEndpointProvider which we tried to hide in an Infrastructure namespace. I don't think RenderMode or the IComponentRenderMode interface itself was ever proposed as API either.

I see that this is being called by an AddWebAssemblyRenderMode extension method in WebAssemblyRazorComponentsEndpointConventionBuilderExtensions. I'm assuming this was done for layering reasons so we could flow options via the call to AddWebAssemblyRenderMode(WebAssemblyComponentsEndpointOptions).

It's really odd to use different patterns for adding server and webassembly render modes. If we're going to do an extension method for one, we should do it for both. It's also a bit unusual to take an WebAssemblyComponentsEndpointOptions directly instead of an Action<WebAssemblyComponentsEndpointOptions>, but maybe it's okay since we're not really doing the options pattern for endpoint config. Regardless, it's something that should be discussed in API review.

{
_options.ConfiguredRenderModes.Add(renderMode);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Reflection;

namespace Microsoft.AspNetCore.Builder;

/// <summary>
/// Configures which assemblies are part of the given Razor Component Application.
/// </summary>
public static class RazorComponentsEndpointConventionBuilderExtensions
{
/// <summary>
/// Adds the given additional assemblies to the component application.
/// </summary>
/// <param name="builder">The <see cref="RazorComponentsEndpointConventionBuilder"/>.</param>"/>
/// <param name="assemblies">The <see cref="Assembly"/> instances to add.</param>
/// <returns>The <see cref="RazorComponentsEndpointConventionBuilder"/>.</returns>
/// <remarks>
/// The provided assemblies will be scanned for pages that will be mapped as endpoints.
/// </remarks>
public static RazorComponentsEndpointConventionBuilder AddAdditionalAssemblies(
this RazorComponentsEndpointConventionBuilder builder,
params Assembly[] assemblies)
{
ArgumentNullException.ThrowIfNull(builder);
ArgumentNullException.ThrowIfNull(assemblies);

foreach (var assembly in assemblies)
{
builder.ApplicationBuilder.AddAssembly(assembly);
}
return builder;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,17 @@
namespace Microsoft.AspNetCore.Builder;

/// <summary>
///
/// Extensions to <see cref="IEndpointRouteBuilder"/> for razor component applications.
/// </summary>
public static class RazorComponentsEndpointRouteBuilderExtensions
{
/// <summary>
///
/// Maps the page components defined in the specified <typeparamref name="TRootComponent"/> to the given assembly
/// and renders the component specified by <typeparamref name="TRootComponent"/> when the route matches.
/// </summary>
/// <param name="endpoints"></param>
/// <returns></returns>
public static RazorComponentEndpointConventionBuilder MapRazorComponents<[DynamicallyAccessedMembers(Component)] TRootComponent>(this IEndpointRouteBuilder endpoints)
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/>.</param>
/// <returns>An <see cref="RazorComponentsEndpointConventionBuilder"/> that can be used to further configure the API.</returns>
public static RazorComponentsEndpointConventionBuilder MapRazorComponents<[DynamicallyAccessedMembers(Component)] TRootComponent>(this IEndpointRouteBuilder endpoints)
{
ArgumentNullException.ThrowIfNull(endpoints);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
using Microsoft.AspNetCore.Routing;
using static Microsoft.AspNetCore.Internal.LinkerFlags;

namespace Microsoft.AspNetCore.Components.Endpoints;
namespace Microsoft.AspNetCore.Components.Endpoints.Infrastructure;

/// <summary>
/// A provider that can register endpoints to support a specific <see cref="IComponentRenderMode"/>.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace Microsoft.AspNetCore.Components.Endpoints;
/// <summary>
/// Metadata that represents the root component associated with an endpoint.
/// </summary>
public class RootComponentMetadata
public sealed class RootComponentMetadata
{
/// <summary>
/// Initializes a new instance of <see cref="RootComponentMetadata"/>.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.Extensions.DependencyInjection;

namespace Microsoft.AspNetCore.Components.Endpoints;
namespace Microsoft.Extensions.DependencyInjection;

/// <summary>
/// A builder that can be used to configure Razor Components.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@

using Microsoft.AspNetCore.Components.Endpoints.FormMapping;

namespace Microsoft.Extensions.DependencyInjection;
namespace Microsoft.AspNetCore.Components.Endpoints;

/// <summary>
/// Provides options for configuring server-side rendering of Razor Components.
/// </summary>
public class RazorComponentOptions
public sealed class RazorComponentsOptions
{
internal readonly FormDataMapperOptions _formMappingOptions = new();

Expand Down Expand Up @@ -58,13 +58,4 @@ public int MaxFormMappingKeySize
get => _formMappingOptions.MaxKeyBufferSize;
set => _formMappingOptions.MaxKeyBufferSize = value;
}

/// <summary>
/// Gets or sets a value that determines whether the current culture should be used when mapping form data.
/// </summary>
public bool FormMappingUseCurrentCulture
{
get => _formMappingOptions.UseCurrentCulture;
set => _formMappingOptions.UseCurrentCulture = value;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

namespace Microsoft.AspNetCore.Components.Endpoints;

internal class RazorComponentsEndpointsOptions
internal class RazorComponentsEndpointOptions
{
public bool DetailedErrors { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

namespace Microsoft.Extensions.DependencyInjection;

internal class RazorComponentsEndpointsDetailedErrorsConfiguration : IConfigureOptions<RazorComponentsEndpointsOptions>
internal class RazorComponentsEndpointsDetailedErrorsConfiguration : IConfigureOptions<RazorComponentsEndpointOptions>
{
public RazorComponentsEndpointsDetailedErrorsConfiguration(IConfiguration configuration)
{
Expand All @@ -17,7 +17,7 @@ public RazorComponentsEndpointsDetailedErrorsConfiguration(IConfiguration config

public IConfiguration Configuration { get; }

public void Configure(RazorComponentsEndpointsOptions options)
public void Configure(RazorComponentsEndpointOptions options)
{
var value = Configuration[WebHostDefaults.DetailedErrorsKey];
options.DetailedErrors = string.Equals(value, "true", StringComparison.OrdinalIgnoreCase) ||
Expand Down
Loading