Building a Hybrid Application in .NET with MAUI, Blazor SSR, and Shared Services

Modern applications rarely live on a single platform. Users expect seamless experiences across mobile, desktop, and web, while developers want to avoid duplicating logic and maintaining multiple codebases.

The .NET ecosystem makes this possible through a powerful combination of .NET MAUI, Blazor Server-Side Rendering (SSR), and cleanly extracted services shared through interfaces.

This article walks through an architecture where:

  • Mobile and desktop apps are built using .NET MAUI
  • Web app is built using Blazor with SSR
  • APIs serve MAUI clients
  • Business logic is extracted into shared interfaces and implementations
  • Blazor SSR consumes services directly, not via HTTP

High-Level Architecture

At a high level, the solution looks like this:

┌────────────────┐
│  Blazor SSR    │
│ (Web App)      │
│ Uses services  │
│ directly       │
└───────▲────────┘
        │
┌───────┴────────┐
│ Application    │
│ Services       │
│ (Interfaces +  │
│ Implementations│
└───────▲────────┘
        │
┌───────┴────────┐
│ Web API        │
│ (Minimal API / │
│ Controllers)   │
└───────▲────────┘
        │
┌───────┴────────┐
│ .NET MAUI App  │
│ (Mobile +      │
│ Desktop)       │
└────────────────┘

Project Structure

A clean solution layout might look like this:

/src
 ├─ MyApp.Domain
 │   ├─ Interfaces
 │   └─ Models
 ├─ MyApp.Application
 │   ├─ Services
 │   └─ DTOs
 ├─ MyApp.Api
 ├─ MyApp.BlazorSSR
 └─ MyApp.Maui

Responsibility Breakdown

ProjectResponsibility
DomainInterfaces, core models, business contracts
ApplicationBusiness logic and service implementations
APIHTTP endpoints for MAUI clients
Blazor SSRWeb UI, uses services directly
MAUIMobile + desktop UI, consumes API

Step 1: Extract Business Logic into Interfaces

The foundation of this architecture is interface-based design.

Example Interface

public interface IProductService
{
    Task<List<Product>> GetProductsAsync();
    Task<Product?> GetByIdAsync(int id);
}

This interface lives in the Domain project and contains no platform-specific code.


Step 2: Implement Services Once

Service implementations go into the Application project.

public class ProductService : IProductService
{
    private readonly AppDbContext _db;

    public ProductService(AppDbContext db)
    {
        _db = db;
    }

    public async Task<List<Product>> GetProductsAsync()
    {
        return await _db.Products.ToListAsync();
    }

    public async Task<Product?> GetByIdAsync(int id)
    {
        return await _db.Products.FindAsync(id);
    }
}

These services:

  • Contain all business logic
  • Are testable
  • Are reused by both API and Blazor SSR

Step 3: Build APIs for MAUI

MAUI apps communicate over HTTP, so we expose APIs.

Minimal API Example

app.MapGet("/api/products", async (IProductService service) =>
{
    return Results.Ok(await service.GetProductsAsync());
});

The API project:

  • References Domain and Application
  • Registers services via dependency injection
  • Acts as a gateway for mobile and desktop MAUI clients

Step 4: Consume API from MAUI

In the MAUI app, use HttpClient to consume the API.

public class ProductApiClient
{
    private readonly HttpClient _http;

    public ProductApiClient(HttpClient http)
    {
        _http = http;
    }

    public async Task<List<Product>> GetProductsAsync()
    {
        return await _http.GetFromJsonAsync<List<Product>>("/api/products");
    }
}

This keeps MAUI:

  • Lightweight
  • Decoupled from backend logic
  • Easily replaceable or scalable

Step 5: Build the Web App with Blazor SSR

Why Blazor Server-Side Rendering?

Blazor SSR is ideal for this architecture because:

  • No API calls needed for internal data
  • Faster initial page load
  • SEO-friendly
  • Shared validation and logic
  • Lower network overhead

Instead of calling HTTP endpoints, Blazor SSR injects services directly.

@inject IProductService ProductService

<ul>
@foreach (var product in products)
{
    <li>@product.Name</li>
}
</ul>

@code {
    private List<Product> products = new();

    protected override async Task OnInitializedAsync()
    {
        products = await ProductService.GetProductsAsync();
    }
}

Why Blazor SSR Should Not Use the API

A common mistake is having Blazor SSR call the same HTTP APIs used by MAUI. This adds unnecessary complexity.

Direct Service Usage Benefits

Direct ServicesHTTP API
FasterSlower
No serializationJSON overhead
Shared validationDuplicated logic
Easier debuggingNetwork failures

Blazor SSR runs on the server — it is already inside the backend boundary.


Dependency Injection Setup

Both API and Blazor SSR register the same services:

builder.Services.AddScoped<IProductService, ProductService>();

This is what enables true code reuse.


Benefits of This Hybrid Architecture

1. Single Source of Truth

All business rules live in one place.

2. Platform Independence

UI changes do not affect core logic.

3. Scalability

You can:

  • Add more MAUI apps
  • Replace Blazor SSR with WebAssembly
  • Introduce new APIs

4. Maintainability

Bug fixes and enhancements are applied once.


When to Choose This Approach

This architecture is ideal if:

  • You need mobile + desktop + web
  • You want maximum code reuse
  • You care about performance and SEO
  • You want clean separation of concerns

Conclusion

By combining .NET MAUI, Blazor SSR, and interface-based services, you can build a truly hybrid application with:

  • Shared logic
  • Platform-specific UIs
  • High performance
  • Clean architecture

This approach leverages the best of the .NET ecosystem while avoiding common pitfalls like duplicated logic and unnecessary API calls.


Author

Salman

Salman

Software Engineer