Cascading Values and Parameters in Blazor

MainLayout.razor

@inherits LayoutComponentBase

<PageTitle>BlazorApp1</PageTitle>

<div class="page">
    <div class="sidebar">
        <NavMenu/>
    </div>

    <main>
        <div class="top-row px-4">
            <a href="https://docs.microsoft.com/aspnet/" target="_blank">About</a>
        </div>

        <article class="content px-4">
            <CascadingValue Value="@Color">
                @Body
            </CascadingValue>
        </article>
    </main>
</div>

@code
{
    private string Color = "Red";
}

Index.razor

@page "/"

<div style="color: @Color">Hello World</div>

@code
{
    [CascadingParameter]
    public string? Color { get; set; }
}

Cascade multiple values

MainLayout.razor

@inherits LayoutComponentBase

<PageTitle>BlazorApp1</PageTitle>

<div class="page">
    <div class="sidebar">
        <NavMenu/>
    </div>

    <main>
        <div class="top-row px-4">
            <a href="https://docs.microsoft.com/aspnet/" target="_blank">About</a>
        </div>

        <article class="content px-4">
            <CascadingValue Value="@Color" Name="Value">
                <CascadingValue Value="@Size" Name="Size">
                    @Body
                </CascadingValue>
            </CascadingValue>
        </article>
    </main>
</div>

@code
{
    private string Color = "Red";
    private string Size = "12px";
}

Index.razor

@page "/"

<div style="color: @Color;font-size: @Size">Hello World</div>

@code
{
    [CascadingParameter(Name = "Color")]
    public string? Color { get; set; }

    [CascadingParameter(Name = "Size")]
    public string? Size { get; set; }
}

Cascade multiple values using Class

MainLayout.razor

@inherits LayoutComponentBase

<PageTitle>BlazorApp1</PageTitle>

<div class="page">
    <div class="sidebar">
        <NavMenu/>
    </div>

    <main>
        <div class="top-row px-4">
            <a href="https://docs.microsoft.com/aspnet/" target="_blank">About</a>
        </div>

        <article class="content px-4">
            <CascadingValue Value="@appState">
                    @Body
                </CascadingValue>
        </article>
    </main>
</div>

@code
{
    private AppState appState = new AppState();
    
    public class AppState
    {
        public string Color = "Red";
        public string Size = "16px";    
    }
}

Index.razor

@page "/"

<div style="color: @AppState.Color;font-size: @AppState.Size">Hello World</div>

@code
{
    [CascadingParameter]
    public MainLayout.AppState? AppState { get; set; }
}

Pass data across a component hierarchy

MainLayout.razor

@inherits LayoutComponentBase

<PageTitle>BlazorApp1</PageTitle>

<div class="page">
    <div class="sidebar">
        <NavMenu/>
    </div>

    <main>
        <div class="top-row px-4">
            <a href="https://docs.microsoft.com/aspnet/" target="_blank">About</a>
        </div>

        <article class="content px-4">
            <CascadingValue Value="@appState">
                    @Body
                </CascadingValue>
        </article>
    </main>
</div>

@code
{
    private AppState appState = new AppState();
    
    public class AppState
    {
        public string Color = "red";
        public string Size = "16px";    
    }
}

Counter.razor

@page "/counter"

<div class="d-flex align-items-center">
    <label>Color</label>
    <div style="width: 150px;padding-right: 10px;">
        <select @bind="@AppState.Color" class="form-control">
            <option value="red">Red</option>
            <option value="green">Green</option>
            <option value="blue">Blue</option>
        </select>
    </div>
    <label>Size</label>
    <div style="width: 150px;">
        <select @bind="@AppState.Size" class="form-control">
            <option value="16px">16px</option>
            <option value="20px">20px</option>
            <option value="30px">30px</option>
        </select>
    </div>
</div>

@code
{
    [CascadingParameter]
    public MainLayout.AppState AppState { get; set; }
}

Index.razor

@page "/"

<div style="color: @AppState.Color;font-size: @AppState.Size">Hello World</div>

@code
{
    [CascadingParameter]
    public MainLayout.AppState? AppState { get; set; }
}

References
https://docs.microsoft.com/en-us/aspnet/core/blazor/components/cascading-values-and-parameters?view=aspnetcore-6.0

CSS isolation in Blazor

To enable CSS isolation, create a razor.css file matching the .razor file for the component in the same folder.
For example, if your component is named “Isolate.razor,” create a file alongside the component named “Isolate.razor.css.” The Isolate.razor.css file is placed in the same folder as the Isolate.razor component.

[Pages/Isolate.razor]

@page "/isolate"
    
<h1>Hello, World</h1>
<p>Welcome   to your new app</p>

[Pages/Isolate.razor.css]

h1 {
       color: blue;
       font-style: italic;
       text-shadow: 2px 2px 2px gray;
}

References
https://docs.microsoft.com/en-us/aspnet/core/blazor/components/css-isolation?view=aspnetcore-6.0
https://www.syncfusion.com/faq/blazor/general/what-is-blazor-css-isolation-how-do-you-enable-it-for-your-application

JavaScript isolation in Blazor

wwwroot/scripts.js:

export function showPrompt(message) {
  return prompt(message, 'Type anything here');
}

Pages/CallJsExample6.razor:

@page "/call-js-example-6"
@implements IAsyncDisposable
@inject IJSRuntime JS

<h1>Call JS Example 6</h1>

<p>
    <button @onclick="TriggerPrompt">Trigger browser window prompt</button>
</p>

<p>
    @result
</p>

@code {
    private IJSObjectReference? module;
    private string? result;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            module = await JS.InvokeAsync<IJSObjectReference>("import", 
                "./scripts.js");
        }
    }

    private async Task TriggerPrompt()
    {
        result = await Prompt("Provide some text");
    }

    public async ValueTask<string?> Prompt(string message) =>
        module is not null ? 
            await module.InvokeAsync<string>("showPrompt", message) : null;

    async ValueTask IAsyncDisposable.DisposeAsync()
    {
        if (module is not null)
        {
            await module.DisposeAsync();
        }
    }
}

References
https://docs.microsoft.com/en-us/aspnet/core/blazor/javascript-interoperability/call-javascript-from-dotnet?view=aspnetcore-6.0#javascript-isolation-in-javascript-modules
https://www.syncfusion.com/faq/blazor/general/what-is-javascript-isolation-in-blazor-components

Call Class instance methods from JavaScript functions in Blazor

The component should keep a reference to the DotNetObjectReference we create.
The component should implement IDisposable and dispose our DotNetObjectReference.

@page "/"
@inject IJSRuntime JSRuntime
@implements IDisposable

<h1>Text received</h1>
<ul>
  @foreach (string text in TextHistory)
  {
    <li>@text</li>
  }
</ul>

@code
{
  List<string> TextHistory = new List<string>();
  DotNetObjectReference<Index> ObjectReference;

  protected override async Task OnAfterRenderAsync(bool firstRender)
  {
    await base.OnAfterRenderAsync(firstRender);
    if (firstRender)
    {
      ObjectReference = DotNetObjectReference.Create(this);
      await JSRuntime.InvokeVoidAsync("BlazorUniversity.startRandomGenerator", ObjectReference);
    }
  }

  [JSInvokable("AddText")]
  public void AddTextToTextHistory(string text)
  {
    TextHistory.Add(text.ToString());
    while (TextHistory.Count > 10)
      TextHistory.RemoveAt(0);
    StateHasChanged();
    System.Diagnostics.Debug.WriteLine("DotNet: Received " + text);
  }

  public void Dispose()
  {
    GC.SuppressFinalize(this);

    if (ObjectReference != null)
    {
      //Now dispose our object reference so our component can be garbage collected
      ObjectReference.Dispose();
    }
  }
}
var BlazorUniversity = BlazorUniversity || {};
BlazorUniversity.startRandomGenerator = function (dotNetObject) {
  return setInterval(function () {
    let text = Math.random() * 1000;
    console.log("JS: Generated " + text);
    dotNetObject.invokeMethodAsync('AddText', text.toString());
  }, 1000);
};
BlazorUniversity.stopRandomGenerator = function (handle) {
  clearInterval(handle);
};

References
https://docs.microsoft.com/en-us/aspnet/core/blazor/javascript-interoperability/call-dotnet-from-javascript?view=aspnetcore-6.0#class-instance-examples
https://blazor-university.com/javascript-interop/calling-dotnet-from-javascript/
https://blazor-university.com/javascript-interop/calling-dotnet-from-javascript/lifetimes-and-memory-leaks/

Call static .NET methods from JavaScript functions in Blazor

DotNet.invokeMethodAsync('{ASSEMBLY NAME}', '{.NET METHOD ID}', {ARGUMENTS});
  • The {ASSEMBLY NAME} placeholder is the app’s assembly name.
  • The {.NET METHOD ID} placeholder is the .NET method identifier.
  • The {ARGUMENTS} placeholder are optional, comma-separated arguments to pass to the method, each of which must be JSON-serializable.
@page "/call-dotnet-example-1"

<h1>Call .NET Example 1</h1>

<p>
    <button onclick="returnArrayAsync()">
        Trigger .NET static method
    </button>
</p>

@code {
    [JSInvokable]
    public static Task<int[]> ReturnArrayAsync()
    {
        return Task.FromResult(new int[] { 1, 2, 3 });
    }
}
<script>
  window.returnArrayAsync = () => {
    DotNet.invokeMethodAsync('BlazorSample', 'ReturnArrayAsync')
      .then(data => {
        console.log(data);
      });
    };
</script>

References
https://docs.microsoft.com/en-us/aspnet/core/blazor/javascript-interoperability/call-dotnet-from-javascript?view=aspnetcore-6.0#invoke-a-static-net-method
https://blazor-university.com/javascript-interop/calling-dotnet-from-javascript/calling-static-dotnet-methods/

Split HTML And C# Code In Blazor Using Partial Class

Partial Class is a feature of implementing a single class into multiple files. So now we will maintain the Html code in Counter.razor file and C# code in Counter.razor.cs file. Counter.razor.cs file acts as a code-behind file for Counter.razor file.

Counter.razor

@page "/counter"

<PageTitle>Counter</PageTitle>

<h1>Counter</h1>

<p role="status">Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

Counter.razor.cs

namespace BlazorApp1.Pages;

public partial class Counter
{
    private int currentCount = 0;

    private void IncrementCount()
    {
        currentCount++;
    }
}

References
https://www.learmoreseekmore.com/2020/06/blazor-paratial-class-or-componentbase-class.html

Suppress UI refreshing in Blazor using ShouldRender

@page "/control-render"

<label>
    <input type="checkbox" @bind="shouldRender" />
    Should Render?
</label>

<p>Current count: @currentCount</p>

<p>
    <button @onclick="IncrementCount">Click me</button>
</p>

@code {
    private int currentCount = 0;
    private bool shouldRender = true;

    protected override bool ShouldRender()
    {
        return shouldRender;
    }

    private void IncrementCount()
    {
        currentCount++;
    }
}

ShouldRender is called each time a component is rendered. Override ShouldRender to manage UI refreshing. If the implementation returns true, the UI is refreshed.

Even if ShouldRender is overridden, the component is always initially rendered.

References
https://docs.microsoft.com/en-us/aspnet/core/blazor/components/rendering?view=aspnetcore-6.0#suppress-ui-refreshing-shouldrender
https://www.syncfusion.com/faq/how-do-i-suppress-the-ui-rendering-in-blazor

Templating components with RenderFragments in Blazor

Sometimes we need to create components that mix consumer-supplied mark-up with their own rendered output.
It would be very messy to pass content to a component as an HTML encoded string parameter:

<Collapsible content="Lots of encoded HTML for your entire view here"/>

And, in addition to the maintenance nightmare, the embedded HTML could only be basic HTML mark-up too, no Blazor components. Basically, it’d be useless, and obviously that’s not how it should be done. The correct approach is to use a RenderFragment.

Without RenderFragment

Index.razor

<table class="table">
    <tr>
        <th>Name</th>
        <th>Gender</th>
        <th>Age</th>
    </tr>
    <tr>
        <td>John</td>
        <td>Male</td>
        <td>37</td>
    </tr>
    <tr>
        <td>Rose</td>
        <td>Female</td>
        <td>32</td>
    </tr>
    <tr>
        <td>Martin</td>
        <td>Male</td>
        <td>1</td>
    </tr>
</table>

Child Content

These are the criteria Blazor uses to inject embedded content into a component. The embedded content may be anything you wish; plain text, HTML elements, more razor mark-up (including more components), and the content of that embedded content may be output anywhere in your component’s mark-up simply by adding @ChildContent.

TableTemplate.razor

<table class="table">
    <tr>
        <th>Name</th>
        <th>Gender</th>
        <th>Age</th>
    </tr>

    @ChildContent
</table>

@code {
    [Parameter]
    public RenderFragment? ChildContent { get; set; }
}

Index.razor

@page "/"

<TableTemplate>
    <tr>
        <td>John</td>
        <td>Male</td>
        <td>37</td>
    </tr>
    <tr>
        <td>Rose</td>
        <td>Female</td>
        <td>32</td>
    </tr>
    <tr>
        <td>Martin</td>
        <td>Male</td>
        <td>1</td>
    </tr>
</TableTemplate>

Multiple RenderFragments

When we write mark-up inside a component, Blazor will assume it should be assigned to a Parameter on the component that is descended from the RenderFragment class and is named ChildContent. If we wish to use a different name, or multiple render fragments, then we must explicitly specify the parameter’s name in our mark-up.

TableTemplate.razor

<table class="table">
    <tr>
        @TableHeader
    </tr>

    @ChildContent
</table>

@code {
    [Parameter]
    public RenderFragment TableHeader { get; set; }

    [Parameter]
    public RenderFragment? ChildContent { get; set; }
}

Index.razor

@page "/"

<TableTemplate>
    <TableHeader>
        <th>Name</th>
        <th>Gender</th>
        <th>Age</th>
    </TableHeader>

    <ChildContent>
        <tr>
            <td>John</td>
            <td>Male</td>
            <td>37</td>
        </tr>
        <tr>
            <td>Rose</td>
            <td>Female</td>
            <td>32</td>
        </tr>
        <tr>
            <td>Martin</td>
            <td>Male</td>
            <td>1</td>
        </tr>
    </ChildContent>

</TableTemplate>

Passing data to a RenderFragment

As well as the standard RenderFragment class, there is also a generic RenderFragment<T> class that can be used to pass data into the RenderFragment.

TableTemplate.razor

@using System.Diagnostics.CodeAnalysis
@typeparam TItem

<table class="table">
    <tr>
        @TableHeader
    </tr>

    @foreach (var item  in Items)
    {
        if (RowTemplate is not null)
        {
            <tr>@RowTemplate(item)</tr>
        }
    }
</table>

@code {

    [Parameter]
    public RenderFragment TableHeader { get; set; }

    [Parameter, AllowNull]
    public IReadOnlyList<TItem> Items { get; set; }

    [Parameter]
    public RenderFragment<TItem>? RowTemplate { get; set; }

}

Index.razor

@page "/"

<TableTemplate Items="people" Context="person">
    <TableHeader>
        <th>Name</th>
        <th>Gender</th>
        <th>Age</th>
    </TableHeader>

    <RowTemplate>
        <td>@person.Name</td>
        <td>@person.Gender</td>
        <td>@person.Age</td>
    </RowTemplate>

</TableTemplate>

@code
{
    private List<Person> people = new()
    {
        new Person() { Name = "John", Gender = "Male", Age = 37 },
        new Person() { Name = "Rose", Gender = "Female", Age = 32 },
        new Person() { Name = "Martin", Gender = "Male", Age = 1 },
    };

    private class Person
    {
        public string Name { get; set; }
        public string Gender { get; set; }
        public int Age { get; set; }
    }
}

References
https://blazor-university.com/templating-components-with-renderfragements/
https://blazor-university.com/templating-components-with-renderfragements/passing-data-to-a-renderfragement/
https://docs.microsoft.com/en-us/aspnet/core/blazor/components/templated-components?view=aspnetcore-6.0

Attribute Splatting and Arbitrary Parameters in Blazor

Components can capture and render additional attributes in addition to the component’s declared parameters. Additional attributes can be captured in a dictionary and then splatted onto an element when the component is rendered using the @attributes Razor directive attribute. This scenario is useful for defining a component that produces a markup element that supports a variety of customizations.

[Parameter(CaptureUnmatchedValues =true)]
public Dictionary<string, object> 
ArbitraryAttributeDictionary { get; set; }

Example

DummyText.razor

<div @attributes="TextAttributes">
    @Value
</div>

@code {

    [Parameter]
    public string Value { get; set; }

    [Parameter(CaptureUnmatchedValues = true)]
    public Dictionary<string, object> TextAttributes  { get; set; }

}

Index.razor

@page "/"

<DummyText Value="Hello World" Title="Hi"></DummyText>

References
https://docs.microsoft.com/en-us/aspnet/core/blazor/components/?view=aspnetcore-6.0#attribute-splatting-and-arbitrary-parameters
http://www.binaryintellect.net/articles/f52ae3dc-53fd-4342-97d1-e9cdcf47cd11.aspx

ASP.NET Core Blazor State Management

We can use ASP.NET Core Protected Browser Storage:

Use the Local Storage

@page "/"
@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage
@inject ProtectedLocalStorage BrowserStorage

<h1>Local Storage!</h1>

<input class="form-control" @bind="currentInputValue" />
<button class="btn btn-secondary" @onclick="Save">Save</button>
<button class="btn btn-secondary" @onclick="Read">Read</button>
<button class="btn btn-secondary" @onclick="Delete">Delete</button>

@code {
  string currentInputValue;

  public async Task Save()
  {
    await BrowserStorage.SetAsync("name", currentInputValue);
  }

  public async Task Read()
  {
    var result = await BrowserStorage.GetAsync<string>("name");
    currentInputValue = result.Success ? result.Value : "";
  }

  public async Task Delete()
  {
    await BrowserStorage.DeleteAsync("name");
  }
}

Use the Session Storage

@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage
@inject ProtectedSessionStorage ProtectedSessionStore

References
https://docs.microsoft.com/en-us/aspnet/core/blazor/state-management?view=aspnetcore-6.0&pivots=server
https://www.thomasclaudiushuber.com/2021/04/19/store-data-of-your-blazor-app-in-the-local-storage-and-in-the-session-storage/