Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Dec 3, 2025

Add test coverage for prerendering closed generic components

  • You've read the Contributor Guide and Code of Conduct.
  • You've included unit or integration tests for your change, where applicable.
  • You've included inline docs for your change, where applicable.
  • There's an open issue for the PR that you are making. If you'd like to propose a new feature or change, please open an issue to discuss the change or find an existing issue.

Add tests verifying closed generic components can be prerendered

Description

The issue reported that generic components couldn't be rendered as root components due to type serialization limitations. Investigation shows the feature already works correctly:

  • Type.FullName produces valid assembly-qualified names for closed generics (e.g., GenericComponent1[[System.Int32, System.Private.CoreLib, ...]]`)
  • Assembly.GetType() correctly resolves these names during deserialization

This PR adds comprehensive test coverage:

EndpointHtmlRendererTest (4 tests)

  • Static rendering of GenericComponent<int>
  • Interactive Server mode (prerendered and non-prerendered)
  • Interactive WebAssembly mode (prerendered)

ServerComponentDeserializerTest (4 tests)

  • Serialization/deserialization round-trip for closed generic types
  • Different type parameters (int, string)
  • Parameter handling with generic components
@* Example: GenericComponent.razor *@
@typeparam TValue

<p>Generic value: @(Value?.ToString() ?? "(null)")</p>
@code {
    [Parameter] public TValue Value { get; set; }
}

Usage in tests:

var result = await renderer.PrerenderComponentAsync(
    httpContext, 
    typeof(GenericComponent<int>), 
    RenderMode.InteractiveServer, 
    ParameterView.FromDictionary(new Dictionary<string, object> { { "Value", 123 } }));
Original prompt

This section details on the original issue you should resolve

<issue_title>Support prerendering closed generic components</issue_title>
<issue_description>

Summary

We don't support rendering generic components as root components due to limitations in the way we serialize the type definition for the component (a similar thing happens for component parameters). We should consider lifting this limitation to support some more advanced scenarios.

Motivation and goals

Turns out that rendering generic components is useful if you want to write a "functional" component to wrap around another component, an example of this is when you are rendering multiple components at different points on a page and you want to, for example, require authentication for those components.

At that point, application developers are forced to write an individual wrapper for each of those components they want to wrap.

In scope

Support rendering closed generic components from Blazor applications.

Out of scope

TBD

Risks / unknowns

None so far

Examples

<CascadingAuthenticationState>
    <AuthorizeView>
        <Authorized>
            <TComponent @attributes="Params" />
        </Authorized>
    </AuthorizeView>
</CascadingAuthenticationState>

@code{
    [Parameter(CaptureUnmatchedValues = true)] public IDictionary<string,object> Params { get; set; }
}

Having a language syntax for writing these types of components is also desirable, otherwise it's cumbersome and likely error prone to author them:

    public class ProtectedWidget<T> : IComponent where T : IComponent
    {
        private RenderHandle _handle;
        private ParameterView _parameters;
        static RenderFragment _authorizeFragment;

        public ProtectedWidget()
        {
            _authorizeFragment = new RenderFragment(AuthorizedFragment);
        }

        public void Attach(RenderHandle renderHandle)
        {
            _handle = renderHandle;
        }

        public Task SetParametersAsync(ParameterView parameters)
        {
            _parameters = parameters;
            _handle.Render(BuildRenderTree);

            return Task.CompletedTask;
        }

        private void BuildRenderTree(RenderTreeBuilder builder)
        {

            builder.OpenComponent<CascadingAuthenticationState>(0);
            builder.OpenComponent<AuthorizeView>(1);
            builder.AddAttribute(1, "Authorized", _authorizeFragment);
            builder.CloseComponent();
            builder.CloseComponent();

        }
        void AuthorizedFragment(RenderTreeBuilder child)
        {
            child.OpenComponent<T>(0);
            child.AddMultipleAttributes(1, _parameters.ToDictionary());
            child.CloseComponent();
        }
    }

</issue_description>

Comments on the Issue (you are @copilot in this section)

@ Thanks for contacting us. We're moving this issue to the `Next sprint planning` milestone for future evaluation / consideration. We will evaluate the request when we are planning the work for the next milestone. To learn more about what to expect next and how this issue will be handled you can read more about our triage process [here](https://github.com/dotnet/aspnetcore/blob/master/docs/TriageProcess.md). @ We've moved this issue to the Backlog milestone. This means that it is not going to be worked on for the coming release. We will reassess the backlog following the current release and consider this item at that time. To learn more about our issue management process and to have better expectation regarding different types of issues you can read our [Triage Process](https://github.com/dotnet/aspnetcore/blob/master/docs/TriageProcess.md). @ We've moved this issue to the Backlog milestone. This means that it is not going to be worked on for the coming release. We will reassess the backlog following the current release and consider this item at that time. To learn more about our issue management process and to have better expectation regarding different types of issues you can read our [Triage Process](https://github.com/dotnet/aspnetcore/blob/main/docs/TriageProcess.md).

💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

@dotnet-policy-service
Copy link
Contributor

Greetings! You've submitted a PR that modifies code that is shared with https://github.com/dotnet/runtime . Please make sure you synchronize this code with the changes in that repo!

Co-authored-by: javiercn <6995051+javiercn@users.noreply.github.com>
Copilot AI changed the title [WIP] Support prerendering of closed generic components Add test coverage for prerendering closed generic components Dec 3, 2025
Copilot finished work on behalf of javiercn December 3, 2025 23:44
Copilot AI requested a review from javiercn December 3, 2025 23:44
@javiercn javiercn marked this pull request as ready for review December 4, 2025 09:38
@javiercn javiercn requested a review from a team as a code owner December 4, 2025 09:38
Copilot AI review requested due to automatic review settings December 4, 2025 09:38
Copilot finished reviewing on behalf of javiercn December 4, 2025 09:41
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds comprehensive test coverage verifying that closed generic components can be successfully prerendered in Blazor applications. The investigation revealed that this functionality already works correctly—no code changes were needed, only test coverage was missing.

Key changes:

  • Created a reusable GenericComponent<TValue> test component for validating generic type handling
  • Added 4 tests to ServerComponentDeserializerTest covering serialization/deserialization of closed generic components
  • Added 4 tests to EndpointHtmlRendererTest covering various rendering modes (static, Interactive Server, Interactive WebAssembly)

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
src/Components/Endpoints/test/TestComponents/GenericComponent.razor New test component demonstrating a generic component with a type parameter that can be prerendered
src/Components/Server/test/Circuits/ServerComponentDeserializerTest.cs Added 4 tests verifying that closed generic components can be serialized and deserialized correctly, with and without parameters
src/Components/Endpoints/test/EndpointHtmlRendererTest.cs Added 4 tests covering static rendering and prerendering of closed generic components in different render modes (Server and WebAssembly)

var parameters = deserializedDescriptor.Parameters.ToDictionary();
Assert.Single(parameters);
Assert.Contains("Value", parameters.Keys);
Assert.Equal(42, Convert.ToInt32(parameters["Value"]!, CultureInfo.InvariantCulture));
Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

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

The use of Convert.ToInt32 with CultureInfo.InvariantCulture is unnecessary here. The parameter value is already an integer (42), and after deserialization it should be directly castable or assertable. In similar tests in this file (e.g., CanParseSingleMarkerWithParameters at line 56), values are asserted directly without conversion.

Consider simplifying to:

Assert.Equal(42, parameters["Value"]);

If type conversion is genuinely needed due to JSON deserialization, consider using a direct cast instead:

Assert.Equal(42, Assert.IsType<JsonElement>(parameters["Value"]).GetInt32());
Suggested change
Assert.Equal(42, Convert.ToInt32(parameters["Value"]!, CultureInfo.InvariantCulture));
Assert.Equal(42, parameters["Value"]);

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support prerendering closed generic components

2 participants