.NET MAUI (Blazor Hybrid)
Host MentorAgent in a .NET MAUI Blazor Hybrid app, pass the signed-in user for role checks, and keep the model key on a server.
On this page
This page is for a .NET MAUI Blazor Hybrid app. In such an app all of MentorAgent runs inside the app, as it does on Blazor Server. When the model key must not travel with the app, the app becomes a client of a MentorAgent server instead.
When to choose it
Section titled “When to choose it”Inside the app
Section titled “Inside the app”- Android and Windows.
Novior.MentorAgentno longer needs the ASP.NET Core shared framework (Microsoft.AspNetCore.App), so the MAUI Blazor Hybrid template builds for Android as it is, and for Windows. The documentation has not checked iOS and Mac Catalyst, which build only on a Mac. - A
BlazorWebViewis interactive by construction: the widget needs no render mode. - There is no circuit, no HTTP request and no ASP.NET Core user. Scoped services live as long as the app: one coordinator and one conversation until the app restarts.
- The model key, or the address of a local model, is part of the app's configuration. That suits an internal tool, a model running on the same machine, or development.
- The dashboard endpoint and the MCP and A2A servers need a web host, so they are not available: they are in
Novior.MentorAgent.Server, which a MAUI app does not reference. MCP and A2A clients work normally.
As a client of a server
Section titled “As a client of a server”For a phone or tablet, and whenever you distribute the app, keep the agent and the key on a server. Run MentorAgent on an ASP.NET Core host with Novior.MentorAgent.Server, configured as in Any front end, and connect the app to its SignalR hub with Microsoft.AspNetCore.SignalR.Client. The protocol is the same for every client.
The rest of this page configures MentorAgent inside the app.
Install the packages
Section titled “Install the packages”.NET CLI
dotnet add package Novior.MentorAgentPackageReference
<ItemGroup>
<PackageReference Include="Novior.MentorAgent" Version="1.0.0" />
</ItemGroup>Central Package Management
<ItemGroup>
<PackageVersion Include="Novior.MentorAgent" Version="1.0.0" />
</ItemGroup><ItemGroup>
<PackageReference Include="Novior.MentorAgent" />
</ItemGroup>File-based app
#:package Novior.MentorAgent@1.0.0Novior.MentorAgent includes no model provider package, and none of the packages that read the configuration. The code on this page uses OpenAI, and step 3 calls AddJsonStream and AddUserSecrets. So add these packages to the MAUI project, the last two with the same major version as your .NET:
dotnet add package Microsoft.Extensions.AI.OpenAI --version 10.6.0
dotnet add package Microsoft.Extensions.Configuration.Json
dotnet add package Microsoft.Extensions.Configuration.UserSecretsFor another provider, see Model providers.
Minimal configuration
Section titled “Minimal configuration”Reference the widget's stylesheet and script in
wwwroot/index.html. The app serves that page itself, and the template does not know the package's assets:<link rel="stylesheet" href="_content/Novior.MentorAgent.Abstractions/css/MentorAgent.css" /> <script src="_content/Novior.MentorAgent.Abstractions/js/MentorAgent.js"></script>Embed
appsettings.jsonin the app: at run time there is no content root to read it from. In the MAUI project file, give it the logical name thatMauiProgram.csreads in step 3, hereCases.appsettings.json:<ItemGroup> <EmbeddedResource Include="appsettings.json" LogicalName="Cases.appsettings.json" /> </ItemGroup>While you develop, keep the key in user secrets:
dotnet user-secrets init dotnet user-secrets set OpenAI:Key <your-key>In
MauiProgram.cs, afterbuilder.Services.AddMauiBlazorWebView();, read the configuration and register MentorAgent:using System.Reflection; // Assembly using Novior.MentorAgent.Extensions; // AddMentorAgent using Novior.MentorAgent.Options; // MentorAnonymousIdentity using Microsoft.AspNetCore.Components.Authorization; // AuthenticationStateProvider using Microsoft.Extensions.Configuration; // AddJsonStream, AddUserSecrets using Microsoft.Extensions.AI; // AsIChatClient using OpenAI; // OpenAIClient// appsettings.json is embedded in the app: there is no content root to read it from using var stream = Assembly.GetExecutingAssembly() .GetManifestResourceStream("Cases.appsettings.json"); if (stream is not null) builder.Configuration.AddJsonStream(stream); #if DEBUG builder.Configuration.AddUserSecrets<App>(); // the key stays out of the app package #endifbuilder.Services.AddMentorAgent(options => { options.AppName = "Case management"; options.ChatClient = new OpenAIClient(builder.Configuration["OpenAI:Key"] ?? throw new InvalidOperationException("OpenAI:Key is missing from the configuration.")) .GetChatClient("gpt-4o-mini") .AsIChatClient(); options.ScanAssemblies = [typeof(MauiProgram).Assembly]; // One installation, one user: without this every app run is a new anonymous user options.AnonymousIdentity = MentorAnonymousIdentity.Shared; });AddUserSecrets<App>()uses the app's ownAppclass to find the project's user secrets. Any class of the app project works.Add the widget to the layout, with no render mode:
@using Novior.MentorAgent.Abstractions.Components <ChatWidget />
Complete configuration
Section titled “Complete configuration”The signed-in user. Without an ASP.NET Core user, MentorAgent asks AuthenticationStateProvider who is using the app. Register your own provider, and call its SignIn method from the Razor component where the user signs in. MentorAgent then reads the same instance, because scoped services live as long as the app:
public sealed class AppAuthenticationStateProvider : AuthenticationStateProvider
{
private ClaimsPrincipal _user = new(new ClaimsIdentity()); // signed out
public override Task<AuthenticationState> GetAuthenticationStateAsync() =>
Task.FromResult(new AuthenticationState(_user));
// Call it from your own sign-in code, with the user's id and roles
public void SignIn(string userId, string name, IEnumerable<string> roles)
{
List<Claim> claims = [new(ClaimTypes.NameIdentifier, userId), new(ClaimTypes.Name, name)];
claims.AddRange(roles.Select(role => new Claim(ClaimTypes.Role, role)));
_user = new ClaimsPrincipal(new ClaimsIdentity(claims, authenticationType: "app"));
NotifyAuthenticationStateChanged(GetAuthenticationStateAsync());
}
public void SignOut()
{
_user = new ClaimsPrincipal(new ClaimsIdentity());
NotifyAuthenticationStateChanged(GetAuthenticationStateAsync());
}
}Register it in MauiProgram.cs, next to AddMentorAgent:
builder.Services.AddAuthorizationCore();
builder.Services.AddScoped<AppAuthenticationStateProvider>();
builder.Services.AddScoped<AuthenticationStateProvider>(
sp => sp.GetRequiredService<AppAuthenticationStateProvider>());MentorAgent checks the roles with ClaimsPrincipal.IsInRole on every call to an action with RequiredRoles. With the provider above, the roles are ClaimTypes.Role claims. It identifies the user for memory and the rate limit by the ClaimTypes.NameIdentifier claim, else by sub, oid or the name.
Memory that survives a restart. AnonymousIdentity = Shared keeps the same memory key across runs. The built-in memory store keeps everything in RAM, though: register your own IMentorMemoryStore before AddMentorAgent to keep what the agent remembers.
Options that matter here
Section titled “Options that matter here”| Option | Why it matters on .NET MAUI |
|---|---|
AnonymousIdentity | The default, PerSession, gives every run of the app a new key, so nothing remembered is recalled after a restart. Shared means one installation, one user |
UseMemoryContext | Turns memory on. Pair it with a persistent IMentorMemoryStore |
ChatClient | Any provider, including a model on the same machine: see Model providers |
SafetyCheckTimeout, ClassifierChatClient | With a slow local model, give the safety checks more time or a faster model |
BotName, WelcomeMessage, Theme, PrimaryColor, Position | The widget reads them from MentorOptions, as on Blazor Server |
RequireConfirmation | Keep it true: the widget then shows the confirmation box, as on the other hosts |
WarmUpAtStartup | No effect: a MAUI app runs no hosted service |
Every option is in Configuration options.
Security and roles
Section titled “Security and roles”- Without a signed-in user, role-gated actions are refused. Until you register an
AuthenticationStateProvider, every action withRequiredRolesis refused for everybody, rather than run unchecked. With the provider above, the checks work as on Blazor Server: see Two checks, in this order. - Confirmation. The confirmation box appears in the widget, as on Blazor Server.
- The key is on the device. Whatever the app reads at run time, a determined user can read too. For an app you distribute, move MentorAgent to a server and connect the app as a client.
- User secrets are for development. They live in your user profile, and the
#if DEBUGline reads them only in Debug builds: a Release build must get the key some other way.
Common errors
Section titled “Common errors”- The build does not find
AddJsonStreamorAddUserSecrets. The two configuration packages are not in the MAUI project, becauseNovior.MentorAgentdoes not bring them: add them, as in Install the packages. - The widget has no styles, or voice and the tour do not work. The stylesheet and script are missing from
wwwroot/index.html. - The app stops at the first page with "OpenAI:Key is missing from the configuration". The embedded
appsettings.jsonhas a different logical name, or a Release build does not read user secrets. Put the value where the app can read it at run time. A MAUI app has no host start, so MentorAgent reports an invalid configuration the first time it uses it. - Every action with
RequiredRolesis refused. NoAuthenticationStateProvideris registered: the log says so. Register the provider and callSignIn. - Nothing is remembered after a restart. Set
AnonymousIdentity = Sharedand register a persistentIMentorMemoryStore. MapMentorAgentMcp,MapMentorAgentA2Aor the dashboard endpoint are not available. They are inNovior.MentorAgent.Serverand need a web host: run them on an ASP.NET Core server.
See also
Section titled “See also”- Channels: how clients reach the agent — Which channel fits your client: the Blazor or MAUI widget, SignalR or SSE for JavaScript and mobile apps, and MCP or A2A for other agents and AI tools.
- Blazor Server app — Configure MentorAgent in a Blazor Web App with Server interactivity: services, the options that matter, the widget and authentication.
- Any front end: React, Angular, Vue — Use MentorAgent from React, Angular, Vue, a mobile app or any HTTP client: SignalR or Server-Sent Events, confirmation over HTTP, and how to stop an answer.
- Configuration options — Every MentorOptions property with its type, default and effect, grouped by area, plus the options of the WebAssembly client and the YAML loader.
