Questa pagina non è ancora tradotta in italiano: qui la trovi in inglese.
.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
Use this page if you have 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.MentorAgentdoes not need the ASP.NET Core shared framework (Microsoft.AspNetCore.App), so the MAUI Blazor Hybrid template builds for Android as it is, and for Windows. iOS and Mac Catalyst have not been checked, because they build only on a Mac. - No render mode. A
BlazorWebViewis always interactive, so the widget needs none. - One user, one conversation. The app has no circuit, no HTTP request and no ASP.NET Core user. Scoped services live as long as the app, so there is one coordinator and one conversation until the app restarts.
- The key is in the app. 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.
- No web endpoints. The dashboard endpoint and the Model Context Protocol (MCP) and Agent-to-Agent (A2A) servers need a web host, so they are not available. They are in
Novior.MentorAgent.Server, which a MAUI app does not reference. The 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.2.0" />
</ItemGroup>Central Package Management
<ItemGroup>
<PackageVersion Include="Novior.MentorAgent" Version="1.2.0" />
</ItemGroup><ItemGroup>
<PackageReference Include="Novior.MentorAgent" />
</ItemGroup>File-based app
#:package Novior.MentorAgent@1.2.0Novior.MentorAgent includes no model provider package and no package that reads configuration files. The code on this page uses OpenAI, and step 4 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?v=1.2.0" />
<script
src="_content/Novior.MentorAgent.Abstractions/js/MentorAgent.js?v=1.2.0">
</script>?v= makes the browser load the files of the new version after an upgrade. Keep it equal to the package version, and change it on every upgrade.
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 4, 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 // AuthenticationStateProvider using Microsoft.AspNetCore.Components.Authorization; 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 // The key stays out of the app package builder.Configuration.AddUserSecrets<App>(); #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 run of the app 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 AuthenticationStateProvider, 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)));
var identity = new ClaimsIdentity(claims, authenticationType: "app");
_user = new ClaimsPrincipal(identity);
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. MentorAgent identifies the user for memory and the rate limit by the ClaimTypes.NameIdentifier claim or, if that claim is missing, by sub, oid or the name.
Memory that survives a restart. You need two things:
- Set
AnonymousIdentity = Shared, so the memory key stays the same across runs. - Register your own
IMentorMemoryStorebeforeAddMentorAgent, because the built-in store keeps everything in RAM.
Options for .NET MAUI
Section titled “Options for .NET MAUI”| 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 include 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, or their?v=still names an older version after an upgrade. - 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.
Vedi anche
Sezione “Vedi anche”- Canali: come i client arrivano all'agente — Scegli come i client raggiungono il tuo agente: il widget della chat, SignalR o HTTP dal tuo front end, o un protocollo per altri agenti e strumenti AI.
- 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 — Build the chat in React, Angular, Vue or a mobile app with a .NET AI agent behind it: SignalR or Server-Sent Events, confirmation over HTTP, stopping 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.
