MentorAgent Docs
Menu

Questa pagina non è ancora tradotta in italiano: qui la trovi in inglese.

How-to guidePackages: Novior.MentorAgent, Novior.MentorAgent.Server, Novior.MentorAgent.Blazor

Blazor WebAssembly and Auto

Keep the widget on the server, or run it in a WebAssembly or Auto client with Novior.MentorAgent.Blazor, backed by Novior.MentorAgent.Server.

On this page

Use this page if you have a standalone Blazor WebAssembly app, or a Blazor Web App with Auto or WebAssembly interactivity. In such an app the agent always runs on the server. The widget can stay on the server too (option A), or run in the browser and reach the agent over SignalR (option B).

Choose it for a Blazor Web App with Auto or WebAssembly interactivity whose layout is rendered statically: Interactivity location: Per page/component in the template. Only the widget runs with Server interactivity: it gets its own InteractiveServer render mode. The rest of the app keeps its render mode. You need neither Novior.MentorAgent.Server nor Novior.MentorAgent.Blazor. An app created with WebAssembly interactivity also needs Server interactivity turned on.

Choose it for a standalone Blazor WebAssembly app, for a Blazor Web App with Interactivity location: Global, or when the widget must run in the browser. The server project gets Novior.MentorAgent.Server, which adds the SignalR hub. The client project gets Novior.MentorAgent.Blazor, which gives the same <ChatWidget /> a hub connection. The model key never reaches the browser. All the features of the agent run on the server: specialists, search over your documents, memory, confirmation, and everything that uses the Model Context Protocol (MCP) or the Agent-to-Agent (A2A) protocol.

For a desktop app see .NET MAUI; for a JavaScript or mobile client, Any front end.

Option A. The server project needs Novior.MentorAgent and the package of your model provider, as on Blazor Server.

Option B. In the server project, Novior.MentorAgent.Server brings Novior.MentorAgent with it:

.NET CLI

Terminal
dotnet add package Novior.MentorAgent.Server

PackageReference

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

Central Package Management

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

File-based app

app.cs
#:package Novior.MentorAgent.Server@1.2.0

With either option, MentorAgent does not include a model provider package. The server code on this page uses OpenAI, so add its package to the server project:

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

For another provider, see Model providers.

In the client project:

.NET CLI

Terminal
dotnet add package Novior.MentorAgent.Blazor

PackageReference

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

Central Package Management

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

File-based app

app.cs
#:package Novior.MentorAgent.Blazor@1.2.0

Option A. Register MentorAgent in the server project with the same Program.cs lines as on Blazor Server:

Program.cs (server)
// MentorLanguage, MentorTheme, ChatPosition, MentorshipLevel
using Novior.MentorAgent.Abstractions.Models;
using Novior.MentorAgent.Extensions;   // AddMentorAgent
using Microsoft.Extensions.AI;         // AsIChatClient
using OpenAI;                          // OpenAIClient
Program.cs (server)
builder.Services.AddMentorAgent(options =>
{
    options.AppName = "Case management";
    options.ChatClient = new OpenAIClient(
            builder.Configuration["OpenAI:Key"]
            ?? throw new InvalidOperationException(
                "Set OpenAI:Key with dotnet user-secrets."))
        .GetChatClient("gpt-4o-mini")
        .AsIChatClient();
    options.ScanAssemblies = [typeof(Program).Assembly];
});

The Auto template already turns on Server interactivity. The WebAssembly template does not: in the same Program.cs, chain .AddInteractiveServerComponents() to AddRazorComponents(), and .AddInteractiveServerRenderMode() to MapRazorComponents<App>(). Without them every page answers HTTP 500.

Then give the widget Server interactivity in the server project's layout:

Components/Layout/MainLayout.razor
@using Novior.MentorAgent.Abstractions.Components
<ChatWidget @rendermode="InteractiveServer" />

This works only because the layout is static. With Interactivity location: Global (--all-interactive), option A fails:

  • the layout is in the client project, inside the interactive router, and a child component cannot switch to another interactive render mode;
  • the client project does not know ChatWidget unless it references a MentorAgent package, so the build stops. If you add the package, the build, the startup and the prerendered page all succeed. The error appears only in the browser, when WebAssembly renders the layout;
  • with Auto, the widget works while the app still runs on the server, and fails once it runs in WebAssembly.

With global interactivity, use option B.

Option B.

  1. In the server project, register the services and map the hub:

    Program.cs (server)
    using Novior.MentorAgent.Extensions;   // AddMentorAgent
    // AddMentorAgentServer, MapMentorAgentServer
    using Novior.MentorAgent.Server.Extensions;
    using Microsoft.Extensions.AI;         // AsIChatClient
    using OpenAI;                          // OpenAIClient
    Program.cs (server)
    builder.Services.AddMentorAgent(options =>
    {
        options.AppName = "Case management";
        options.ChatClient = new OpenAIClient(
                builder.Configuration["OpenAI:Key"]
                ?? throw new InvalidOperationException(
                    "Set OpenAI:Key with dotnet user-secrets."))
            .GetChatClient("gpt-4o-mini")
            .AsIChatClient();
        options.ScanAssemblies = [typeof(Program).Assembly];
    });
    builder.Services.AddMentorAgentServer();
    // after var app = builder.Build();
    // /mentor-hub, /mentor/chat, /mentor/approve, /mentor/cancel ...
    app.MapMentorAgentServer();
  2. In the client project, register the widget's services:

    Program.cs (client)
    using Novior.MentorAgent.Abstractions.Models; // MentorLanguage, MentorTheme
    using Novior.MentorAgent.Blazor.Extensions;   // AddMentorAgentBlazor
    Program.cs (client)
    // The widget posts Stop and confirmations through an HttpClient. The
    // standalone template registers this line; a Blazor Web App does not
    builder.Services.AddScoped(sp => new HttpClient
    {
        BaseAddress = new Uri(builder.HostEnvironment.BaseAddress),
    });
    
    builder.Services.AddMentorAgentBlazor(options =>
    {
        // Same origin as the server: the default "/mentor-hub" is resolved
        // against the base address of the page
        options.HubUrl = "/mentor-hub";
    });

    HubUrl defaults to /mentor-hub. A relative value is resolved against the base address of the page, as a browser does: /mentor-hub points to the root of the origin, and mentor-hub to the base path of the app. It works when the server also serves the client, as in a Blazor Web App or a hosted WebAssembly app. For a server on another origin, set an absolute HubUrl: see Complete configuration.

  3. Add the widget to a layout or page of the client project, as on Blazor Server. In a Blazor Web App the client project has a layout only with Interactivity location: Global:

    Layout/MainLayout.razor (client)
    @using Novior.MentorAgent.Abstractions.Components
    <ChatWidget />
  4. In a standalone app, reference the widget's stylesheet and script in wwwroot/index.html. That page is served before .NET starts in the browser, so the widget cannot add them in time.

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

Keep the ?v= query string and change it on every upgrade: nothing else tells the browser that its cached copy is old.

A Blazor Web App has no index.html. Its server prerenders the widget, and the widget adds both tags to the <head> of the page itself, as on Blazor Server. Add the two lines to Components/App.razor only if you turn prerendering off.

A standalone client on another origin, with signed-in users. On the server, allow the client's origin and require authentication on the hub:

Program.cs (server)
builder.Services.AddCors(options => options.AddPolicy("MentorClients",
    policy => policy
    .WithOrigins("https://localhost:7001")   // the WebAssembly app's origin
    .AllowAnyHeader()
    .AllowAnyMethod()
    .AllowCredentials()));                   // SignalR needs credentials

// Plus your authentication scheme (JWT bearer, cookies):
// builder.Services.AddAuthentication()...
app.UseCors("MentorClients");   // before authentication and authorization
app.UseAuthentication();
app.UseAuthorization();
app.MapMentorAgentServer(configureTransports: e => e.RequireAuthorization());

On the client, point to the server and send the user's token:

Program.cs (client)
builder.Services.AddScoped(sp => new HttpClient
{
    BaseAddress = new Uri(builder.HostEnvironment.BaseAddress),
});

builder.Services.AddMentorAgentBlazor(options =>
{
    // Server on another origin: absolute URL, and CORS on the server
    options.HubUrl = "https://api.example.com/mentor-hub";
    // The signed-in user's token, for the hub, Stop and confirmations
    options.AccessTokenProvider = () => Task.FromResult(tokenStore.Token);

    // The widget: on WebAssembly these settings live here, not in MentorOptions
    options.BotName = "Case assistant";
    options.Language = MentorLanguage.English;
    options.Theme = MentorTheme.Minimal;
    options.EnableSuggestions = true;
});

TokenStore stands for whatever your application uses to keep the signed-in user's token.

OptionProjectWhy it matters
HubUrlclientThe default /mentor-hub is resolved against the base address of the page. Set an absolute URL for a server on another origin. The origin of an absolute URL is also where the widget posts Stop and confirmations
AccessTokenProviderclientThe user's token, read on connect and on every reconnect, and sent with Stop and confirmations. Without it every user is anonymous to the server
ConfigureConnectionclientAnything else the hub connection needs: a header, a transport, cookies
BotName, Theme, PrimaryColor, Position, WelcomeMessage, LanguageclientOn WebAssembly the widget reads its look and its own texts from the client options, not from MentorOptions. In a Blazor Web App the server prerenders it with MentorOptions first: give both the same values
EnableSuggestionsclientOff by default on the client, while it is on by default in MentorOptions
EnableOnboardingTourclientTurn it on in the client options. The server answers GET /mentor/tour whatever its own value. In a Blazor Web App, give MentorOptions the same value, because the server prerenders the widget
EnableImageInputbothTurn it on in the client options and in MentorOptions
Everything about the agentserverModel, agents, search over your documents, memory, MCP, A2A, costs and limits stay in MentorOptions, on the server. Language in MentorOptions sets the language of the answers

Every server option is in Configuration options, and every client option under MentorAgentBlazorOptions.

  • Who the user is. AddMentorAgentServer() passes the user of the hub connection to MentorAgent. RequiredRoles, per-user memory and the rate limit then work as on Blazor Server, if the client sends the token through AccessTokenProvider.
  • In a Blazor Web App too. AddRazorComponents() and AddMentorAgentServer() can come in either order. MentorAgent reads the user of the hub connection from the request, before it asks Blazor's own AuthenticationStateProvider, which works only inside a circuit. To add claims or roles for the hub, see Secure the endpoints.
  • With Auto, the user comes from two places. On the first visit the page runs on the server, and the widget runs in the circuit: the user is the user of the circuit. After the switch to WebAssembly the widget uses the hub: the user is the user of the token that AccessTokenProvider sends. Without a token, the widget is anonymous after the switch and role-gated actions are refused.
  • When the token is read. The widget opens the connection with the first message and keeps it for the life of the page. A user who signs in after that stays anonymous until the page reloads.
  • JSON Web Token (JWT) bearer. In the browser, WebSocket and Server-Sent Events connections cannot carry an Authorization header, so SignalR sends the token in the access_token query string. The server must read it from there for /mentor-hub: see Choose the authentication scheme.
  • Confirmation. The widget shows the confirmation box. The answer goes to POST /mentor/approve and Stop to POST /mentor/cancel, on the hub's origin and with the same token. While a signed-in user's connection is open, only that user can send them. When the connection closes, the turn that waits for a confirmation ends with it. The widget handles the 404 that POST /mentor/approve returns when nothing waits for the answer any more. See Where nobody can answer for the other channels.
  • The hub is open until you protect it. configureTransports: e => e.RequireAuthorization() covers the hub and both /mentor/chat endpoints.
  • The widget never connects from another origin. The browser blocked the SignalR handshake. List the client's origin with AllowCredentials(), and use an absolute HubUrl. Call UseCors before UseAuthentication() and UseAuthorization(), and write those two calls in Program.cs yourself. Otherwise authorization can refuse the browser's preflight request. This is a request that the browser sends before the real request, and it carries no credentials.
  • The widget appears without styles for a moment. The stylesheet and script are missing from the page that hosts the app. Add them as in step 4 of Minimal configuration.
  • After an upgrade, the tour does not start and voice output waits for the end of the answer. The browser still has an old MentorAgent.js: change the ?v= value.
  • Every page answers HTTP 500 after you give the widget its own render mode: "the required endpoints are not mapped on the server". The app was created with WebAssembly interactivity and has no Server interactivity: add AddInteractiveServerComponents() and AddInteractiveServerRenderMode(), as in Minimal configuration.
  • "Cannot create a component of type 'Novior.MentorAgent.Abstractions.Components.ChatWidget'… is not supported by WebAssembly rendering" in the browser console. The widget has a server render mode inside an interactive router (Interactivity location: Global). Option A needs a static layout: with global interactivity, use option B.
  • The WebAssembly runtime does not start, and the console says CannotResolveService for HttpClient. The client project of a Blazor Web App registers no HttpClient, and the widget needs one for Stop and confirmations. Add the AddScoped(sp => new HttpClient { … }) line of the client snippet in Minimal configuration.
  • Every action with RequiredRoles is refused, and memory starts over at every reconnect. The server sees an anonymous caller: set AccessTokenProvider, and with JWT bearer read the token from the query string.
  • Stop or a confirmation seems ignored. The client has no HttpClient registered, or the server's Cross-Origin Resource Sharing (CORS) policy does not allow the POST or the Authorization header. The widget only logs the failure, in the browser console.
  • SerializeSessionAsync throws NotSupportedException. On WebAssembly the conversation lives on the server. Save and restore it over HTTP with GET and POST /mentor/session, as described in Any front end. The client cannot read or change the snapshot. Store it as text and send it back unchanged.
  • 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.
  • Secure the endpoints — Protect the SignalR hub, Server-Sent Events, /mcp and /a2a endpoints with authentication and CORS before you expose them.
  • 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.