Skip to content

Blazor Data Access Patterns

Depending on your Blazor rendering mode, you need different strategies for accessing data from components. The BFF security framework provides a consistent model: tokens never leave the server, and browser components access data through BFF-hosted endpoints secured with the authentication cookie.

For server-side rendering, components access data directly (database, services). For WASM rendering, components make HTTP calls to BFF-hosted endpoints which handle token attachment.

An Embedded API is hosted within the BFF itself. It lives within the server’s security boundary, so no token needs to be passed to the browser.

Use an interface to abstract between server and client implementations:

Shared/IDataAccessor.cs
public interface IDataAccessor
{
Task<Data[]> GetData();
}
public record Data(string Value);
Server/ServerDataAccessor.cs
internal class ServerDataAccessor : IDataAccessor
{
public Task<Data[]> GetData()
{
// Access data directly (database, cache, etc.)
return Task.FromResult(new[] { new Data("example") });
}
}

Register the server implementation and expose it as a BFF endpoint:

Server/Program.cs
builder.Services.AddSingleton<IDataAccessor, ServerDataAccessor>();
// ...
app.MapGet("/some_data", async (IDataAccessor dataAccessor) => await dataAccessor.GetData())
.RequireAuthorization()
.AsBffApiEndpoint();

On the client, use an HttpClient that routes through the BFF host:

Client/Program.cs
builder.Services.AddBffBlazorClient()
.AddLocalApiHttpClient<HttpClientDataAccessor>();
// Register the concrete implementation with the abstraction
builder.Services.AddSingleton<IDataAccessor>(sp =>
sp.GetRequiredService<HttpClientDataAccessor>());
Client/HttpClientDataAccessor.cs
internal class HttpClientDataAccessor(HttpClient client) : IDataAccessor
{
public async Task<Data[]> GetData() =>
await client.GetFromJsonAsync<Data[]>("/some_data")
?? throw new JsonException("Failed to deserialize");
}

If your BFF needs to proxy requests to a remote API (one that requires a bearer token), configure a remote endpoint on the server and access it from the client via the BFF proxy.

Server/Program.cs
app.MapRemoteBffApiEndpoint("/remote-apis/data", new Uri("https://api.example.com/data"))
.WithAccessToken(RequiredTokenType.User);

Also register an HttpClient that attaches the user access token for use in Embedded API endpoints:

builder.Services.AddUserAccessTokenHttpClient("backend",
configureClient: client => client.BaseAddress = new Uri("https://api.example.com/"));
Client/Program.cs
builder.Services.AddBffBlazorClient();
builder.Services.AddRemoteApiHttpClient("backend");
builder.Services.AddTransient(sp =>
sp.GetRequiredService<IHttpClientFactory>().CreateClient("backend"));

The diagram below shows the full flow:

In Interactive Auto mode, a component may render on the server first, then transition to WASM. Use the interface-based abstraction pattern from above: inject IDataAccessor in your component, and register both ServerDataAccessor (for server rendering) and HttpClientDataAccessor (for WASM rendering).

@* Component works identically in server and WASM rendering modes *@
@inject IDataAccessor DataAccessor
@if (items == null)
{
<p>Loading...</p>
}
else
{
@foreach (var item in items)
{
<p>@item.Value</p>
}
}
@code {
private Data[]? items;
protected override async Task OnInitializedAsync()
{
items = await DataAccessor.GetData();
}
}