MentorAgent Docs
Menu

How-to guidePackage: Novior.MentorAgent

.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.

View as Markdown
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.

  • Android and Windows. Novior.MentorAgent no 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 BlazorWebView is 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.

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.

.NET CLI

Terminal
dotnet add package Novior.MentorAgent

PackageReference

YourApp.csproj
<ItemGroup>
  <PackageReference Include="Novior.MentorAgent" Version="1.0.0" />
</ItemGroup>

Central Package Management

Directory.Packages.props
<ItemGroup>
  <PackageVersion Include="Novior.MentorAgent" Version="1.0.0" />
</ItemGroup>
YourApp.csproj
<ItemGroup>
  <PackageReference Include="Novior.MentorAgent" />
</ItemGroup>

File-based app

app.cs
#:package Novior.MentorAgent@1.0.0

Novior.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:

Terminal
dotnet add package Microsoft.Extensions.AI.OpenAI --version 10.6.0
dotnet add package Microsoft.Extensions.Configuration.Json
dotnet add package Microsoft.Extensions.Configuration.UserSecrets

For another provider, see Model providers.

  1. 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:

    HTML
    <link rel="stylesheet" href="_content/Novior.MentorAgent.Abstractions/css/MentorAgent.css" />
    <script src="_content/Novior.MentorAgent.Abstractions/js/MentorAgent.js"></script>
  2. Embed appsettings.json in the app: at run time there is no content root to read it from. In the MAUI project file, give it the logical name that MauiProgram.cs reads in step 3, here Cases.appsettings.json:

    XML
    <ItemGroup>
      <EmbeddedResource Include="appsettings.json" LogicalName="Cases.appsettings.json" />
    </ItemGroup>

    While you develop, keep the key in user secrets:

    Terminal
    dotnet user-secrets init
    dotnet user-secrets set OpenAI:Key <your-key>
  3. In MauiProgram.cs, after builder.Services.AddMauiBlazorWebView();, read the configuration and register MentorAgent:

    MauiProgram.cs
    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
    MauiProgram.cs
    // 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
    #endif
    MauiProgram.cs
    builder.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 own App class to find the project's user secrets. Any class of the app project works.

  4. Add the widget to the layout, with no render mode:

    Components/Layout/MainLayout.razor
    @using Novior.MentorAgent.Abstractions.Components
    <ChatWidget />

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:

AppAuthenticationStateProvider.cs
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:

MauiProgram.cs
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.

OptionWhy it matters on .NET MAUI
AnonymousIdentityThe 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
UseMemoryContextTurns memory on. Pair it with a persistent IMentorMemoryStore
ChatClientAny provider, including a model on the same machine: see Model providers
SafetyCheckTimeout, ClassifierChatClientWith a slow local model, give the safety checks more time or a faster model
BotName, WelcomeMessage, Theme, PrimaryColor, PositionThe widget reads them from MentorOptions, as on Blazor Server
RequireConfirmationKeep it true: the widget then shows the confirmation box, as on the other hosts
WarmUpAtStartupNo effect: a MAUI app runs no hosted service

Every option is in Configuration options.

  • Without a signed-in user, role-gated actions are refused. Until you register an AuthenticationStateProvider, every action with RequiredRoles is 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 DEBUG line reads them only in Debug builds: a Release build must get the key some other way.
  • The build does not find AddJsonStream or AddUserSecrets. The two configuration packages are not in the MAUI project, because Novior.MentorAgent does 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.json has 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 RequiredRoles is refused. No AuthenticationStateProvider is registered: the log says so. Register the provider and call SignIn.
  • Nothing is remembered after a restart. Set AnonymousIdentity = Shared and register a persistent IMentorMemoryStore.
  • MapMentorAgentMcp, MapMentorAgentA2A or the dashboard endpoint are not available. They are in Novior.MentorAgent.Server and need a web host: run them on an ASP.NET Core server.
  • 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.