Skip to content
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

Blazor component passed as cascading parameter is null when used in MainLayout.razor (.NET 8.0 RC 1) #50724

Closed
1 task done
Stamo-Gochev opened this issue Sep 15, 2023 · 6 comments
Labels
area-blazor Includes: Blazor, Razor Components ✔️ Resolution: By Design Resolved because the behavior in this issue is the intended design. Status: Resolved

Comments

@Stamo-Gochev
Copy link

Stamo-Gochev commented Sep 15, 2023

Is there an existing issue for this?

  • I have searched the existing issues

Describe the bug

When a Blazor component is used in MainLayout.razor in a Blazor app with Blazor Server interactive render mode, passing this component as a cascading parameter results in the component having the value null.

Details

Note: The explanations below are extracted from the code we are using, which is proprietary. This is why the scenario is a bit simplified and might look like the functionality can be achieved in a different way, but please focus on how a component passed as a cascading parameter in MainLayout.razor becomes null - this is the main issue we are facing. I am not able to directly show the real code as it is licensed (https://shorturl.at/bklns).

The Blazor app renders a RootComponent, which basically re-renders its child content. The component wraps the @Body element inside MainLayout.razor and it is used together with a ContentComponent that appears on the Home.razor page.

When the app is run, the first time the ContentComponent is initialized, the RootComponent has its correct value, but on consecutive calls it becomes null.

blazor-layout-issue

Expected Behavior

The component that is passed down the hierarchy as a cascading parameter should not be null.

Note 1: The problem is not reproducible when exactly the same configuration is used in a .NET 6.0 project, so I suppose this is related to the new interactive modes and how they work. Please advise if some configuration is incorrect, but for now, this seems like a regression to us.

Note 2: We do not want to multi-target in order to "workaround" this in some way (if you can suggest a workaround) as we want to avoid this following the suggestion in https://learn.microsoft.com/en-au/aspnet/core/blazor/components/render-modes?view=aspnetcore-8.0#apply-a-render-mode-to-a-component-definition - "Component authors should avoid coupling a component's implementation to a specific render mode"

Note 3: The code works if the RootComponent is moved to wrap the whole Home.razor page, but this is not an option for us - we want to keep the compatibility of the configuration in MainLayout.razor.

Steps To Reproduce

  1. Clone https://github.com/Stamo-Gochev/blazor-layout-issue
  2. Run the app and open the Home.razor page
  3. Observe the log for the error or set a breakpoint in ContentComponent and see the Debug window when the RootComponent value becomes null or see the attached GIF

Exceptions (if any)

The cascading value is null.

.NET Version

8.0.100-rc.1.23455.8

Anything else?

$ dotnet --info
.NET SDK:
 Version:   8.0.100-rc.1.23455.8
 Commit:    e14caf947f

Runtime Environment:
 OS Name:     Windows
 OS Version:  10.0.19044
 OS Platform: Windows
 RID:         win-x64
 Base Path:   C:\Program Files\dotnet\sdk\8.0.100-rc.1.23455.8\

.NET workloads installed:
There are no installed workloads to display.

Host:
  Version:      8.0.0-rc.1.23419.4
  Architecture: x64
  Commit:       92959931a3
  RID:          win-x64

.NET SDKs installed:
  8.0.100-rc.1.23455.8 [C:\Program Files\dotnet\sdk]
@dotnet-issue-labeler dotnet-issue-labeler bot added the area-blazor Includes: Blazor, Razor Components label Sep 15, 2023
@Stamo-Gochev Stamo-Gochev changed the title Blazor cascading parameter is null when used in MainLayout.razor Blazor component passed as cascading parameter is null when used in MainLayout.razor Sep 15, 2023
@Stamo-Gochev Stamo-Gochev changed the title Blazor component passed as cascading parameter is null when used in MainLayout.razor Blazor component passed as cascading parameter is null when used in MainLayout.razor (.NET 8.0 RC 1) Sep 15, 2023
@gragra33
Copy link

gragra33 commented Sep 15, 2023

I have looked at this and can see page rendering (only the first time) with the correct cascading value, then resets and shows the default. It appears that the page is rendering twice.

Here are my test cases (simplified code):

You can watch what is happening by navigating to the \counter page in each project.

@akorchev
Copy link

We have the same issue in Radzen.Blazor - a CascadingParameter is null in the child because of the new static rendering mode (I presume).

@Stamo-Gochev Fun times ahead for us Blazor UI component developers ;) Those new blazor interactivity modes will make us cry for sure.

@gragra33
Copy link

gragra33 commented Sep 15, 2023

@akorchev yeah, the CascadingParameter is set then is null. You can see it in the above sample projects. Hopefully addressed in RC2.

@SteveSandersonMS
Copy link
Member

@Stamo-Gochev I'm afraid cascading parameters do not pass across rendermode boundaries. There are two main aspects to understanding this:

  1. Interactive sessions run in a different context than the SSR page. There is no requirement that the server producing the SSR page is even the same machine that hosts some later Server interactive session, and of course for WebAssembly components the server is a different machine to the client. The whole intention for SSR is to render statelessly to get the full performance of pure stateless HTML rendering.
  2. Since any state crossing this boundary has to be serializable, it could not possibly include component instances like in your sample. Components are arbitrary objects that reference a vast chain of other objects, including the Renderer, the DI container, and every DI service instance. Developers must explicitly cause some state to be serialized from SSR and hence made available in subsequent interactive components. There are two main ways to do that:
    • Parameters passed across SSR/interactive boundaries are serialized automatically (if they are JSON-serializable, otherwise it's an error)
    • State stored in PersistentComponentState is serialized and recovered automatically (if it's JSON-serializable, otherwise it's an error)

We chose not to JSON-serialize cascading parameters because the typical usage patterns for cascading parameters are somewhat like DI services. There are often platform-specific variants of cascading parameters, so it would be unhelpful to developers if we stopped them from having Server-interactive specific versions or WebAssembly-specific versions. And many cascading parameter values in general are not serializable, so it would be impractical to update existing apps if you had to stop using all nonserializable cascading parameter values. It wouldn't even help in your case since your cascading parameter values are definitely not serializable.

Recommendation

If you need to make state available to all interactive components as a cascading parameter, you'll need to use the new API we've added to enable this. In Program.cs, it's now possible to register cascading parameters in the same way as registering DI services. For example:

// There are also various other overloads, e.g., if you need to use a factory pattern or to emit updated values later
builder.Services.AddCascadingValue(someValue);

Such values will then be available to all components, including interactive ones, since they go through the same DI container setup. Likewise you can call this from Program.cs in WebAssembly projects if you need.

As a component library author, you could create an extension method like:

builder.Services.AddTelerikCascadingParameters();

... and instruct developers to call that, as an alternative to telling them to add your <RootComponent> component in MainLayout.razor. Then it would work everywhere. I appreciate that causes some extra work for you as you will need to take a different approach - sorry for the inconvenience! But it's unavoidable (as far as I can imagine) since in general it wouldn't be possible to serialize component instances across server boundaries.

Does that make sense?

@SteveSandersonMS SteveSandersonMS added the ✔️ Resolution: By Design Resolved because the behavior in this issue is the intended design. label Sep 18, 2023
@ghost ghost added the Status: Resolved label Sep 18, 2023
@Stamo-Gochev
Copy link
Author

Thanks for the info and the recommendations, @SteveSandersonMS, we will consider the suggestions. I do understand that this is actually by design taking into account the interactivity-related specifics.

@ghost
Copy link

ghost commented Sep 19, 2023

This issue has been resolved and has not had any activity for 1 day. It will be closed for housekeeping purposes.

See our Issue Management Policies for more information.

@ghost ghost closed this as completed Sep 19, 2023
@ghost ghost locked as resolved and limited conversation to collaborators Oct 19, 2023
This issue was closed.
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-blazor Includes: Blazor, Razor Components ✔️ Resolution: By Design Resolved because the behavior in this issue is the intended design. Status: Resolved
Projects
None yet
Development

No branches or pull requests

4 participants