Table of Contents

English | 日本語 | 简体中文 | 繁體中文 | 한국어 |

Extension Points

ZYC.Framework extensions are mostly registered through modules and Autofac. A module loads with the host, registers services or UI contributions, and then the shell composes menus, tabs, workspace operations, status bar items, taskbar menu items, drag/drop actions, and Aspire resources from those registrations.

Extension Point Map

Extension point Register from Runtime consumer
Module lifecycle ModuleBase.RegisterAsync, LoadAsync, AfterLoadedAsync Host startup and module loader.
URI tabs ITabItemFactoryManager.RegisterFactory<T>() TabManager.InternalNavigateAsync(...).
Simple view tabs ISimpleTabItemFactoryManager.Register(...) Built-in SimpleTabItemFactory.
Main menu IMainMenuManager, IMainMenuItemsProvider MainMenuManager and main menu views.
Window title IWindowTitleManager, IWindowTitleExtendManager WindowTitleView.
Workspace menu IWorkspaceMenuManager WorkspaceMenuView.
Workspace context menu manager IWorkspaceContextMenuManager Manager exists and sorts items; use it when wiring a context-menu surface.
Tab header context menu ITabItemHeaderContextMenuItemView TabItemHeaderContextMenuItemsResolver.
Status bar IStatusBarManager, IStatusBarItemsProvider StatusBarManager.
Taskbar menu ITaskbarMenuManager TaskbarContextMenu.
Config/state IConfig, IState ModuleTools.RegisterAllFromAssembly(...).
Events IEventAggregator Runtime publish/subscribe bus.
Toasts IToastManager, IToast Toast popup host.
Drag/drop IDropActionProvider DropOrchestrator.
Aspire resources IExtensionResourcesProvider AspireService.Build(...).
CLI options ModuleBase.RegisterCommandLineOption(...) ZYC.Framework.CLI root command.

Module Lifecycle

Use the module lifecycle to decide where each registration belongs:

Method Use for
RegisterAsync(ContainerBuilder builder) Autofac registrations that must exist before the container is built.
LoadAsync(ILifetimeScope lifetimeScope) Runtime contributions such as tab factories, menu items, status bar providers, and Aspire resource registration.
AfterLoadedAsync(ILifetimeScope lifetimeScope) Cross-module work that needs all modules to already be loaded.
RegisterCommandLineOption(...) CLI flags owned by the module.

Most UI modules only need LoadAsync.

URI Tabs

URI tabs are the primary way to expose a feature surface. Register an ITabItemFactory, usually by using the helper on ILifetimeScope:

public override Task LoadAsync(ILifetimeScope lifetimeScope)
{
    lifetimeScope.RegisterTabItemFactory<ReportsTabItemFactory>();
    return Task.CompletedTask;
}

Use TabItemRouteAttribute when the route can be described by URI parts. Override CheckUriMatchedAsync when the decision needs a service, file type check, or more complex policy.

Use RegisterSimpleTabItemFactory(...) only for the small single-view case where a UserControl can be opened without a dedicated route model.

The root menu contains built-in providers for File, View, Tools, Extensions, and About. Module menu items normally register under one of those providers:

public override Task LoadAsync(ILifetimeScope lifetimeScope)
{
    lifetimeScope.RegisterExtensionsMainMenuItem<ReportsMainMenuItem>();
    return Task.CompletedTask;
}

Menu ordering uses Anchor first and Priority inside the anchor group. MainMenuManager inserts separators between anchor groups and sorts sub-items recursively.

Create a module-owned IMainMenuItemsProvider only when the module needs a parent menu with multiple child commands. For one command, register an IMainMenuItem under an existing provider.

Window Title

Use IWindowTitleManager for compact command-style title-bar buttons and IWindowTitleExtendManager for richer title-bar content. Extension items implement IWindowTitleExtendItem and return the view object that should be displayed in the title bar.

public override Task LoadAsync(ILifetimeScope lifetimeScope)
{
    lifetimeScope.Resolve<IWindowTitleExtendManager>()
        .RegisterItem<ReportsWindowTitleItem>();

    return Task.CompletedTask;
}

Use this surface for module-owned status or account controls that must stay visible near the window chrome. Use quick bar items for user-favorited shortcuts instead of module-owned title-bar UI.

Workspace Menus

Use IWorkspaceMenuManager for commands that should appear in the visible workspace drop-down beside the workspace index:

public override Task LoadAsync(ILifetimeScope lifetimeScope)
{
    lifetimeScope.Resolve<IWorkspaceMenuManager>()
        .RegisterItem<ReportsWorkspaceMenuItem>();

    return Task.CompletedTask;
}

IWorkspaceMenuItem supports Title, Command, SubItems, Icon, Anchor, Priority, and Localization. The visible WorkspaceMenuView currently reads IWorkspaceMenuManager.GetItems().

IWorkspaceContextMenuManager is a separate manager that sorts items by Anchor and Priority recursively. Do not assume its items are visible unless a context-menu view has been wired to it.

Tab Header Context Menu

Tab header menu items are WPF menu item views registered as ITabItemHeaderContextMenuItemView:

[RegisterAs(typeof(ITabItemHeaderContextMenuItemView))]
internal partial class ReportsTabHeaderMenuItem :
    ITabItemHeaderContextMenuItemView
{
    public int Order => 20;
}

TabItemHeaderContextMenuItemsResolver resolves all registered views and orders them by Order. Because WPF context menus are late-bound, prefer command parameters and the existing ContextMenuItemBase pattern when the item needs the current tab instance.

Status Bar

A status bar extension contributes an IStatusBarItemsProvider; the provider returns one or more IStatusBarItem instances. StatusBarManager aggregates all registered providers and orders items by Order.

public override Task LoadAsync(ILifetimeScope lifetimeScope)
{
    lifetimeScope.Resolve<IStatusBarManager>()
        .RegisterStatusBarItemsProvider<ReportsStatusBarItemsProvider>();

    return Task.CompletedTask;
}

Each item chooses its side with StatusBarSection.Left or StatusBarSection.Right.

Taskbar Menu

Taskbar menu items implement ITaskbarMenuItem and are registered with ITaskbarMenuManager:

lifetimeScope.Resolve<ITaskbarMenuManager>()
    .RegisterMenuItem(lifetimeScope.Resolve<ReportsTaskbarMenuItem>());

The taskbar menu groups by Info.Anchor, orders by Info.Priority, and recursively sorts sub-items. Use this surface for tray/window-level commands, not for feature navigation that belongs in the main menu.

Config and State

Any concrete type implementing IConfig or IState is loaded from the settings directory during module assembly registration and registered into Autofac. Use this for small JSON-serializable settings and state.

Guidelines:

  • Put user-editable options in IConfig.
  • Put runtime persistence in IState.
  • Keep the type small and version-tolerant.
  • Do not use config/state as a large business data store.

Events and Toasts

Use IEventAggregator for decoupled runtime notifications:

lifetimeScope.PublishEvent(new ReportsChangedEvent());
lifetimeScope.SubscribeEvent<ReportsChangedEvent>(OnReportsChanged, onUiThread: true);

Use IToastManager for user-facing transient feedback:

toastManager.PromptMessage(ToastMessage.Info("Report exported.", localization: false));
toastManager.PromptException(exception);

Events are for coordination. Toasts are for visible feedback. Avoid using toast messages as control flow.

Drag and Drop

Drag/drop actions are contributed through IDropActionProvider. The orchestrator asks every provider for compatible DropAction items, filters by CanRun(), de-duplicates by Id, and then either executes the default action or shows a picker.

Use this extension point when a module wants to handle dropped files, paths, or tab payloads in a workspace-aware way. The DropContext includes the target object, workspace id, modifier keys, screen point, and cancellation token.

Aspire Resources

Aspire extension modules register IExtensionResourcesProvider. AspireService.Build(...) resolves all providers and calls ConfigureResources(builder) on each one.

For command-line child services, register through ICommandlineResourcesProvider:

lifetimeScope.Resolve<ICommandlineResourcesProvider>()
    .Register(new CommandlineServiceOptions
    {
        Name = "reports-worker",
        WorkDirectory = workerDirectory,
        Command = "dotnet run"
    });

Use this for sidecar services that should be launched by the Aspire host instead of by a large in-app UI.

CLI Options

The CLI loads modules and calls RegisterCommandLineOption(container, optionRegister) before finalizing the root command. Use this when the module needs its own command-line switch.

For full scaffolding commands, use explicit subcommands such as the built-in zyc new and zyc new-module pattern instead of overloading unrelated flags.

Choosing the Right Surface

Goal Use
Open a feature view URI + ITabItemFactory
Add one top-level app command Existing main menu provider
Add several commands under one module parent Module-owned IMainMenuItemsProvider
Add a workspace operation IWorkspaceMenuManager
Add a tab header action ITabItemHeaderContextMenuItemView
Show lightweight runtime status Status bar provider
Add tray/window command ITaskbarMenuManager
Persist small settings or state IConfig / IState
Coordinate runtime behavior IEventAggregator
Show user feedback IToastManager
Add dropped-file behavior IDropActionProvider
Launch a sidecar service Aspire resource provider