Scoped Contexts
Scoped contexts let users navigate into sub-trees of your command graph — like cd for your app’s data model.
Defining contexts
Section titled “Defining contexts”app.Context("client", client =>{ client.Map("list", static (IContactStore store) => store.All());
client.Context("{id:int}", scoped => { scoped.Map("show", static (int id, IContactStore store) => store.Get(id)); scoped.Map("remove", static (int id, IContactStore store) => { store.Remove(id); return Results.Success("Removed."); }); });});The shape of this graph:
myapp└── client ├── list └── {id:int} ├── show └── removeREPL navigation
Section titled “REPL navigation”In REPL mode, enter a context by typing its name. Use .. to go up:
> client[client]> listAlice (1)Bob (2)
[client]> 1[client/1]> showId: 1 Name: Alice
[client/1]> ..[client]> ..>In CLI mode, the same routes work as a flat command:
myapp client 1 showmyapp client 1 removeDI-backed handlers
Section titled “DI-backed handlers”With ReplApp (vs. the lighter CoreReplApp), handlers can take DI-injected services:
var app = ReplApp.Create(services =>{ services.AddSingleton<IContactStore, ContactStore>();}).UseDefaultInteractive();
app.Context("client", client =>{ client.Map("list", (IContactStore store) => store.All());
client.Context("{id:int}", scoped => { scoped.Map("show", static (int id, IContactStore store) => store.Get(id)); });});
return app.Run(args);Parameters that match registered services are injected automatically. Parameters that match route segments are bound from the route.
Scoped state
Section titled “Scoped state”A context does not create a captured object scope by returning nested mappings. The nested routes are configured up front, and route values from parent contexts are bound into child handlers when commands run:
client.Context("{id:int}", scoped =>{ scoped.Map("show", static ([FromContext] int id, IContactStore store) => store.Get(id) ?? throw new KeyNotFoundException($"Contact {id} not found."));
scoped.Map("update {name}", static ([FromContext] int id, string name, IContactStore store) => { var contact = store.Get(id) ?? throw new KeyNotFoundException($"Contact {id} not found."); var updated = contact with { Name = name }; store.Save(updated); return updated; });});The id value comes from the active {id:int} context. [FromContext] makes that source explicit and avoids ambiguity if the same type is also available from DI.
For session-level or user-level mutable state, prefer a service registered with AddScoped<T> in hosted sessions, or an explicit store service for CLI/REPL apps.
Completion providers
Section titled “Completion providers”Register a completion provider to populate Tab suggestions in REPL mode:
client.Context("{id:int}", scoped => { /* ... */ }) .WithCompletion((IContactStore store) => store.All().Select(c => new CompletionItem(c.Id.ToString(), c.Name)));Further reading
Section titled “Further reading”- Routes & Parameters — route constraint syntax, completion providers, and parameter binding.
- Modules — packaging reusable scope patterns into
IReplModuleclasses. - Dependency Injection — injecting services into scoped handlers.