Telerik blogs

With auto render mode, your component will run either on the server or in the browser, depending on how fast Blazor WASM can spin up. But now you need to figure out how to fetch and share data between components. Here are some handy techniques you can use.

.NET 8 brings a number of changes to Blazor but perhaps the biggest arrives with its multiple render modes.

It’s now possible to have your components run in different ways:

  • On the server, using static server-side rendering
  • Using Blazor Server
  • Using Blazor WASM
  • Using the new Auto mode (WASM if available, otherwise Server)

The challenge, if you choose to mix and match render modes, lies in figuring out how to handle fetching (and sharing) data when your components can run in different places.

In this article, we’ll explore some options for handling data when your components run using the new auto mode, plus some tips for sharing state as you go.

Routing to a Component Rendered Using InteractiveAuto

Let’s first start with a scenario where we want to render weather data in a component running via InteractiveAuto render mode.

@page "/Weather"
@rendermode InteractiveAuto

With InteractiveAuto as the render mode, Blazor will attempt to run this component via Blazor WASM when a user visits /Weather.

If the WASM runtime isn’t in the browser’s cache and/or can’t load quickly enough, then Server will be used instead, as a fallback.

Add to this the fact that .NET 8 Razor components are also prerendered by default and you end up with a component which could be rendered:

  • Once on the server (prerendering), using server-side rendering (where plain HTML will be returned to the browser)
  • Then again using either:
    • Blazor Server, or
    • Blazor WASM

The upshot of this is we need to ensure our humble component works in all these scenarios!

Now your first thought might be to create a routable component, and run the whole thing using InteractiveAuto.

In a minute we’ll explore an alternative, which turns out to be a simpler option, but let’s stick with this for now.

Here’s an example of a simple page to fetch weather data.

BlazorDemoApp.Client

@page "/Weather"
@using BlazorDemoApp.Shared.Weather
@rendermode InteractiveAuto
@inject HttpClient HttpClient

<h1>Weather</h1>

@if (_model?.Forecasts == null)
{
    <p>
        <em>Loading...</em>
    </p>
}
else
{
    <!-- show weather data here -->
}
@code {

    private WeatherForecastModel? _forecasts;

    protected override async Task OnInitializedAsync()
    {
        _forecasts = await HttpClient.GetFromJsonAsync<WeatherForecastModel>("api/weather");
    }
}

Notice how the page specifies InteractiveAuto as its render mode.

The challenge with this approach comes from the fact this component will, at some point, render via Blazor WASM. When it does, the component will need to make calls to the backend via HTTP, as it has no direct access to the server and/or resources (such as databases).

Here’s the demo endpoint which returns random weather data.

public static class WeatherEndpoints
{
    public static void MapWeatherEndpoints(this WebApplication app)
    {
        app.MapGet("api/weather", Handler);
    }

    private static IResult Handler(HttpContext context)
    {
        var startDate = DateOnly.FromDateTime(DateTime.Now);
        var summaries = new[]
            { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" };
        var forecasts = Enumerable.Range(1, 5).Select(index => new WeatherForecast
        {
            Date = startDate.AddDays(index),
            TemperatureC = Random.Shared.Next(-20, 55),
            Summary = summaries[Random.Shared.Next(summaries.Length)]
        }).ToList();

    	return Results.Ok(new WeatherForecastModel { Forecasts = forecasts });
    }
}

When we test this in the browser all seems to work OK.

The page loads, data is fetched and displayed.

Weather demo page showing forecasts data in a table

We can see the HTTP call is made to fetch weather data, and the data is shown in the UI.

But there are a couple of problems.

Problem 1: The Data Is Being Fetched Twice

Reviewing this in the browser, we see some initial data which is then replaced by new data. Because we’re generating random data, we can see this happen, where one set of weather data is shown then replaced by another set of (different) weather data.

Weather table which starts with one set of forecasts then, after a second or two, shows different data even though the user is still looking at the same page

This is down to prerendering, where the component is rendered first on the server. This helps ensure the user seems something while one of the interactive modes kicks in.

When the component renders again, using either Blazor Server or Blazor WASM, a new call is made to the API. At this point, new data is fetched, and the component shows that new data.

Problem 2: The Server Makes an Unnecessary Hop via HTTP

The second problem isn’t a showstopper, but is a little unnecessary.

Even when the component is (pre)rendered on the server, or rendered via Blazor Server, we’re using HttpClient to make a call, via the network, to our backend API.

Given the API and the app are one and the same, this indirection and call via HTTP is unnecessary, when we could just go direct to the DB/Backend service instead.

Of course, when running on WASM, we do still need that HTTP call to the backend.

Let’s tackle these two problems in order.

Avoiding Multiple Calls

First, let’s tackle the double load of data.

It’s fine for our component to fetch data during pre-rendering, but ideally we’d like to then reuse that data when the component renders again via Server or WASM.

One way to achieve that is to use PersistentComponentState to store the retrieved data during the first (pre)render, then retrieve it when the component renders interactively.

BlazorDemoApp.Client

@page "/Weather"
@using BlazorDemoApp.Shared.Weather

@rendermode InteractiveAuto
@inject HttpClient HttpClient
@inject PersistentComponentState ApplicationState
@implements IDisposable

<h1>Weather</h1>

@if (_model?.Forecasts == null)
{
    <p>
        <em>Loading...</em>
    </p>
}
else
{
    <!-- loop over _mode.Forecasts and render UI here -->
}
@code {

    private WeatherForecastModel? _model;
    private PersistingComponentStateSubscription _subscription;

    protected override async Task OnInitializedAsync()
    {
        _subscription = ApplicationState.RegisterOnPersisting(Persist);

        var foundInState = ApplicationState.TryTakeFromJson<WeatherForecastModel>("weather", out var forecasts);

        _model = foundInState
            ? forecasts
            : await HttpClient.GetFromJsonAsync<WeatherForecastModel>("api/weather");
    }

    private Task Persist()
    {
       ApplicationState.PersistAsJson("weather", _model);
       return Task.CompletedTask;
    }

    public void Dispose()
    {
        _subscription.Dispose();
    }

}

There’s a bit going on here, so let’s break it down.

In OnInitializedAsync we check ApplicationState to see if we’ve already stored weather data previously.

During the first (pre)render this will return false (not found in state), so we go ahead and fetch the weather data.

On subsequent (interactive) renders, this will return true (state found), so we can use that persisted state (and save a new call to fetch the data).

var foundInState = ApplicationState.TryTakeFromJson<WeatherForecastModel>("weather", out var forecasts);

_model = foundInState
            ? forecasts // second or subsequent renders
            : await HttpClient.GetFromJsonAsync<WeatherForecastModel>("api/weather"); // first render

We also set up a subscription to ApplicationState.RegisterOnPersisting, which Blazor will invoke during rendering and register our Persist method as a handler for it.

In the Persist method, we take the weather data and persist it to ApplicationState.

private Task Persist()
{
   ApplicationState.PersistAsJson("weather", _model);
   return Task.CompletedTask;
}

This ensures our fetched weather data will be persisted by Blazor when the component is rendered.

Finally we need to implement IDisposable and dispose of the subscription (to avoid any potential memory leaks). With this, the data is fetched once, persisted to component state (on the server) then retrieved from component state (on the client, running in the browser).

Avoid the HTTP Call When Rendering on the Server

The second challenge is to skip the unnecessary HTTP call when rendering on the server, and go direct to the DB instead. This is a classic case of needing two implementations of the same requirement, which is most easily solved by using an interface.

We’re currently calling HttpClient direct, but if we put that behind an interface we can have two implementations of the code to fetch weather data—one which goes via HTTP and one which goes direct.

First we can define an interface for fetching the data:

BlazorDemoApp.Shared/Weather/IWeatherService.cs

public interface IWeatherService
{
    public Task<WeatherForecastModel?> FetchAsync();
}

Then refactor our existing code into an implementation for the client.

BlazorDemoApp.Client/Weather/ClientWeatherService

public class ClientWeatherService(HttpClient httpClient) : IWeatherService
{
    public async Task<WeatherForecastModel?> FetchAsync()
    {
        return await httpClient.GetFromJsonAsync<WeatherForecastModel>("api/weather");
    }
}

We need to register this implementation in the client project’s Program.cs.

var builder = WebAssemblyHostBuilder.CreateDefault(args);

builder.Services.AddScoped<IWeatherService, ClientWeatherService>();

...

Now we can turn our attention to the server.

BlazorDemoApp/Endpoints/ServerWeatherService.cs

public class ServerWeatherService : IWeatherService
{
    public async Task<WeatherForecastModel?> FetchAsync()
    {
        var startDate = DateOnly.FromDateTime(DateTime.Now);
        var summaries = new[]
            { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" };
        var forecasts = Enumerable.Range(1, 5).Select(index => new WeatherForecast
        {
            Date = startDate.AddDays(index),
            TemperatureC = Random.Shared.Next(-20, 55),
            Summary = summaries[Random.Shared.Next(summaries.Length)]
        }).ToList();

        return new WeatherForecastModel { Forecasts = forecasts };
    }
}

This time we can retrieve the data directly. In practice this would likely interact with a database or some other resource.

As we did with the client project, we need to register this implementation in the server project’s Program.cs.

builder.Services.AddScoped<IWeatherService, ServerWeatherService>();

At this point we can update the API endpoint to use this same implementation.

That way we have one version of the truth when it comes to fetching weather data, and both implementations will end up using the exact same code on the server.

public static class WeatherEndpoints
{
    public static void MapWeatherEndpoints(this WebApplication app)
    {
        app.MapGet("api/weather", Handler);
    }

    private static async Task<IResult> Handler(HttpContext context, IWeatherService weatherService)
    {
        return Results.Ok(await weatherService.FetchAsync());
    }
}

Finally, we need to modify the Weather component to use this new service to fetch the data:

@page "/ServerWeather"
@using BlazorDemoApp.Shared.Weather
@inject IWeatherService WeatherService

...
@code {

    private WeatherForecastModel? _model;
    private PersistingComponentStateSubscription _subscription;

    protected override async Task OnInitializedAsync()
    {
        _subscription = ApplicationState.RegisterOnPersisting(Persist);

        var foundInState = ApplicationState.TryTakeFromJson<WeatherForecastModel>("weather", out var forecasts);

        _model = foundInState
            ? forecasts
            : await WeatherService.FetchAsync(); // use the injected instance of `IWeatherService` to fetch the weather data
    }

    private Task Persist()
    {
       ApplicationState.PersistAsJson("weather", _model);
       return Task.CompletedTask;
    }

    public void Dispose()
    {
        _subscription.Dispose();
    }

}

A Simpler Option?

Now we’ve got to a point where everything works, but we’ve had to implement quite a lot of boilerplate to get this set up.

We’ve ended up interacting with ApplicationState to reduce the number of times we fetch data.

We’ve also ended up with multiple implementations of a service to fetch data, so we can avoid unnecessary network calls if we’re performing the fetch on the client.

With these changes we’ve covered all bases.

Whether we keep prerendering enabled or ultimately disable it for this page, the component will still be able to fetch data, regardless of whether it’s running via Blazor Server or Blazor WASM.

But, if we just look at the requirement from a slightly different angle we can see another way to make this work with .NET 8.

At the moment, our Weather component is fetching its own data. As a result, because it’s set to run via InteractiveAuto, it needs to be able to fetch data in all render modes.

But what if we were to fetch the data in a separate “host” component, which always runs on the server?

We could then have a second component, which accepts the weather data via a parameter. This new, second component could run using InteractiveAuto render mode.

Diagram showing an Index component running on a server, which fetches data from a database then passes it to another component called WeatherTable

Here’s an example to make this a little clearer!

BlazorDemoApp/…/Weather/Index.razor

@page "/ServerWeather"
@using BlazorDemoApp.Shared.Weather
@inject ServerWeatherService WeatherService

<WeatherTable Model="_model" @rendermode="InteractiveAuto"/>
@code {
    private WeatherForecastModel? _model;

    protected override async Task OnInitializedAsync()
    {
        _model = await WeatherService.FetchAsync();
    }
}

This component will render on the server (as it has no other render mode specified).

On the server, it will fetch the weather data (and can go direct to the DB as this is all running on the server).

Once it has that data, it can forward it on to a new component, called WeatherTable.

WeatherTable has one job: to take the data it’s given via its Model parameter and display it.

WeatherTable.razor

@using BlazorDemoApp.Shared.Weather
<h3>WeatherTable</h3>

@if (Model?.Forecasts == null)
{
    <p>
        <em>Loading...</em>
    </p>
}
else
{
    <table class="table">
        <thead>
        <tr>
            <th>Date</th>
            <th>Temp. (C)</th>
            <th>Temp. (F)</th>
            <th>Summary</th>
        </tr>
        </thead>
        <tbody>
        @foreach (var forecast in Model.Forecasts)
        {
            <tr>
                <td>@forecast.Date.ToShortDateString()</td>
                <td>@forecast.TemperatureC</td>
                <td>@forecast.TemperatureF</td>
                <td>@forecast.Summary</td>
            </tr>
        }
        </tbody>
    </table>
}
@code {

    [Parameter] public WeatherForecastModel? Model { get; set; }
    
}

As you can see, this component is much simpler. It doesn’t need to concern itself with injected services or persisting component state.

This WeatherTable component can run in any of the render modes, so long as it’s given data via its Model parameter.

How Is Data Shared Between Render Modes?

All this might have you wondering how Blazor actually handles this.

Given the host Weather/Index.razor component is running on the server, and WeatherTable is potentially running in the browser, via WASM, how does Blazor get the data from one to the other?

The answer can be found in the HTML that’s generated when Index,razor is rendered on the server.

Here’s a simplified example.

<article>
    <!--Blazor:{
"type":"auto",
"prerenderId":"72542b09f0a24154a85bdda7731dad18",
"key":{"locationHash":"<hash>",
"formattedComponentKey":""},
"sequence":0,
"descriptor":"<encodeddata>",
"assembly":"BlazorDemoApp.Client",
"typeName":"BlazorDemoApp.Client.Weather.WeatherTable",
"parameterDefinitions":"<encodeddata>"
}-->
    <h3>WeatherTable</h3>
    <table class="table">
        <thead>
            <tr>
                <th>Date</th>
                <th>Temp. (C)</th>
                <th>Temp. (F)</th>
                <th>Summary</th>
            </tr>
        </thead>
        <tbody>
			...
        </tbody>
    </table>
</article>

For the data that needs to “cross boundaries,” Blazor (on the server) encodes it and includes it in the rendered HTML.

Blazor on the client then decodes that data and uses it to populate the Model parameter on WeatherTable.

In practice, you shouldn’t need to dive into the details of how this works, but it’s useful to know what’s going on when you share data between components running with different render modes.

Finally, it’s worth noting this Weather table isn’t actually the best example of a component which you’d want to run using InteractiveAuto render mode.

As we’re simply rendering data this component, would probably work best as a server-rendered component. InteractiveAuto is useful if you have a component which users will interact with, clicking buttons, toggling sliders etc.

In that case it make sense to use one of Blazor’s interactive modes, and the techniques we’ve explored here for handling the retrieval and sharing of data.

In Summary

.NET 8 offers more tools and options for building apps using Razor Components and Blazor. Key among those is the ability to mix and match render modes.

Interactive Auto mode makes it possible to run your components using Blazor WASM, but fall back to Blazor Server if WASM is unavailable.

When you use this mode, it can add a degree of complication to your components, to ensure they work in all render modes.

There are two key ways to make your component work in InteractiveAuto mode.

The first involves making the component itself able to fetch data in all render modes. With this option, you can limit the number of times you fetch the same data by persisting component state between renders.

You can also avoid unnecessary HTTP calls by implementing two versions of your business logic/data fetching code: one for the client which goes via HTTP and one for the server which bypasses that HTTP call.

However, an arguably simpler approach is to use a “host” component that runs on the server and fetches data, then have a separate component which takes this data via a parameter.

This “second” component can then run using whichever render mode you prefer, and work with the data it’s been given, but without having to handle the complexity of fetching that data itself.


Use a component library in sync with Microsoft’s release cadence. Telerik UI for Blazor offers more than 110 truly native Blazor components to make it fast and easy to develop new Blazor apps or modernize legacy apps. Try it free for 30 days.

Jon Hilton
About the Author

Jon Hilton

Jon spends his days building applications using Microsoft technologies (plus, whisper it quietly, a little bit of JavaScript) and his spare time helping developers level up their skills and knowledge via his blog, courses and books. He's especially passionate about enabling developers to build better web applications by mastering the tools available to them. Follow him on Twitter here.

Related Posts

Comments

Comments are disabled in preview mode.