Applying Layouts in ASP.NET Core Blazor

Apply a layout to a component

@page "/episodes"
@layout DoctorWhoLayout

<h2>Episodes</h2>

<ul>
    <li>
        <a href="https://www.bbc.co.uk/programmes/p00vfknq">
            <em>The Ribos Operation</em>
        </a>
    </li>
    <li>
        <a href="https://www.bbc.co.uk/programmes/p00vfdsb">
            <em>The Sun Makers</em>
        </a>
    </li>
    <li>
        <a href="https://www.bbc.co.uk/programmes/p00vhc26">
            <em>Nightmare of Eden</em>
        </a>
    </li>
</ul>

Apply a layout to a folder of components

_Imports.razor:

@layout DoctorWhoLayout
...

Apply a default layout to an app

App.razor:

<Router AppAssembly="@typeof(Program).Assembly">
    <Found Context="routeData">
        <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
    </Found>
    <NotFound>
        <p>Sorry, there's nothing at this address.</p>
    </NotFound>
</Router>

Apply a layout to arbitrary content (LayoutView component)

App.razor:

<Router AppAssembly="@typeof(Program).Assembly">
    <Found Context="routeData">
        <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
    </Found>
    <NotFound>
        <LayoutView Layout="@typeof(ErrorLayout)">
            <h1>Page not found</h1>
            <p>Sorry, there's nothing at this address.</p>
        </LayoutView>
    </NotFound>
</Router>

References
https://docs.microsoft.com/en-us/aspnet/core/blazor/components/layouts?view=aspnetcore-6.0#apply-a-layout
https://blazor-university.com/layouts/using-layouts/

Navigate from One Component to another Component in ASP.NET Core Blazor

Navigating from link

<h3>Anchor Link</h3>
<p>
    <a href="/navigate1">Navigate 1</a><br />
</p>

<h3>Nav Link</h3>
<p>
    <NavLink href="/navigate2">Navigate 2</NavLink><br />
</p>

Navigate from code

@page "/page1"
@inject NavigationManager UriHelper

<h3>Naviagte to another component Programatically</h3>
<button @onclick=@Navigate>Navigate</button>


@code {
void Navigate()
{
    UriHelper.NavigateTo("page2");
}
}

References
https://www.syncfusion.com/faq/blazor/routing/how-do-you-navigate-from-one-component-to-another-component-in-asp-net-core-blazor

Built-in Event Arguments in ASP.NET Core Blazor

@page "/event-handler-example-3"

@for (var i = 0; i < 4; i++)
{
    <p>
        <button @onclick="ReportPointerLocation">
            Where's my mouse pointer for this button?
        </button>
    </p>
}

<p>@mousePointerMessage</p>

@code {
    private string? mousePointerMessage;

    private void ReportPointerLocation(MouseEventArgs e)
    {
        mousePointerMessage = $"Mouse coordinates: {e.ScreenX}:{e.ScreenY}";
    }
}

References
https://docs.microsoft.com/en-us/aspnet/core/blazor/components/event-handling#built-in-event-arguments

Event Handling in ASP.NET Core Blazor

<button @onclick="() => { counter += 1; }">Button1</button>
<button @onclick="Increment1">Button2</button>
<button @onclick="Increment2">Button2</button>

<div>
    <span>Count : </span>
    <span>@counter</span>
</div>

@code
{
    private int counter = 0;

    private void Increment1()
    {
        counter += 1;
    }

    private async Task Increment2()
    {
        await Task.Delay(1000);
        counter += 1;
    }
}

References
https://docs.microsoft.com/en-us/aspnet/core/blazor/components/event-handling

Data Binding in ASP.NET Core Blazor

<p>
    <input @bind="inputValue" />
</p>

<p>@inputValue</p>

@code {
    private string? inputValue;
}

Equivalent HTML binding

<p>
    <label>
        Normal Blazor binding: 
        <input @bind="InputValue" />
    </label>
</p>

<p>
    <label>
        Demonstration of equivalent HTML binding: 
        <input value="@InputValue"
            @onchange="@((ChangeEventArgs __e) => InputValue = __e?.Value?.ToString())" />
    </label>
</p>

<p>
    <code>InputValue</code>: @InputValue
</p>

@code {
    private string? InputValue { get; set; }
}

Bind a property or field on other Document Object Model (DOM) events

@page "/bind-event"

<p>
    <input @bind="InputValue" @bind:event="oninput" />
</p>

<p>
    <code>InputValue</code>: @InputValue
</p>

@code {
    private string? InputValue { get; set; }
}

References
https://docs.microsoft.com/en-us/aspnet/core/blazor/components/data-binding

Transient fault handling in gRPC with retries in C#

gRPC calls can be interrupted by transient faults. Transient faults include:

  • Momentary loss of network connectivity.
  • Temporary unavailability of a service.
  • Timeouts due to server load.

When a gRPC call is interrupted, the client throws an RpcException with details about the error. The client app must catch the exception and choose how to handle the error.

var client = new Greeter.GreeterClient(channel);
try
{
    var response = await client.SayHelloAsync(
        new HelloRequest { Name = ".NET" });

    Console.WriteLine("From server: " + response.Message);
}
catch (RpcException ex)
{
    // Write logic to inspect the error and retry
    // if the error is from a transient fault.
}

Duplicating retry logic throughout an app is verbose and error-prone. Fortunately, the .NET gRPC client now has built-in support for automatic retries. Retries are centrally configured on a channel, and there are many options for customizing retry behavior using a RetryPolicy.

var defaultMethodConfig = new MethodConfig
{
    Names = { MethodName.Default },
    RetryPolicy = new RetryPolicy
    {
        MaxAttempts = 5,
        InitialBackoff = TimeSpan.FromSeconds(1),
        MaxBackoff = TimeSpan.FromSeconds(5),
        BackoffMultiplier = 1.5,
        RetryableStatusCodes = { StatusCode.Unavailable }
    }
};

// Clients created with this channel will automatically retry failed calls.
var channel = GrpcChannel.ForAddress("https://localhost:5001", new GrpcChannelOptions
{
    ServiceConfig = new ServiceConfig { MethodConfigs = { defaultMethodConfig } }
});

References
https://devblogs.microsoft.com/dotnet/grpc-in-dotnet-6/
https://docs.microsoft.com/en-us/aspnet/core/grpc/retries?view=aspnetcore-6.0

gRPC client-side load balancing in C#

The following code example configures a channel to use DNS service discovery with round-robin load balancing:

var channel = GrpcChannel.ForAddress(
    "dns:///my-example-host",
    new GrpcChannelOptions
    {
        Credentials = ChannelCredentials.Insecure,
        ServiceConfig = new ServiceConfig { LoadBalancingConfigs = { new RoundRobinConfig() } }
    });
var client = new Greet.GreeterClient(channel);

var response = await client.SayHelloAsync(new HelloRequest { Name = "world" });

References
https://devblogs.microsoft.com/dotnet/grpc-in-dotnet-6/
https://docs.microsoft.com/en-us/aspnet/core/grpc/loadbalancing?view=aspnetcore-6.0

Parallel.ForEachAsync in .NET 6

using System.Net.Http.Headers;
using System.Net.Http.Json;

var userHandlers = new []  { "users/VahidN", "users/shanselman", "users/jaredpar", "users/davidfowl" };

using HttpClient client = new()
{
    BaseAddress = new Uri("https://api.github.com"),
};
client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("DotNet", "6"));

ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = 3 };

await Parallel.ForEachAsync(userHandlers, parallelOptions, async (uri, token) =>
{
    var user = await client.GetFromJsonAsync<GitHubUser>(uri, token);
    Console.WriteLine($"Name: {user.Name}\nBio: {user.Bio}\n");
});

public class GitHubUser
{
    public string Name { get; set; }
    public string  Bio { get; set; }
}

References
https://www.hanselman.com/blog/parallelforeachasync-in-net-6