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.
Overview
Section titled “Overview”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.
Embedded (Local) APIs
Section titled “Embedded (Local) APIs”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.
Defining the Abstraction
Section titled “Defining the Abstraction”Use an interface to abstract between server and client implementations:
public interface IDataAccessor{ Task<Data[]> GetData();}
public record Data(string Value);Server Implementation
Section titled “Server Implementation”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:
builder.Services.AddSingleton<IDataAccessor, ServerDataAccessor>();
// ...
app.MapGet("/some_data", async (IDataAccessor dataAccessor) => await dataAccessor.GetData()) .RequireAuthorization() .AsBffApiEndpoint();Client (WASM) Implementation
Section titled “Client (WASM) Implementation”On the client, use an HttpClient that routes through the BFF host:
builder.Services.AddBffBlazorClient() .AddLocalApiHttpClient<HttpClientDataAccessor>();
// Register the concrete implementation with the abstractionbuilder.Services.AddSingleton<IDataAccessor>(sp => sp.GetRequiredService<HttpClientDataAccessor>());internal class HttpClientDataAccessor(HttpClient client) : IDataAccessor{ public async Task<Data[]> GetData() => await client.GetFromJsonAsync<Data[]>("/some_data") ?? throw new JsonException("Failed to deserialize");}Secured Remote APIs
Section titled “Secured Remote APIs”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-side Proxy Setup
Section titled “Server-side Proxy Setup”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-Side Access
Section titled “Client-Side Access”builder.Services.AddBffBlazorClient();builder.Services.AddRemoteApiHttpClient("backend");builder.Services.AddTransient(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient("backend"));The diagram below shows the full flow:
Auto-Rendering Mode
Section titled “Auto-Rendering Mode”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(); }}