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

Using per page/component render modes, cascading parameters turn null after rendering. #53482

Open
1 task done
carlfranklin opened this issue Jan 19, 2024 · 12 comments
Open
1 task done
Labels
area-blazor Includes: Blazor, Razor Components Docs This issue tracks updating documentation Samples
Milestone

Comments

@carlfranklin
Copy link

carlfranklin commented Jan 19, 2024

Is there an existing issue for this?

  • I have searched the existing issues

Describe the bug

Using per page/component render modes, cascading parameters turn null after rendering.

Expected Behavior

The issue is documented with code at https://github.com/carlfranklin/Blazor8Test

Using per page/component render modes, cascading parameters turn null after rendering.

I have a .NET 8 Blazor Web App with the Interactive Render Mode set to Server, and the Interactivity Location set to Global.

This is my version info:

Microsoft Visual Studio Professional 2022
Version 17.8.5
VisualStudio.17.Release/17.8.5+34511.84
Microsoft .NET Framework
Version 4.8.09037

Installed Version: Professional

ASP.NET and Web Tools   17.8.358.6298
ASP.NET and Web Tools

Azure App Service Tools v3.0.0   17.8.358.6298
Azure App Service Tools v3.0.0

Azure Functions and Web Jobs Tools   17.8.358.6298
Azure Functions and Web Jobs Tools

C# Tools   4.8.0-7.23572.1+7b75981cf3bd520b86ec4ed00ec156c8bc48e4eb
C# components used in the IDE. Depending on your project type and settings, a different version of the compiler may be used.

Common Azure Tools   1.10
Provides common services for use by Azure Mobile Services and Microsoft Azure Tools.

DevExpress Dashboard Extension   1.4
A Visual Studio extension that invokes the Dashboard Designer editor.

DevExpress Reporting Extension   1.4
A Visual Studio extension that invokes the Report Designer editor for report definition VSREPX files.

DevExpress Reporting Tools Extension   1.0
Extends Visual Studio with tools required for the Report Designer editor.

DevExpress VSDesigner NETFramework Package   1.0
A Visual Studio extension that invokes the Report and Dashboard designer editors.

DevExpress.DeploymentTool   1.0
A useful tool for deploying DevExpress assemblies.

DevExpress.Win.LayoutAssistant Extension   1.0
DevExpress.Win.LayoutAssistant Visual Studio Extension Detailed Info

Extensibility Message Bus   1.4.39 (main@e8108eb)
Provides common messaging-based MEF services for loosely coupled Visual Studio extension components communication and integration.

GitHub Copilot   1.149.0.0 (v1.149.0.0@9a0f75deb)
GitHub Copilot is an AI pair programmer that helps you write code faster and with less work.

GitHub Copilot Agent   1.149.0

Microsoft JVM Debugger   1.0
Provides support for connecting the Visual Studio debugger to JDWP compatible Java Virtual Machines

Mono Debugging for Visual Studio   17.8.17 (957fbed)
Support for debugging Mono processes with Visual Studio.

NuGet Package Manager   6.8.0
NuGet Package Manager in Visual Studio. For more information about NuGet, visit https://docs.nuget.org/

Razor (ASP.NET Core)   17.8.3.2405201+d135dd8d2ec1c2fbdee220e8656b308694e17a4b
Provides languages services for ASP.NET Core Razor.

SQL Server Data Tools   17.8.120.1
Microsoft SQL Server Data Tools

TypeScript Tools   17.0.20920.2001
TypeScript Tools for Microsoft Visual Studio

Visual Basic Tools   4.8.0-7.23572.1+7b75981cf3bd520b86ec4ed00ec156c8bc48e4eb
Visual Basic components used in the IDE. Depending on your project type and settings, a different version of the compiler may be used.

Visual F# Tools   17.8.0-beta.23475.2+10f956e631a1efc0f7f5e49c626c494cd32b1f50
Microsoft Visual F# Tools

Visual Studio IntelliCode   2.2
AI-assisted development for Visual Studio.

VisualStudio.DeviceLog   1.0
Information about my package

VisualStudio.Mac   1.0
Mac Extension for Visual Studio

VSPackage Extension   1.0
VSPackage Visual Studio Extension Detailed Info

Xamarin   17.8.0.157 (d17-8@8e82278)
Visual Studio extension to enable development for Xamarin.iOS and Xamarin.Android.

Xamarin Designer   17.8.3.6 (remotes/origin/d17-8@eccf46a291)
Visual Studio extension to enable Xamarin Designer tools in Visual Studio.

Xamarin.Android SDK   13.2.2.0 (d17-5/45b0e14)
Xamarin.Android Reference Assemblies and MSBuild support.
    Mono: d9a6e87
    Java.Interop: xamarin/java.interop/d17-5@149d70fe
    SQLite: xamarin/sqlite/3.40.1@68c69d8
    Xamarin.Android Tools: xamarin/xamarin-android-tools/d17-5@ca1552d

In App.razor, I have disabled pre-rendering, but it also fails with pre-rendering on.

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <base href="/" />
    <link rel="stylesheet" href="bootstrap/bootstrap.min.css" />
    <link rel="stylesheet" href="app.css" />
    <link rel="stylesheet" href="Blazor8Test.styles.css" />
    <link rel="icon" type="image/png" href="favicon.png" />
    <HeadOutlet @rendermode="@InteractiveServer" />
</head>

<body>
    <!-- Turn off pre-rendering -->
    <Routes @rendermode="@(new InteractiveServerRenderMode(false))" />
    <script src="_framework/blazor.web.js"></script>
</body>

</html>

I have a component in the client project called CascadingAppState.razor:

<CascadingValue Value="this">
    @ChildContent
</CascadingValue>
using Microsoft.AspNetCore.Components;

namespace Blazor8Test.Client;

public partial class CascadingAppState : ComponentBase
{
    [Parameter]
    public RenderFragment ChildContent { get; set; }

    private int count = 0;
    public int Count
    {
        get => count;
        set
        {
            count = value;
            StateHasChanged();
        }
    }
}

I have modified Routes.razor as follows:

<CascadingAppState>
    <Router AppAssembly="@typeof(Program).Assembly">
        <Found Context="routeData">
            <RouteView RouteData="@routeData" DefaultLayout="@typeof(Layout.MainLayout)" />
            <FocusOnNavigate RouteData="@routeData" Selector="h1" />
        </Found>
    </Router>
</CascadingAppState>

I have implemented CascadingAppState in Counter.razor:

@page "/counter"

<PageTitle>Counter</PageTitle>

<h1>Counter Render Mode @renderMode</h1>

<p role="status">Current count: @AppState.Count</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {

    private int currentCount = 0;
    private string renderMode = "SSR";

    [CascadingParameter]
    public CascadingAppState AppState { get; set; } = null;

    private void IncrementCount()
    {
        AppState.Count++;
    }

    protected override void OnAfterRender(bool firstRender)
    {
        if (firstRender)
        {
            renderMode = OperatingSystem.IsBrowser() ? "Wasm" : "Server";
            StateHasChanged();
        }
    }
}

Note that I am also showing the current render mode: SSR, Server, or WASM.

Behavior

Run the app and navigate to the Counter page. It works as advertised

image-20240119085526314

image-20240119085431751

Increment the counter, navigate to Home and back. The value persists because of the Cascading App State:

image-20240119085520380

image-20240119085526314

image-20240119085520380

Now remove the "Global" feature in App.razor:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <base href="/" />
    <link rel="stylesheet" href="bootstrap/bootstrap.min.css" />
    <link rel="stylesheet" href="app.css" />
    <link rel="stylesheet" href="Blazor8Test.styles.css" />
    <link rel="icon" type="image/png" href="favicon.png" />
    <HeadOutlet />
</head>

<body>
    <Routes />
    <script src="_framework/blazor.web.js"></script>
</body>

</html>

And add this line on line 2 of Counter.razor:

@rendermode InteractiveServer

Run it again

image-20240119085526314

Upon navigation, you get a Null Reference Exception on AppState:

image-20240119085842892

Steps To Reproduce

Follow the instructions I have laid out

Exceptions (if any)

No response

.NET Version

Version info is included in description

Anything else?

No response

@dotnet-issue-labeler dotnet-issue-labeler bot added the area-blazor Includes: Blazor, Razor Components label Jan 19, 2024
@javiercn
Copy link
Member

@carlfranklin thanks for reaching out.

This is likely because you are trying to pass the cascading parameters across serialization boundaries (which is not generally supported)

There's a bug tracking support for this type of thing here, however, in general the values will have to be serializable #51969

@SQL-MisterMagoo
Copy link
Contributor

I think the problem here is that Routes.razor which contains the CascadingState is not marked as Interactive, so it does not participate in the interactivity.

If you mark Routes as Interactive, this problem goes away.

I guess you could also move the CascadingState to a new layout that is marked Interactive and have any Interactive routable pages use the new layout.

There may be other ways of course, but it doesn't seem like a bug that state parented in a non-interactive component is null.

@carlfranklin
Copy link
Author

carlfranklin commented Jan 31, 2024 via email

@SQL-MisterMagoo
Copy link
Contributor

SQL-MisterMagoo commented Jan 31, 2024 via email

@sbwalker
Copy link

sbwalker commented Feb 2, 2024

There are a lot of limitations dealing with render mode boundaries... I documented a bunch of them here (including the CascadingParameter behavior):

https://www.linkedin.com/posts/shaunbrucewalker_blazor-dotnet8-activity-7128497980755603456-QsnX

  • Interactive render modes can be used from a Static component, including using different render modes for sibling components.

  • You can't switch to a different Interactive render mode in a child component.

  • Parameters passed to an Interactive child component from a Static parent must be JSON serializable. This means that you can't pass render fragments or child content from a Static parent component to an Interactive child component.

  • CascadingParameters cannot be passed to an Interactive child component from a Static parent. The CascadingParameter will be null in the child component.

  • Scoped Services which contain state cannot be passed to an Interactive component. The Scoped Service state will be null.

  • DynamicComponent cannot be used to transition from a Static parent to an Interactive child. This is because it relies on a Type parameter which is not serializable.

  • Static components do not support OnAfterRender life-cycle events and do not support JS Interop

@mkArtakMSFT mkArtakMSFT added Docs This issue tracks updating documentation Samples labels Feb 12, 2024
@mkArtakMSFT
Copy link
Member

Parking in Preview 7 to provide a sample demonstrating how to do this.

@mkArtakMSFT mkArtakMSFT added this to the 9.0-preview7 milestone Feb 12, 2024
@rockfordlhotka
Copy link

@mkArtakMSFT hopefully the ultimate resolution allows bi-directional state management - which is to say that if I change a value in a wasm page, that value should continue to be consistent in server static and server interactive pages, and visa versa.

Basically, if I implement a Counter page and put the counter value in some cascading/global location, I should get the same Counter value in every render mode throughout the lifetime of a single app "session".

@Otto404
Copy link

Otto404 commented Apr 16, 2024

I think the confusion that MS created with cascading parameters in conjunction with static SSR and interactive server-side rendering was a major mistake.
As far as I can see, the cascading parameters are now completely useless.
I'm seriously considering ditching Blazor and using Flutter.

@sbwalker
Copy link

@Otto404 you may find the following example useful:

https://github.com/sbwalker/Oqtane.State

It demonstrates a simple approach for using Cascading Parameters and Scoped Services in Blazor in .NET 8 when using static and interactive components. Note that it only works for the most common scenario I have seen in Blazor, where you are using Cascading Parameters and Scoped Services as essentially a "read-only immutable cache" for the current user session.

@rockfordlhotka
Copy link

I think the confusion that MS created with cascading parameters in conjunction with static SSR and interactive server-side rendering was a major mistake. As far as I can see, the cascading parameters are now completely useless. I'm seriously considering ditching Blazor and using Flutter.

Before leaving Blazor, I'd look at the solutions from myself and @sbwalker - I think we've come up with good answers to the challenge that make the new render modes work really well in a lot of scenarios. And the flexible render modes are (imo) very useful in real-world scenarios.

@Otto404
Copy link

Otto404 commented Apr 24, 2024

@rockfordlhotka could you give me a pointer to your solution? thanks,

@rockfordlhotka
Copy link

@Otto404

@rockfordlhotka could you give me a pointer to your solution? thanks,

I solved the issue without cascading parameters, instead basically creating a per-user "session" concept that is read-write across static, server-interactive, and wasm-interactive pages.

https://blog.lhotka.net/2023/11/28/Per-User-Blazor-8-State

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-blazor Includes: Blazor, Razor Components Docs This issue tracks updating documentation Samples
Projects
None yet
Development

No branches or pull requests

7 participants