aspnetcore/blazor/hybrid/class-libraries-best-practices.md
This article explains the recommended pattern for creating Razor class libraries (RCLs) that combine .NET MAUI and Razor functionality. The architecture explained in this article is adopted by the .NET MAUI Blazor Hybrid and Web solution template.
RCLs should adopt host-agnostic design, where libraries are reusable UI component packages that work across different platforms, app types, Blazor hosting models (Blazor Server, Blazor WebAssembly, Blazor Hybrid/MAUI), and static server-side rendering. To maintain this flexibility, RCLs shouldn't depend on specific hosting infrastructure or platform APIs:
These goals are accomplished by:
Host-agnostic design has the following advantages:
Host independence
Clean dependency flow
Testability
IDeviceInfoService are mocked for unit testing.Maximum reusability
Lib.Maui provides native device APIs).IDeviceInfoService uses browser APIs).Keep the RCL platform-agnostic.
The MAUI library references RCL. The RCL doesn't reference the MAUI library:
Use interfaces for abstractions.
Register services appropriately.
Test independently
The following sections demonstrate how to adopt the following example app architecture:
Lib (RCL)
Lib.Maui (MAUI Class Library)
LibDeviceInfo, Permissions)MauiApp (MAUI Blazor Application)
Lib and Lib.MauiProject structure summary:
MauiClassLibrarySample.sln
├── Lib/ # RCL
│ ├── Lib.csproj # Microsoft.NET.Sdk.Razor, net10.0
│ ├── IDeviceInfoService.cs # Platform abstraction
│ ├── Component1.razor # Razor component using abstraction
│ └── wwwroot/
│ └── lib.js # JS initializer
│
├── Lib.Maui/ # MAUI Class Library
│ ├── Lib.Maui.csproj # Microsoft.NET.Sdk, multi-targeted, UseMaui=true
│ └── MauiDeviceInfoService.cs # MAUI implementation of IDeviceInfoService
│
└── MauiApp/ # MAUI Blazor Application
├── MauiApp.csproj # References both Lib and Lib.Maui
├── MauiProgram.cs # Registers services
└── Components/Pages/
└── Home.razor # Uses Component1 from RCL
Create a RCL without <UseMaui>:
dotnet new razorclasslib -o Lib
Key characteristics:
Microsoft.NET.Sdk.Razor SDK.net10.0 (or your target framework).In Lib/IDeviceInfoService.cs:
namespace Lib;
/// <summary>
/// Abstraction for retrieving device-specific information.
/// This interface should be implemented by platform-specific libraries (e.g., MAUI).
/// </summary>
public interface IDeviceInfoService
{
/// <summary>
/// Gets the name of the platform (e.g., "Android", "iOS", "Windows").
/// </summary>
string Platform { get; }
/// <summary>
/// Gets the device model (e.g., "iPhone 14 Pro", "Samsung Galaxy S23").
/// </summary>
string DeviceModel { get; }
/// <summary>
/// Gets the operating system version.
/// </summary>
string OSVersion { get; }
}
In Lib/Component1.razor:
@inject IDeviceInfoService DeviceInfo
<div class="component">
<h3>Device Information</h3>
<p><strong>Platform:</strong> @DeviceInfo.Platform</p>
<p><strong>Device Model:</strong> @DeviceInfo.DeviceModel</p>
<p><strong>OS Version:</strong> @DeviceInfo.OSVersion</p>
</div>
Create a MAUI class library with <UseMaui>true</UseMaui>:
dotnet new classlib -o Lib.Maui
Edit Lib.Maui/Lib.Maui.csproj:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net10.0-android;net10.0-ios;net10.0-maccatalyst</TargetFrameworks>
<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows'))">$(TargetFrameworks);net10.0-windows10.0.19041.0</TargetFrameworks>
<UseMaui>true</UseMaui>
<SingleProject>true</SingleProject>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">15.0</SupportedOSPlatformVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'maccatalyst'">15.0</SupportedOSPlatformVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">21.0</SupportedOSPlatformVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</SupportedOSPlatformVersion>
<TargetPlatformMinVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</TargetPlatformMinVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Maui.Controls" Version="$(MauiVersion)" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Lib\Lib.csproj" />
</ItemGroup>
</Project>
Key properties:
<UseMaui>true</UseMaui> enables MAUI functionality.<SingleProject>true</SingleProject> uses single-project MAUI structure.Lib RCL.In Lib.Maui/MauiDeviceInfoService.cs:
using Lib;
namespace Lib.Maui;
/// <summary>
/// MAUI-specific implementation of IDeviceInfoService.
/// Uses Microsoft.Maui.Devices.DeviceInfo to retrieve platform information.
/// </summary>
public class MauiDeviceInfoService : IDeviceInfoService
{
public string Platform => Microsoft.Maui.Devices.DeviceInfo.Platform.ToString();
public string DeviceModel => Microsoft.Maui.Devices.DeviceInfo.Model;
public string OSVersion => Microsoft.Maui.Devices.DeviceInfo.VersionString;
}
Create a MAUI Blazor app to consume both libraries:
dotnet new maui-blazor -o MauiApp
Add Lib and Lib.Maui project references to the MauiApp project:
<ItemGroup>
<ProjectReference Include="..\Lib\Lib.csproj" />
<ProjectReference Include="..\Lib.Maui\Lib.Maui.csproj" />
</ItemGroup>
In MauiApp/MauiProgram.cs, register the MAUI implementation:
using Microsoft.Extensions.Logging;
using Lib;
using Lib.Maui;
namespace MauiApp;
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
});
builder.Services.AddMauiBlazorWebView();
// Register the MAUI-specific implementation of IDeviceInfoService
builder.Services.AddSingleton<IDeviceInfoService, MauiDeviceInfoService>();
#if DEBUG
builder.Services.AddBlazorWebViewDeveloperTools();
builder.Logging.AddDebug();
#endif
return builder.Build();
}
}
In MauiApp/Components/Pages/Home.razor:
@page "/"
@using Lib
<h1>Hello, world!</h1>
<p>Welcome to your new app demonstrating the RCL + MAUI library pattern.</p>
<h2>Device Information Component</h2>
<p>
This component is defined in the Lib RCL and uses the IDeviceInfoService
abstraction, which is implemented by Lib.Maui.
</p>
<Component1 />
Create a solution file and build:
dotnet new sln -n MauiClassLibrarySample
dotnet sln add Lib\Lib.csproj Lib.Maui\Lib.Maui.csproj MauiApp\MauiApp.csproj
dotnet build
Use abstractions in the RCL for any functionality that requires MAUI APIs.
| Functionality | Pattern |
|---|---|
| Device information | Define IDeviceInfoService in the RCL, implement in MAUI library. |
| Permissions | Define IPermissionsService in the RCL, use Permissions API in MAUI library. |
| File system access | Define IFileService in the RCL, use FileSystem API in MAUI library. |
| Connectivity | Define IConnectivityService in the RCL, use Connectivity API in MAUI library. |
| Geolocation | Define ILocationService in the RCL, use Geolocation API in MAUI library. |
In the RCL (Lib):
public interface IPermissionsService
{
Task<bool> CheckCameraPermissionAsync();
Task<bool> RequestCameraPermissionAsync();
}
In the MAUI Library (Lib.Maui):
public class MauiPermissionsService : IPermissionsService
{
public async Task<bool> CheckCameraPermissionAsync()
{
var status = await Permissions.CheckStatusAsync<Permissions.Camera>();
return status == PermissionStatus.Granted;
}
public async Task<bool> RequestCameraPermissionAsync()
{
var status = await Permissions.RequestAsync<Permissions.Camera>();
return status == PermissionStatus.Granted;
}
}
In the app (MauiApp):
builder.Services.AddSingleton<IPermissionsService, MauiPermissionsService>();
Symptom: Build warnings suggesting to add explicit <PackageReference> for MAUI packages.
Solution: These warnings are informational. The packages are added implicitly when <UseMaui>true</UseMaui> is set.
Symptom: InvalidOperationException: Unable to resolve service for type 'Lib.IDeviceInfoService'
Solution: Register the service in MauiProgram.cs:
builder.Services.AddSingleton<IDeviceInfoService, MauiDeviceInfoService>();