Blazor Microservice Communication Patterns
When building modern web applications with Blazor, efficient communication between client and server components is essential. This guide explores how to implement robust microservice communication patterns using HttpClient and related extensions.
Setting Up the Project Structure
Begin by creating a new Blazor WebAssembly project with hosting enabled. This will establish the foundation for our communication demonstration. The project will consist of three main components: a server project, a shared library, and a client application.
Server-Side Implementation
Our server project exposes a REST API through a WeatherForecastController. This controller provides weather data that our client will consume.
using WeatherApp.Shared;
using Microsoft.AspNetCore.Mvc;
namespace WeatherApp.Server.Controllers;
[ApiController]
[Route("api/[controller]")]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] WeatherSummaries = new[]
{
"Clear", "Cloudy", "Rainy", "Snowy", "Stormy", "Windy"
};
private readonly ILogger<weatherforecastcontroller> _logger;
public WeatherForecastController(ILogger<weatherforecastcontroller> logger)
{
_logger = logger;
}
[HttpGet]
public IEnumerable<weatherdata> GetWeatherData()
{
return Enumerable.Range(1, 7)
.Select(index => new WeatherData
{
Date = DateTime.Now.AddDays(index),
TemperatureC = Random.Shared.Next(-10, 35),
Summary = WeatherSummaries[Random.Shared.Next(WeatherSummaries.Length)]
})
.ToArray();
}
}</weatherdata></weatherforecastcontroller></weatherforecastcontroller>
This controller exposes a GET endpoint at /api/WeatherForecast that returns an array of weather data objects. The [Route("api/[controller]")] attribute automatically maps the controller name to the route, and the [HttpGet] attribute specifies the HTTP method.
Shared Model Layer
Creating shared models between client and server eliminates the need for duplicate type definitions and ensures consistent data structures.
namespace WeatherApp.Shared;
public class WeatherData
{
public DateTime Date { get; set; }
public int TemperatureC { get; set; }
public string? Summary { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}
By placing this model in a shared project, both client and server can reference the same type, preventing serialization mismatches and reducing maintenance overhead.
Cliant-Side Implementation
The client component fetches and displays weather data using HttpClient.
@page "/weather"
@using WeatherApp.Shared
@inject HttpClient Http
<h3>Weekly Weather Forecast</h3>
@if (weatherData == null)
{
<p>Loading weather information...</p>
}
else
{
<table class="table">
<thead>
<tr>
<th>Date</th>
<th>Temperature (C)</th>
<th>Temperature (F)</th>
<th>Condition</th>
</tr>
</thead>
<tbody>
@foreach (var day in weatherData)
{
<tr>
<td>@day.Date.ToShortDateString()</td>
<td>@day.TemperatureC</td>
<td>@day.TemperatureF</td>
<td>@day.Summary</td>
</tr>
}
</tbody>
</table>
}
@code {
private WeatherData[]? weatherData;
protected override async Task OnInitializedAsync()
{
weatherData = await Http.GetFromJsonAsync<weatherdata>("api/WeatherForecast");
}
}</weatherdata>
The component injects HttpClient and uses the GetFromJsonAsync extension method to fetch data. The @if block handles the loading state before data is available.
HttpClient Best Practices
HttpClient should never be instantiated directly in Blazor components. Instead, register it as a scoped service in Program.cs:
builder.Services.AddScoped(sp => new HttpClient
{
BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)
});
Advanced HTTP Operations
Beyond GET requests, HttpClient provides methods for POST, PUT, and DELETE operations. The HttpClientJsonExtensions class offers convenient methods for JSON serialization.
// POST request with JSON payload
public async Task SubmitOrder(OrderRequest order)
{
await Http.PostAsJsonAsync("api/orders", order);
}
// PUT request with JSON payload
public async Task UpdateProduct(Product product)
{
await Http.PutAsJsonAsync($"api/products/{product.Id}", product);
}
Custom Serialization Configuration
Control JSON serialization behavior using JsonSerializerOptions:
private readonly JsonSerializerOptions _jsonOptions = new()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
WriteIndented = true,
Converters = { new JsonStringEnumConverter() }
};
// Usage with HttpClient
var result = await Http.GetFromJsonAsync<weatherdata>(
"api/WeatherForecast", _jsonOptions);</weatherdata>
Real-World Application: Pizza Ordering System
Let's implement a complete ordering system with menu and order services.
Menu Service Implementation
using PizzaApp.Shared;
using System.Net.Http.Json;
using System.Threading.Tasks;
namespace PizzaApp.Client.Services
{
public class MenuService : IMenuService
{
private readonly HttpClient _httpClient;
public MenuService(HttpClient httpClient)
{
_httpClient = httpClient;
}
public async Task<menu> GetAvailableMenu()
{
var pizzas = await _httpClient.GetFromJsonAsync<pizza>("api/menu");
return new Menu { Items = pizzas!.ToList() };
}
}
}</pizza></menu>
Order Service Implementation
using PizzaApp.Shared;
using System.Net.Http.Json;
using System.Threading.Tasks;
namespace PizzaApp.Client.Services
{
public class OrderService : IOrderService
{
private readonly HttpClient _httpClient;
public OrderService(HttpClient httpClient)
{
_httpClient = httpClient;
}
public async Task SubmitOrder(OrderRequest order)
{
await _httpClient.PostAsJsonAsync("api/orders", order);
}
}
}
Database Integration
For data persistence, we'll use Entity Framework Core to manage our data models:
public class PizzaDbContext : DbContext
{
public PizzaDbContext(DbContextOptions<pizzadbcontext> options)
: base(options) { }
public DbSet<pizza> Pizzas { get; set; } = default!;
public DbSet<order> Orders { get; set; } = default!;
public DbSet<customer> Customers { get; set; } = default!;
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<order>()
.HasMany(o => o.Pizzas)
.WithMany(p => p.Orders)
.UsingEntity(j => j.ToTable("OrderPizzas"));
}
}</order></customer></order></pizza></pizzadbcontext>
Dependency Injection Configuration
Register services in Program.cs for dependency injection:
builder.Services.AddScoped(sp => new HttpClient
{
BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)
});
builder.Services.AddTransient<imenuservice menuservice="">();
builder.Services.AddTransient<iorderservice orderservice="">();</iorderservice></imenuservice>
Handling Loading States
Implement loading indicators for better user experience:
@if (menu == null)
{
<div class="text-center">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Loading menu...</span>
</div>
</div>
}
else
{
<pizzalist items="@menu.Items"></pizzalist>
}
Conclusion
Blazor's HttpClient integration provides a robust mechanism for microservice communication. By leveraging shared models, proper dependency injection, and JSON serialization extensions, you can build scalable and maintainable applications with seamless client-server interaction.