From f44ef60082246262a4533ee8a403d1ced8af29a1 Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Fri, 21 Feb 2025 09:09:54 -0500 Subject: [PATCH 01/12] Cascading values+params for state management --- .../cascading-values-and-parameters.md | 156 +++++++++++++++++- aspnetcore/blazor/state-management.md | 14 +- 2 files changed, 168 insertions(+), 2 deletions(-) diff --git a/aspnetcore/blazor/components/cascading-values-and-parameters.md b/aspnetcore/blazor/components/cascading-values-and-parameters.md index cca65dfa1436..6cc38e57f0bc 100644 --- a/aspnetcore/blazor/components/cascading-values-and-parameters.md +++ b/aspnetcore/blazor/components/cascading-values-and-parameters.md @@ -75,7 +75,7 @@ The following `Daleks` component displays the cascaded values. :::moniker range=">= aspnetcore-8.0" -In the following example, `Dalek` is registered as a cascading value using [`CascadingValueSource`](xref:Microsoft.AspNetCore.Components.CascadingValueSource%601), where `` is the type. The `isFixed` flag indicates whether the value is fixed. If false, all recipients are subscribed for update notifications, which are issued by calling . Subscriptions create overhead and reduce performance, so set `isFixed` to `true` if the value doesn't change. +In the following example, `Dalek` is registered as a cascading value using [`CascadingValueSource`](xref:Microsoft.AspNetCore.Components.CascadingValueSource%601), where `` is the type. The `isFixed` flag indicates whether the value is fixed. If false, all recipients are subscribed for update notifications. Subscriptions create overhead and reduce performance, so set `isFixed` to `true` if the value doesn't change. ```csharp builder.Services.AddCascadingValue(sp => @@ -94,6 +94,160 @@ builder.Services.AddCascadingValue(sp => > > Avoid using to register a component type as a cascading value. Instead, wrap the `...` in the `Routes` component (`Components/Routes.razor`) with the component and adopt global interactive server-side rendering (interactive SSR). For an example, see the [`CascadingValue` component](#cascadingvalue-component) section. +Calling to issue update notifications can be used to signal multiple Razor component subscribers that a cascading value has changed. Notifications aren't possible for subscribers that adopt static server-side rendering (static SSR), so subscribers must adopt an interactive render mode. + +In the following example: + +* `NotifyingDalek` implements to notify clients that a property value has changed. When the `Units` property is set, the (`PropertyChanged`) is invoked. +* The `SetUnitsToOneThousand` method can be triggered by subscribers to set `Units` to 1,000 with a simulated processing delay. +* Because the 's type in this example is a class type, you can meet virtually any state management feature specification requirement. However, subscriptions create overhead and reduce performance, so benchmark the performance of this approach in your app and compare it to other [state management approaches](xref:blazor/state-management) before adopting it in a production app with constrained processing and memory resources. + +`NotifyingDalek.cs`: + +```csharp +using System.ComponentModel; +using System.Runtime.CompilerServices; + +public class NotifyingDalek : INotifyPropertyChanged +{ + public event PropertyChangedEventHandler? PropertyChanged; + private int units; + + public int Units + { + get => units; + set + { + if (units != value) + { + units = value; + OnPropertyChanged(); + } + } + } + + protected virtual void OnPropertyChanged( + [CallerMemberName] string? propertyName = default) + => PropertyChanged?.Invoke(this, new(propertyName)); + + public async Task SetUnitsToOneThousand() + { + // Simulate a three second delay in processing + await Task.Delay(3000); + + Units = 1000; + } +} +``` + +The following `CascadingValueSourceFactory` has a `CreateNotifying` method to create a from a type that implements . + +`CascadingValueSourceFactory.cs`: + +```csharp +using Microsoft.AspNetCore.Components; +using System.ComponentModel; + +public static class CascadingValueSourceFactory +{ + public static CascadingValueSource CreateNotifying( + T value, bool isFixed = false) where T : INotifyPropertyChanged + { + var source = new CascadingValueSource(value, isFixed); + + value.PropertyChanged += (sender, args) => source.NotifyChangedAsync(); + + return source; + } +} +``` + +The type's (`PropertyChanged`) calls the 's method to notify subscribers that the cascading value has changed. The is discarded when calling because the call only represents the duration of the dispatch to the synchronous context. Exceptions are handled internally by dispatching them to the renderer within the context of whichever component threw when receiving the update. This is the same way that exceptions are processed with a , which isn't notified about exceptions that happen inside notification recipients. + +In the `Program` file†, `NotifyingDalek` is created as a with an initial `Unit` value of 888 units: + +```csharp +builder.Services.AddCascadingValue( + s => CascadingValueSourceFactory.CreateNotifying(new NotifyingDalek() { Units = 888 })); +``` + +> [!NOTE] +> †For Blazor Web App solutions consisting of server and client (`.Client`) projects: +> +> * The preceding `NotifyingDalek.cs` and `CascadingValueSourceFactory.cs` files are placed in the `.Client` project. +> * The preceding code is placed into the `Program` file of *both* projects. + +The following component is used to demonstrate how changing the value of `NotifyingDalek.Units` notifies subscribers. + +`Daleks.razor`: + +```razor +

Daleks component

+ +
+ Dalek Units: @Dalek?.Units +
+ +
+ + +
+ +
+ +
+ +

+ Dalek© Terry Nation
+ Doctor Who© BBC +

+ +@code { + private int dalekCount; + + [CascadingParameter] + public NotifyingDalek? Dalek { get; set; } + + private void Update() + { + if (Dalek is not null) + { + Dalek.Units = dalekCount; + dalekCount = 0; + } + } + + private async Task SetOneThousandUnits() + { + if (Dalek is not null) + { + await Dalek.SetUnitsToOneThousand(); + } + } +} +``` + +To demonstrate multiple subscriber notifications, the following `DaleksMain` component renders three `Daleks` components. When the unit count (`Units`) of one `Dalek` component is updated, the other two `Dalek` component subscribers are updated. + +`DaleksMain.razor`: + +```razor +@page "/daleks-main" + +Daleks Main + +

Daleks Main

+ + + + + + +``` + :::moniker-end ## `CascadingValue` component diff --git a/aspnetcore/blazor/state-management.md b/aspnetcore/blazor/state-management.md index 35e48eb6c00b..76295bbb2a97 100644 --- a/aspnetcore/blazor/state-management.md +++ b/aspnetcore/blazor/state-management.md @@ -61,6 +61,7 @@ Common locations exist for persisting state: * [URL](#url-server) * [Browser storage](#browser-storage-server) * [In-memory state container service](#in-memory-state-container-service) +* [Cascading values and parameters](#cascading-values-and-parameters)

Server-side storage

@@ -620,7 +621,8 @@ Common locations exist for persisting state: * [Server-side storage](#server-side-storage-wasm) * [URL](#url-wasm) * [Browser storage](#browser-storage-wasm) -* [In-memory state container service](#in-memory-state-container-service) +* [In-memory state container service](#in-memory-state-container-service) +* [Cascading values and parameters](#cascading-values-and-parameters)

Server-side storage

@@ -805,6 +807,16 @@ services.AddScoped(); The preceding components implement , and the `OnChange` delegates are unsubscribed in the `Dispose` methods, which are called by the framework when the components are disposed. For more information, see . +## Cascading values and parameters + +Use [cascading values and parameters](xref:blazor/components/cascading-values-and-parameters) to manage state by flowing data from an ancestor Razor component to descendent components. + +:::moniker range=">= aspnetcore-8.0" + +Root-level cascading values with a permit Razor component subscriber notifications of changed cascading values. For more information and a working example, see the `NotifyingDalek` example in . + +:::moniker-end + ## Additional approaches When implementing custom state storage, a useful approach is to adopt [cascading values and parameters](xref:blazor/components/cascading-values-and-parameters): From 5669e1352c5dd1be6758577ff6228b36801bb9c8 Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Mon, 24 Feb 2025 11:53:32 -0500 Subject: [PATCH 02/12] Updates --- .../cascading-values-and-parameters.md | 36 ++++++++++++++++--- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/aspnetcore/blazor/components/cascading-values-and-parameters.md b/aspnetcore/blazor/components/cascading-values-and-parameters.md index 6cc38e57f0bc..c2aec8acbb07 100644 --- a/aspnetcore/blazor/components/cascading-values-and-parameters.md +++ b/aspnetcore/blazor/components/cascading-values-and-parameters.md @@ -100,7 +100,6 @@ In the following example: * `NotifyingDalek` implements to notify clients that a property value has changed. When the `Units` property is set, the (`PropertyChanged`) is invoked. * The `SetUnitsToOneThousand` method can be triggered by subscribers to set `Units` to 1,000 with a simulated processing delay. -* Because the 's type in this example is a class type, you can meet virtually any state management feature specification requirement. However, subscriptions create overhead and reduce performance, so benchmark the performance of this approach in your app and compare it to other [state management approaches](xref:blazor/state-management) before adopting it in a production app with constrained processing and memory resources. `NotifyingDalek.cs`: @@ -150,14 +149,39 @@ using System.ComponentModel; public static class CascadingValueSourceFactory { - public static CascadingValueSource CreateNotifying( + public static NotifyingCascadingValueSource CreateNotifying( T value, bool isFixed = false) where T : INotifyPropertyChanged { - var source = new CascadingValueSource(value, isFixed); + return new NotifyingCascadingValueSource(value, isFixed); + } +} - value.PropertyChanged += (sender, args) => source.NotifyChangedAsync(); +public class NotifyingCascadingValueSource : IDisposable where T : INotifyPropertyChanged +{ + private readonly T value; + private readonly CascadingValueSource source; - return source; + public NotifyingCascadingValueSource(T value, bool isFixed = false) + { + this.value = value; + source = new CascadingValueSource(value, isFixed); + this.value.PropertyChanged += OnValuePropertyChanged; + } + + private void OnValuePropertyChanged(object? sender, PropertyChangedEventArgs e) + { + source.NotifyChangedAsync(); + } + + public CascadingValueSource Source => source; + + public void Dispose() + { + value.PropertyChanged -= OnValuePropertyChanged; + + // The following prevents derived types that introduce a + // finalizer from needing to re-implement IDisposable. + GC.SuppressFinalize(this); } } ``` @@ -248,6 +272,8 @@ To demonstrate multiple subscriber notifications, the following `DaleksMain` com ``` +Because the 's type in this example is a class type, you can meet virtually any state management feature specification requirement. However, subscriptions create overhead and reduce performance, so benchmark the performance of this approach in your app and compare it to other [state management approaches](xref:blazor/state-management) before adopting it in a production app with constrained processing and memory resources. + :::moniker-end ## `CascadingValue` component From 50c2ca9eb962a8eb8bd2cf8f0b1fd9364214f589 Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Mon, 24 Feb 2025 12:59:19 -0500 Subject: [PATCH 03/12] Updates --- aspnetcore/blazor/components/cascading-values-and-parameters.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aspnetcore/blazor/components/cascading-values-and-parameters.md b/aspnetcore/blazor/components/cascading-values-and-parameters.md index c2aec8acbb07..60b2ef8cf7c3 100644 --- a/aspnetcore/blazor/components/cascading-values-and-parameters.md +++ b/aspnetcore/blazor/components/cascading-values-and-parameters.md @@ -170,7 +170,7 @@ public class NotifyingCascadingValueSource : IDisposable where T : INotifyPro private void OnValuePropertyChanged(object? sender, PropertyChangedEventArgs e) { - source.NotifyChangedAsync(); + _ = source.NotifyChangedAsync(); } public CascadingValueSource Source => source; From 8202784ca4d7440ec2da37ace4d83ea54fed0dab Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Mon, 24 Feb 2025 15:27:53 -0500 Subject: [PATCH 04/12] Updates --- .../cascading-values-and-parameters.md | 72 ++++++++++--------- 1 file changed, 38 insertions(+), 34 deletions(-) diff --git a/aspnetcore/blazor/components/cascading-values-and-parameters.md b/aspnetcore/blazor/components/cascading-values-and-parameters.md index 60b2ef8cf7c3..03e0aaccf95b 100644 --- a/aspnetcore/blazor/components/cascading-values-and-parameters.md +++ b/aspnetcore/blazor/components/cascading-values-and-parameters.md @@ -139,60 +139,64 @@ public class NotifyingDalek : INotifyPropertyChanged } ``` -The following `CascadingValueSourceFactory` has a `CreateNotifying` method to create a from a type that implements . +The following `CascadingValueServiceCollectionExtensions` creates a from a type that implements . -`CascadingValueSourceFactory.cs`: +`CascadingValueServiceCollectionExtensions.cs`: ```csharp -using Microsoft.AspNetCore.Components; using System.ComponentModel; +using Microsoft.AspNetCore.Components; -public static class CascadingValueSourceFactory -{ - public static NotifyingCascadingValueSource CreateNotifying( - T value, bool isFixed = false) where T : INotifyPropertyChanged - { - return new NotifyingCascadingValueSource(value, isFixed); - } -} +namespace Microsoft.Extensions.DependencyInjection; -public class NotifyingCascadingValueSource : IDisposable where T : INotifyPropertyChanged +public static class CascadingApplicationStateServiceCollectionExtensions { - private readonly T value; - private readonly CascadingValueSource source; - - public NotifyingCascadingValueSource(T value, bool isFixed = false) + public static IServiceCollection AddCascadingApplicationState( + this IServiceCollection serviceCollection, T value, bool isFixed = false) + where T : INotifyPropertyChanged { - this.value = value; - source = new CascadingValueSource(value, isFixed); - this.value.PropertyChanged += OnValuePropertyChanged; + return serviceCollection.AddCascadingValue(services => + { + return new ApplicationStateCascadingValueSource(value, isFixed); + }); } - private void OnValuePropertyChanged(object? sender, PropertyChangedEventArgs e) + private sealed class ApplicationStateCascadingValueSource + : CascadingValueSource, IDisposable where T : INotifyPropertyChanged { - _ = source.NotifyChangedAsync(); - } + private readonly T value; + private readonly CascadingValueSource source; - public CascadingValueSource Source => source; + public ApplicationStateCascadingValueSource(T value, bool isFixed = false) + : base(value, isFixed = false) + { + this.value = value; + source = new CascadingValueSource(value, isFixed); + this.value.PropertyChanged += HandlePropertyChanged; + } - public void Dispose() - { - value.PropertyChanged -= OnValuePropertyChanged; + private void HandlePropertyChanged(object? sender, PropertyChangedEventArgs e) + { + _ = NotifyChangedAsync(); + } + + public CascadingValueSource Source => source; - // The following prevents derived types that introduce a - // finalizer from needing to re-implement IDisposable. - GC.SuppressFinalize(this); + public void Dispose() + { + value.PropertyChanged -= HandlePropertyChanged; + } } } ``` -The type's (`PropertyChanged`) calls the 's method to notify subscribers that the cascading value has changed. The is discarded when calling because the call only represents the duration of the dispatch to the synchronous context. Exceptions are handled internally by dispatching them to the renderer within the context of whichever component threw when receiving the update. This is the same way that exceptions are processed with a , which isn't notified about exceptions that happen inside notification recipients. +The type's (`HandlePropertyChanged`) calls the 's method to notify subscribers that the cascading value has changed. The is discarded when calling because the call only represents the duration of the dispatch to the synchronous context. Exceptions are handled internally by dispatching them to the renderer within the context of whichever component threw when receiving the update. This is the same way that exceptions are processed with a , which isn't notified about exceptions that happen inside notification recipients. The event handler is disconnected in the `Dispose` method to prevent a memory leak. -In the `Program` file†, `NotifyingDalek` is created as a with an initial `Unit` value of 888 units: +In the `Program` file†, `NotifyingDalek` is passed to create a with an initial `Unit` value of 888 units: ```csharp -builder.Services.AddCascadingValue( - s => CascadingValueSourceFactory.CreateNotifying(new NotifyingDalek() { Units = 888 })); +builder.Services.AddCascadingApplicationState( + new NotifyingDalek() { Units = 888 }); ``` > [!NOTE] @@ -272,7 +276,7 @@ To demonstrate multiple subscriber notifications, the following `DaleksMain` com ``` -Because the 's type in this example is a class type, you can meet virtually any state management feature specification requirement. However, subscriptions create overhead and reduce performance, so benchmark the performance of this approach in your app and compare it to other [state management approaches](xref:blazor/state-management) before adopting it in a production app with constrained processing and memory resources. +Because the 's type in this example (`NotifyingDalek`) is a class type, you can meet virtually any state management feature specification requirement. However, subscriptions create overhead and reduce performance, so benchmark the performance of this approach in your app and compare it to other [state management approaches](xref:blazor/state-management) before adopting it in a production app with constrained processing and memory resources. :::moniker-end From 87a954db92a5dc95ea0efec0a37a86145fc1f6e8 Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Mon, 24 Feb 2025 15:48:18 -0500 Subject: [PATCH 05/12] Updates --- .../blazor/components/cascading-values-and-parameters.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/aspnetcore/blazor/components/cascading-values-and-parameters.md b/aspnetcore/blazor/components/cascading-values-and-parameters.md index 03e0aaccf95b..c892654de230 100644 --- a/aspnetcore/blazor/components/cascading-values-and-parameters.md +++ b/aspnetcore/blazor/components/cascading-values-and-parameters.md @@ -180,8 +180,6 @@ public static class CascadingApplicationStateServiceCollectionExtensions _ = NotifyChangedAsync(); } - public CascadingValueSource Source => source; - public void Dispose() { value.PropertyChanged -= HandlePropertyChanged; @@ -202,7 +200,7 @@ builder.Services.AddCascadingApplicationState( > [!NOTE] > †For Blazor Web App solutions consisting of server and client (`.Client`) projects: > -> * The preceding `NotifyingDalek.cs` and `CascadingValueSourceFactory.cs` files are placed in the `.Client` project. +> * The preceding `NotifyingDalek.cs` and `CascadingValueServiceCollectionExtensions.cs` files are placed in the `.Client` project. > * The preceding code is placed into the `Program` file of *both* projects. The following component is used to demonstrate how changing the value of `NotifyingDalek.Units` notifies subscribers. From 968e3b283e4b024ed9741f93524e74f6e022ba65 Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Tue, 25 Feb 2025 08:32:25 -0500 Subject: [PATCH 06/12] Updates --- .../cascading-values-and-parameters.md | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/aspnetcore/blazor/components/cascading-values-and-parameters.md b/aspnetcore/blazor/components/cascading-values-and-parameters.md index c892654de230..7107521e8d7e 100644 --- a/aspnetcore/blazor/components/cascading-values-and-parameters.md +++ b/aspnetcore/blazor/components/cascading-values-and-parameters.md @@ -139,9 +139,9 @@ public class NotifyingDalek : INotifyPropertyChanged } ``` -The following `CascadingValueServiceCollectionExtensions` creates a from a type that implements . +The following `CascadingStateServiceCollectionExtensions` creates a from a type that implements . -`CascadingValueServiceCollectionExtensions.cs`: +`CascadingStateServiceCollectionExtensions.cs`: ```csharp using System.ComponentModel; @@ -149,25 +149,25 @@ using Microsoft.AspNetCore.Components; namespace Microsoft.Extensions.DependencyInjection; -public static class CascadingApplicationStateServiceCollectionExtensions +public static class CascadingStateServiceCollectionExtensions { - public static IServiceCollection AddCascadingApplicationState( + public static IServiceCollection AddCascadingStateNotifier( this IServiceCollection serviceCollection, T value, bool isFixed = false) where T : INotifyPropertyChanged { return serviceCollection.AddCascadingValue(services => { - return new ApplicationStateCascadingValueSource(value, isFixed); + return new CascadingStateValueSource(value, isFixed); }); } - private sealed class ApplicationStateCascadingValueSource + private sealed class CascadingStateValueSource : CascadingValueSource, IDisposable where T : INotifyPropertyChanged { private readonly T value; private readonly CascadingValueSource source; - public ApplicationStateCascadingValueSource(T value, bool isFixed = false) + public CascadingStateValueSource(T value, bool isFixed = false) : base(value, isFixed = false) { this.value = value; @@ -193,15 +193,15 @@ The type's (`HandleProp In the `Program` file†, `NotifyingDalek` is passed to create a with an initial `Unit` value of 888 units: ```csharp -builder.Services.AddCascadingApplicationState( +builder.Services.AddCascadingStateNotifier( new NotifyingDalek() { Units = 888 }); ``` > [!NOTE] > †For Blazor Web App solutions consisting of server and client (`.Client`) projects: > -> * The preceding `NotifyingDalek.cs` and `CascadingValueServiceCollectionExtensions.cs` files are placed in the `.Client` project. -> * The preceding code is placed into the `Program` file of *both* projects. +> * The preceding `NotifyingDalek.cs` and `CascadingStateServiceCollectionExtensions.cs` files are placed in the `.Client` project. +> * The preceding code is placed into each project's `Program` file. The following component is used to demonstrate how changing the value of `NotifyingDalek.Units` notifies subscribers. @@ -276,6 +276,8 @@ To demonstrate multiple subscriber notifications, the following `DaleksMain` com Because the 's type in this example (`NotifyingDalek`) is a class type, you can meet virtually any state management feature specification requirement. However, subscriptions create overhead and reduce performance, so benchmark the performance of this approach in your app and compare it to other [state management approaches](xref:blazor/state-management) before adopting it in a production app with constrained processing and memory resources. +Any change in state (any property value change of the class) causes all subscribed components to rerender, regardless of which part of the state that they use. **Avoid creating a single large class representing the entire global application state.** Instead, create granular classes and cascade them separately with specific subscriptions to cascading parameters, ensuring that only components subscribed to a specific portion of the application state are affected by changes. + :::moniker-end ## `CascadingValue` component From c3b1cb78981118970b986df76b2080b9f95cad22 Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Tue, 25 Feb 2025 08:36:14 -0500 Subject: [PATCH 07/12] Updates --- aspnetcore/blazor/components/cascading-values-and-parameters.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aspnetcore/blazor/components/cascading-values-and-parameters.md b/aspnetcore/blazor/components/cascading-values-and-parameters.md index 7107521e8d7e..81aede886781 100644 --- a/aspnetcore/blazor/components/cascading-values-and-parameters.md +++ b/aspnetcore/blazor/components/cascading-values-and-parameters.md @@ -98,7 +98,7 @@ Calling to notify clients that a property value has changed. When the `Units` property is set, the (`PropertyChanged`) is invoked. +* `NotifyingDalek` implements to notify clients that a property value has changed. When the `Units` property is set, the (`PropertyChanged`) is invoked. Keep in mind for production code that any change in state (any property value change of the class) causes all subscribed components to rerender, regardless of which part of the state they use. We recommend creating granular classes, cascading them separately with specific subscriptions to ensure that only components subscribed to a specific portion of the application state are affected by changes. * The `SetUnitsToOneThousand` method can be triggered by subscribers to set `Units` to 1,000 with a simulated processing delay. `NotifyingDalek.cs`: From 99c0e3d6da3748ac4e7817cf60c5bfe5804b6b4f Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Tue, 25 Feb 2025 08:37:55 -0500 Subject: [PATCH 08/12] Updates --- .../blazor/components/cascading-values-and-parameters.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/aspnetcore/blazor/components/cascading-values-and-parameters.md b/aspnetcore/blazor/components/cascading-values-and-parameters.md index 81aede886781..cdb708f20bfb 100644 --- a/aspnetcore/blazor/components/cascading-values-and-parameters.md +++ b/aspnetcore/blazor/components/cascading-values-and-parameters.md @@ -98,9 +98,11 @@ Calling to notify clients that a property value has changed. When the `Units` property is set, the (`PropertyChanged`) is invoked. Keep in mind for production code that any change in state (any property value change of the class) causes all subscribed components to rerender, regardless of which part of the state they use. We recommend creating granular classes, cascading them separately with specific subscriptions to ensure that only components subscribed to a specific portion of the application state are affected by changes. +* `NotifyingDalek` implements to notify clients that a property value has changed. When the `Units` property is set, the (`PropertyChanged`) is invoked. * The `SetUnitsToOneThousand` method can be triggered by subscribers to set `Units` to 1,000 with a simulated processing delay. +Keep in mind for production code that any change in state (any property value change of the class) causes all subscribed components to rerender, regardless of which part of the state they use. We recommend creating granular classes, cascading them separately with specific subscriptions to ensure that only components subscribed to a specific portion of the application state are affected by changes. + `NotifyingDalek.cs`: ```csharp From fdc6a783e3842b85a8d556a66e4250e652d67495 Mon Sep 17 00:00:00 2001 From: Luke Latham <1622880+guardrex@users.noreply.github.com> Date: Tue, 25 Feb 2025 11:07:33 -0500 Subject: [PATCH 09/12] Apply suggestions from code review --- .../cascading-values-and-parameters.md | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/aspnetcore/blazor/components/cascading-values-and-parameters.md b/aspnetcore/blazor/components/cascading-values-and-parameters.md index cdb708f20bfb..9215fead14e4 100644 --- a/aspnetcore/blazor/components/cascading-values-and-parameters.md +++ b/aspnetcore/blazor/components/cascading-values-and-parameters.md @@ -75,7 +75,7 @@ The following `Daleks` component displays the cascaded values. :::moniker range=">= aspnetcore-8.0" -In the following example, `Dalek` is registered as a cascading value using [`CascadingValueSource`](xref:Microsoft.AspNetCore.Components.CascadingValueSource%601), where `` is the type. The `isFixed` flag indicates whether the value is fixed. If false, all recipients are subscribed for update notifications. Subscriptions create overhead and reduce performance, so set `isFixed` to `true` if the value doesn't change. +In the following example, `Dalek` is registered as a cascading value using [`CascadingValueSource`](xref:Microsoft.AspNetCore.Components.CascadingValueSource%601), where `` is the type. The `isFixed` flag indicates whether the value is fixed. If `false`, all recipients are subscribed for update notifications. Subscriptions create overhead and reduce performance, so set `isFixed` to `true` if the value doesn't change. ```csharp builder.Services.AddCascadingValue(sp => @@ -131,7 +131,7 @@ public class NotifyingDalek : INotifyPropertyChanged [CallerMemberName] string? propertyName = default) => PropertyChanged?.Invoke(this, new(propertyName)); - public async Task SetUnitsToOneThousand() + public async Task SetUnitsToOneThousandAsync() { // Simulate a three second delay in processing await Task.Delay(3000); @@ -154,10 +154,10 @@ namespace Microsoft.Extensions.DependencyInjection; public static class CascadingStateServiceCollectionExtensions { public static IServiceCollection AddCascadingStateNotifier( - this IServiceCollection serviceCollection, T value, bool isFixed = false) + this IServiceCollection services, T state, bool isFixed = false) where T : INotifyPropertyChanged { - return serviceCollection.AddCascadingValue(services => + return serviceCollection.AddCascadingValue(sp => { return new CascadingStateValueSource(value, isFixed); }); @@ -166,15 +166,15 @@ public static class CascadingStateServiceCollectionExtensions private sealed class CascadingStateValueSource : CascadingValueSource, IDisposable where T : INotifyPropertyChanged { - private readonly T value; + private readonly T state; private readonly CascadingValueSource source; - public CascadingStateValueSource(T value, bool isFixed = false) - : base(value, isFixed = false) + public CascadingStateValueSource(T state, bool isFixed = false) + : base(state, isFixed = false) { - this.value = value; - source = new CascadingValueSource(value, isFixed); - this.value.PropertyChanged += HandlePropertyChanged; + this.state= state; + source = new CascadingValueSource(state, isFixed); + this.state.PropertyChanged += HandlePropertyChanged; } private void HandlePropertyChanged(object? sender, PropertyChangedEventArgs e) @@ -184,7 +184,7 @@ public static class CascadingStateServiceCollectionExtensions public void Dispose() { - value.PropertyChanged -= HandlePropertyChanged; + state.PropertyChanged -= HandlePropertyChanged; } } } @@ -252,7 +252,7 @@ The following component is used to demonstrate how changing the value of `Notify { if (Dalek is not null) { - await Dalek.SetUnitsToOneThousand(); + await Dalek.SetUnitsToOneThousandAsync(); } } } @@ -278,7 +278,7 @@ To demonstrate multiple subscriber notifications, the following `DaleksMain` com Because the 's type in this example (`NotifyingDalek`) is a class type, you can meet virtually any state management feature specification requirement. However, subscriptions create overhead and reduce performance, so benchmark the performance of this approach in your app and compare it to other [state management approaches](xref:blazor/state-management) before adopting it in a production app with constrained processing and memory resources. -Any change in state (any property value change of the class) causes all subscribed components to rerender, regardless of which part of the state that they use. **Avoid creating a single large class representing the entire global application state.** Instead, create granular classes and cascade them separately with specific subscriptions to cascading parameters, ensuring that only components subscribed to a specific portion of the application state are affected by changes. +Any change in state (any property value change of the class) causes all subscribed components to rerender, regardless of which part of the state they use. **Avoid creating a single large class representing the entire global application state.** Instead, create granular classes and cascade them separately with specific subscriptions to cascading parameters, ensuring that only components subscribed to a specific portion of the application state are affected by changes. :::moniker-end From 0b9e9ca7d763817de9dd21628a8f07c81abe66ce Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Tue, 25 Feb 2025 11:21:29 -0500 Subject: [PATCH 10/12] Updates --- .../components/cascading-values-and-parameters.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/aspnetcore/blazor/components/cascading-values-and-parameters.md b/aspnetcore/blazor/components/cascading-values-and-parameters.md index 9215fead14e4..985e06ff4875 100644 --- a/aspnetcore/blazor/components/cascading-values-and-parameters.md +++ b/aspnetcore/blazor/components/cascading-values-and-parameters.md @@ -99,7 +99,7 @@ Calling to notify clients that a property value has changed. When the `Units` property is set, the (`PropertyChanged`) is invoked. -* The `SetUnitsToOneThousand` method can be triggered by subscribers to set `Units` to 1,000 with a simulated processing delay. +* The `SetUnitsToOneThousandAsync` method can be triggered by subscribers to set `Units` to 1,000 with a simulated processing delay. Keep in mind for production code that any change in state (any property value change of the class) causes all subscribed components to rerender, regardless of which part of the state they use. We recommend creating granular classes, cascading them separately with specific subscriptions to ensure that only components subscribed to a specific portion of the application state are affected by changes. @@ -153,13 +153,13 @@ namespace Microsoft.Extensions.DependencyInjection; public static class CascadingStateServiceCollectionExtensions { - public static IServiceCollection AddCascadingStateNotifier( + public static IServiceCollection AddNotifyingCascadingValue( this IServiceCollection services, T state, bool isFixed = false) where T : INotifyPropertyChanged { - return serviceCollection.AddCascadingValue(sp => + return services.AddCascadingValue(sp => { - return new CascadingStateValueSource(value, isFixed); + return new CascadingStateValueSource(state, isFixed); }); } @@ -172,7 +172,7 @@ public static class CascadingStateServiceCollectionExtensions public CascadingStateValueSource(T state, bool isFixed = false) : base(state, isFixed = false) { - this.state= state; + this.state = state; source = new CascadingValueSource(state, isFixed); this.state.PropertyChanged += HandlePropertyChanged; } @@ -195,7 +195,7 @@ The type's (`HandleProp In the `Program` file†, `NotifyingDalek` is passed to create a with an initial `Unit` value of 888 units: ```csharp -builder.Services.AddCascadingStateNotifier( +builder.Services.AddNotifyingCascadingValue( new NotifyingDalek() { Units = 888 }); ``` From 8ca035e825786bff6991b3cc8af3c509bf25ba27 Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Tue, 25 Feb 2025 13:56:51 -0500 Subject: [PATCH 11/12] Updates --- aspnetcore/blazor/components/cascading-values-and-parameters.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aspnetcore/blazor/components/cascading-values-and-parameters.md b/aspnetcore/blazor/components/cascading-values-and-parameters.md index 985e06ff4875..56dfe25d3ed0 100644 --- a/aspnetcore/blazor/components/cascading-values-and-parameters.md +++ b/aspnetcore/blazor/components/cascading-values-and-parameters.md @@ -157,7 +157,7 @@ public static class CascadingStateServiceCollectionExtensions this IServiceCollection services, T state, bool isFixed = false) where T : INotifyPropertyChanged { - return services.AddCascadingValue(sp => + return services.AddCascadingValue(sp => { return new CascadingStateValueSource(state, isFixed); }); From 2d49bcb6a90a84eb8f3f0425cdb9cdafb7c4a445 Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Tue, 25 Feb 2025 13:59:05 -0500 Subject: [PATCH 12/12] Updates --- .../blazor/components/cascading-values-and-parameters.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/aspnetcore/blazor/components/cascading-values-and-parameters.md b/aspnetcore/blazor/components/cascading-values-and-parameters.md index 56dfe25d3ed0..82d510502bf6 100644 --- a/aspnetcore/blazor/components/cascading-values-and-parameters.md +++ b/aspnetcore/blazor/components/cascading-values-and-parameters.md @@ -195,8 +195,7 @@ The type's (`HandleProp In the `Program` file†, `NotifyingDalek` is passed to create a with an initial `Unit` value of 888 units: ```csharp -builder.Services.AddNotifyingCascadingValue( - new NotifyingDalek() { Units = 888 }); +builder.Services.AddNotifyingCascadingValue(new NotifyingDalek() { Units = 888 }); ``` > [!NOTE]