Blazor components can accept parameters that are used to pass information from a parent component to a child component. This is a powerful feature however in a real-world application such as Oqtane where you may have many layers of nested components, it can be quite cumbersome and verbose if you need to pass a parameter value explicitly through all of the hierarchical layers. Cascading parameters solve this problem by providing a convenient way for an ancestor component to provide a value that is then available to all descendent components.
Cascading components are easy to use in Blazor. Blazor provides a special component called CascadingValue. This component allows whatever value is passed to it to be cascaded down its component tree to all of its descendants. The descendant components can then choose to utilize the value by declaring a property of the same type decorated with the [CascadingParameter] attribute.
In addition to simplifying the passing of parameter values between components, cascading parameters also have an additional benefit of maintaining state within your Blazor application. You can wrap your ancestor application component in a cascading parameter where you can load some global application state during startup that is then available to all child components for the lifetime of your application.
Let 's see how this is implemented in Oqtane.
The App.razor component is the entry point for the application and it contains a reference to the custom SiteRouter component. There is a variety of application state that we want to load and share with all of the nested child components that are loaded dynamically at run-time. We accomplish this by wrapping the SiteRouter in a CascadingValue component. The Cascading Value component includes a Value parameter which references a local variable called PageState which is going to act as the container for our shared application state that is available to all hierarchical child components in the render tree.
<CascadingValue Value="@PageState">
<SiteRouter OnStateChange="@ChangeState" />
</CascadingValue>
@functions {
private PageState PageState { get; set; }
private void ChangeState(PageState pagestate)
{
PageState = pagestate;
StateHasChanged();
}
}
Now lets look at our SiteRouter component. You can see that it declares a PageState property with a [CascadingParameter] attribute. This allows our SiteRouter to access the value which was set in the App.razor component. You will also notice it has a parameter named OnStateChange. This is a special type of Action parameter that allows our SiteRouter child component to communicate with the App.razor parent component which I will explain in a moment.
@DynamicComponent
@functions {
[CascadingParameter] PageState PageState { get; set; }
[Parameter] Action<PageState> OnStateChange { get; set; }
RenderFragment DynamicComponent { get; set; }
...
The SiteRouter is responsible for responding to any change in the client Url and loading the appropriate PageState resources to render the page. However we don 't want to constantly reload all of the PageState resources so we use conditional logic to determine if a PageState property value has changed or if we can rely on the property value which is already loaded. You can see an example of this logic below where we are checking if the collection of site alias ' have been previously loaded for this site. If so then we use the value which exists in the PageState already; otherwise, we load the collection by calling the Alias service. You should be able to imagine how this approach drastically reduces the number of service calls an application needs to make because it is taking advantage of state which is already loaded. This is true regardless of whether the application is running in client-side mode or server-side mode, andd it creates a very high performance and responsive application.
if (PageState == null)
{
aliases = await AliasService.GetAliasesAsync();
alias = null;
}
else
{
aliases = PageState.Aliases;
alias = PageState.Alias;
}
...
Any required changes to the PageState need to be updated within the SiteRouter and the new PageState object is passed to App.razor using the OnStateChange parameter. This is accomplished by invoking the Action in the App.razor component.
OnStateChange?.Invoke(pagestate);
The PageState cascading parameter is available to all hierarchical child components in the render tree culminating at the Module level. Modules can take advantage of PageState without explicitly declaring the [CascadingParameter] in their component logic because it is defined in the ModuleBase class that all modules must inherit from.
namespace Oqtane.Modules
{
public class ModuleBase : ComponentBase, IModuleControl
{
[CascadingParameter]
protected PageState PageState { get; set; }
Hopefully this blog has explained the technical implementation details as well as the benefits of cascading parameters in Blazor. It is a powerful technique which developers should take advantage in their real world Blazor applications.