Hooks
Hooks are the extensibility mechanism that allows DBTools modules to respond to Revit application events without coupling directly to the Revit API event system. They provide a clean, testable abstraction for behaviors like auto-updating elements when views change or injecting contextual ribbon UI.
Overview
What Are Hooks?
Hooks are interfaces that define contracts for responding to specific application-level events. Rather than subscribing directly to Revit's UIControlledApplication events, tools register hook handlers that the central DbtHookHost dispatches to when events occur.
Why Use Hooks?
| Direct Revit Events | DBTools Hooks |
|---|---|
| Scattered subscriptions across tools | Centralized event management |
| Manual exception handling | Automatic error handling via ISafeExecutor |
| Tight coupling to Revit API | Testable abstractions |
| Risk of blocking UI thread | Async-first design |
| Complex lifecycle management | Automatic attach/detach |
Key Benefits
- Centralized Dispatch: All event handling flows through
DbtHookHost, ensuring consistent error handling and logging - Async Support: Hook handlers can perform async work without blocking Revit's UI thread
- Automatic Lifecycle: Hooks are attached/detached during app startup/shutdown
- Settings Integration: View-activated tasks integrate with the settings system for enable/disable control
- Warning System: Hooks can set warnings when they fail, disabling auto-update until the user resolves them
Hook Architecture
Component Overview
flowchart TB
subgraph Revit["Revit Application"]
Events["UIControlledApplication Events"]
end
subgraph Core["DBTools.Core"]
HookHost["DbtHookHost"]
Registry["DbtToolRegistry"]
Executor["ISafeExecutor"]
end
subgraph Handlers["Hook Handlers"]
VAH["ViewActivatedHookHandler"]
CRI["IContextualRibbonInjector"]
end
subgraph Tasks["View Activated Tasks"]
ET["ElevationTagsViewActivatedTask"]
FT["FoundationTagsViewActivatedTask"]
JGT["JoistGirderViewActivatedTask"]
end
Events -->|ViewActivated| HookHost
Events -->|SelectionChanged| CRI
HookHost --> Executor
Executor --> VAH
Executor --> CRI
VAH --> ET
VAH --> FT
VAH --> JGT
Registry -.->|"GetHookImplementations<T>()"| HookHost
Source:
src/DBTools.Core/Tools/DbtHookHost.cs:28-55
Registration Flow
sequenceDiagram
participant Boot as DbtServiceBootstrapper
participant Module as ToolModule
participant Registry as DbtToolRegistry
participant Host as DbtHookHost
participant DI as IServiceProvider
Boot->>Module: RegisterHooks(registry, manifest)
Module->>Registry: RegisterHook<THook, TImpl>()
Note over Registry: Stores Type mapping
Boot->>DI: BuildServiceProvider()
Boot->>Host: Attach(application)
Host->>Registry: GetHookImplementations<T>()
Host->>DI: GetService(implType)
Host->>Host: Subscribe to Revit events
The DbtHookHost
DbtHookHost is the central coordinator that:
- Resolves handlers from the DI container using types registered in
DbtToolRegistry - Subscribes to Revit events (e.g.,
ViewActivated) - Dispatches to handlers wrapped in
ISafeExecutorfor error handling - Manages lifecycle via
Attach()andDetach()methods
public sealed class DbtHookHost
{
private static readonly HashSet<Type> _supportedHookInterfaces = new(new[]
{
typeof(IViewActivatedHookHandler),
typeof(IContextualRibbonInjector)
});
public void Attach(UIControlledApplication application)
{
var viewHandlers = ResolveHandlers<IViewActivatedHookHandler>();
var contextualInjectors = ResolveHandlers<IContextualRibbonInjector>();
if (viewHandlers.Count > 0)
application.ViewActivated += OnViewActivated;
InitializeContextualInjectorsNonBlocking(application, contextualInjectors);
}
}
Source:
src/DBTools.Core/Tools/DbtHookHost.cs:57-79
Available Hook Types
IViewActivatedHookHandler
Responds to Revit's ViewActivated event, which fires whenever the user switches views. This is the primary hook for auto-update functionality.
public interface IViewActivatedHookHandler
{
Task OnViewActivatedAsync(
UIControlledApplication application,
ViewActivatedEventArgs args,
CancellationToken ct);
}
Source:
src/DBTools.Core/Tools/DbtHookHost.cs:14-17
Use Cases:
- Auto-updating elevation tags when entering floor plans
- Refreshing parameter values on view change
- Updating cached view-specific data
Important Considerations:
- Handler runs on the Revit UI thread context
- Must not block; use async patterns
- Errors are caught by
ISafeExecutorand logged
IContextualRibbonInjector
Manages dynamic ribbon UI that appears based on application state (typically selection). Unlike view-activated hooks, contextual injectors manage their own event subscriptions.
public interface IContextualRibbonInjector
{
Task InitializeAsync(UIControlledApplication application, CancellationToken ct);
Task ShutdownAsync(UIControlledApplication application, CancellationToken ct);
}
Source:
src/DBTools.Core/Tools/DbtHookHost.cs:19-23
Use Cases:
- Adding "Edit" buttons to contextual tabs when tool-specific elements are selected
- Showing/hiding ribbon controls based on selection state
- Injecting panels into Revit's contextual "Modify" tabs
Lifecycle:
InitializeAsynccalled duringDbtHookHost.Attach()- Injector subscribes to
SelectionChangedor other events internally ShutdownAsynccalled duringDbtHookHost.Detach()
ExternalEvent Rule:
- Create
ExternalEventinstances only from standard Revit API execution contexts (for example, insideInitializeAsync/RefreshAsyncwork that runs throughModelessQueuedCallGate). - Do not call
ExternalEvent.Create(...)directly from Autodesk ribbon command callbacks.
Source:
src/Tools/Structural/SGT/Shell/Commands/ContextualRibbonInjector.cs:59
Source:src/Tools/Structural/SGT/Shell/Commands/ContextualRibbonInjector.cs:106
Source:src/Tools/Structural/SGT/Shell/Commands/ContextualRibbonInjector.cs:424
High-Frequency Hook Logging Policy
For high-frequency event hooks (SelectionChanged, ViewActivated), suppress routine start/completion executor noise and keep diagnostics focused on state changes and failures:
- Set
SafeExecuteOptions.LogStart = false - Set
SafeExecuteOptions.LogCompletion = false - Set
SafeExecuteOptions.WorkPerformed = falsefor no-op refresh passes - Keep
NotifyKind = Nonefor background hook refreshes
Use tool-level logs for meaningful state transitions (for example, contextual eligibility changed), and keep deep per-probe diagnostics at Trace so Debug logs stay readable during long Revit sessions.
Source:
src/DBTools.Core/Execution/SafeExecutor.cs:460
Source:src/DBTools.Core/Tools/DbtHookHost.cs:152
Source:src/DBTools.Core/Revit/UI/ContextualRibbonInjectorBase.cs:293
Source:src/Tools/Testing/ERFT/Features/Commands/ErftContextualRibbonInjector.cs:152
The IViewActivatedTask System
For the common pattern of "do something when a view activates," DBTools provides a higher-level abstraction: IViewActivatedTask. The single ViewActivatedHookHandler dispatches to all registered tasks.
IViewActivatedTask Interface
public interface IViewActivatedTask
{
string TaskId { get; }
string DisplayName { get; }
IReadOnlyCollection<string> WarningIdsToSetOnFailure { get; }
Task<bool> ShouldRunAsync(UIApplication uiapp, ViewActivatedEventArgs args, CancellationToken ct);
Task ExecuteAsync(UIApplication uiapp, ViewActivatedEventArgs args, CancellationToken ct);
}
Source:
src/DBTools.Core/Tools/IViewActivatedTask.cs:9-16
ViewActivatedTaskBase
A base class that provides settings integration and common checks:
public abstract class ViewActivatedTaskBase<TSettings> : IViewActivatedTask
where TSettings : class, IAutoUpdateSettings, new()
{
public async Task<bool> ShouldRunAsync(...)
{
var settings = GetSettings(_settingsProvider);
// Built-in checks
if (!settings.AutoUpdateEnabled) return false;
if (await _warnings.IsWarningActiveAsync(...)) return false;
// Delegate to subclass
return await ShouldRunInternalAsync(uiapp, args, settings, ct);
}
protected abstract TSettings GetSettings(ISettingsProvider settingsProvider);
protected virtual Task<bool> ShouldRunInternalAsync(...) => Task.FromResult(true);
public abstract Task ExecuteAsync(...);
}
Source:
src/DBTools.Core/Tools/ViewActivatedTaskBase.cs:15-85
Execution Flow
sequenceDiagram
participant Revit
participant Host as DbtHookHost
participant Exec as ISafeExecutor
participant Handler as ViewActivatedHookHandler
participant Task as IViewActivatedTask
participant Settings as ISettingsProvider
Revit->>Host: ViewActivated event
Host->>Exec: RunAsync(handler logic)
Exec->>Handler: OnViewActivatedAsync()
loop For each registered task
Handler->>Task: ShouldRunAsync()
Task->>Settings: Get<TSettings>()
alt AutoUpdateEnabled && no active warning
Task-->>Handler: true
Handler->>Exec: RunAsync(task.Execute)
Exec->>Task: ExecuteAsync()
alt Success
Task-->>Exec: Complete
else Failure
Exec->>Settings: SetWarning(id, true)
end
else Disabled or warning active
Task-->>Handler: false (skip)
end
end
Registering Hooks
In ToolModule.RegisterHooks()
Override RegisterHooks in your DbtToolModule to register hook handlers:
public sealed class MyToolModule : DbtToolModule
{
public override void RegisterServices(IServiceCollection services, DbtToolManifest manifest)
{
// Register the handler as a service
services.AddSingleton<MyContextualInjector>();
}
public override void RegisterHooks(DbtToolRegistry registry, DbtToolManifest manifest)
{
Guard.NotNull(registry, nameof(registry));
Guard.NotNull(manifest, nameof(manifest));
registry.RegisterHook<IContextualRibbonInjector, MyContextualInjector>();
}
}
Source:
src/Tools/Structural/SGT/SgtToolModule.cs:20-25
Registering View Activated Tasks
View-activated tasks use a different pattern. Instead of registering directly as hooks, they're registered as IViewActivatedTask services that the single ViewActivatedHookHandler collects:
public override void RegisterServices(IServiceCollection services, DbtToolManifest manifest)
{
var warningIds = GetWarningIdsFromManifest(manifest);
services.AddSingleton<IViewActivatedTask>(sp => new ElevationTagsViewActivatedTask(
sp.GetRequiredService<ISettingsProvider>(),
sp.GetRequiredService<IDbtLoggingHost>(),
sp.GetRequiredService<ILogger<ElevationTagsViewActivatedTask>>(),
warningIds));
}
Source:
src/Tools/Common/ElevationTags/ElevationTagsToolModule.cs:25-35
Registration in AppHookModule
The core AppHookModule registers the single ViewActivatedHookHandler that dispatches to all tasks:
public sealed class AppHookModule : DbtToolModule
{
public override void RegisterServices(IServiceCollection services, DbtToolManifest manifest)
{
services.AddSingleton<ViewActivatedHookHandler>();
}
public override void RegisterHooks(DbtToolRegistry registry, DbtToolManifest manifest)
{
registry.RegisterHook<IViewActivatedHookHandler, ViewActivatedHookHandler>();
}
}
Source:
src/DBTools.App/Features/Hooks/AppHookModule.cs:7-22
Implementing Hook Handlers
Step 1: Define Your Handler Class
public sealed class MyViewActivatedTask : ViewActivatedTaskBase<MyToolSettings>
{
public MyViewActivatedTask(
ISettingsProvider settings,
ILogger<MyViewActivatedTask> logger,
IReadOnlyCollection<string> warningIds)
: base(settings, logger, "mytool.updater", "My Tool Auto-Update", warningIds)
{
}
protected override MyToolSettings GetSettings(ISettingsProvider provider)
=> provider.Get<MyToolSettings>();
}
Step 2: Implement ShouldRunInternalAsync (Optional)
Filter when your task should run based on view type, document state, etc.:
protected override Task<bool> ShouldRunInternalAsync(
UIApplication uiapp,
ViewActivatedEventArgs args,
MyToolSettings settings,
CancellationToken ct)
{
var view = uiapp.ActiveUIDocument?.Document?.ActiveView;
if (view == null) return Task.FromResult(false);
// Only run in floor plans
return Task.FromResult(
view.ViewType == ViewType.FloorPlan ||
view.ViewType == ViewType.CeilingPlan);
}
Source:
src/Tools/Common/ElevationTags/Hooks/ElevationTagsViewActivatedTask.cs:40-52
Step 3: Implement ExecuteAsync
Perform the actual work:
public override Task ExecuteAsync(
UIApplication uiapp,
ViewActivatedEventArgs args,
CancellationToken ct)
{
var doc = uiapp.ActiveUIDocument?.Document
?? throw new InvalidOperationException("No active document.");
var view = doc.ActiveView
?? throw new InvalidOperationException("No active view.");
var settings = GetSettings(SettingsProvider);
var gate = new ModalInlineCallGate(uiapp);
var tx = new CallGateTransactionRunner(gate);
var updater = new MyAutoUpdater(tx, doc, view, Logger, settings);
updater.Run();
return Task.CompletedTask;
}
Source:
src/Tools/Common/ElevationTags/Hooks/ElevationTagsViewActivatedTask.cs:54-65
Step 4: Register in Your ToolModule
public override void RegisterServices(IServiceCollection services, DbtToolManifest manifest)
{
var warningIds = new[] { "mytool.auto_update.warning" };
services.AddSingleton<IViewActivatedTask>(sp => new MyViewActivatedTask(
sp.GetRequiredService<ISettingsProvider>(),
sp.GetRequiredService<ILogger<MyViewActivatedTask>>(),
warningIds));
}
Implementing Contextual Ribbon Injectors
For tools that need dynamic ribbon UI (like SGT's "Edit" button on selected girts):
Step 1: Implement IContextualRibbonInjector
public sealed class MyContextualInjector : IContextualRibbonInjector
{
private readonly ILogger<MyContextualInjector> _logger;
private readonly ISafeExecutor _executor;
private bool _subscribed;
public Task InitializeAsync(UIControlledApplication application, CancellationToken ct)
{
if (_subscribed) return Task.CompletedTask;
// Subscribe to selection changes
// Use ModelessQueuedCallGate for safe async access
return _gate.RunAsync(app =>
{
app.SelectionChanged += OnSelectionChanged;
_subscribed = true;
}, ct);
}
public Task ShutdownAsync(UIControlledApplication application, CancellationToken ct)
{
if (!_subscribed) return Task.CompletedTask;
return _gate.RunAsync(app =>
{
app.SelectionChanged -= OnSelectionChanged;
_subscribed = false;
RemoveInjectedUI();
}, ct);
}
}
Source:
src/Tools/Structural/SGT/Shell/Commands/ContextualRibbonInjector.cs:59
Step 2: Handle Selection Changes
private async void OnSelectionChanged(object? sender, SelectionChangedEventArgs e)
{
if (sender is not UIApplication uiapp) return;
try
{
await _executor.RunAsync(
() => RefreshAsync(uiapp, CancellationToken.None),
_logger,
notifier: null,
opts: new SafeExecutor.SafeExecuteOptions
{
Name = "My Contextual Selection Hook",
SuppressCompletionBanner = true,
NotifyKind = SafeExecutor.NotifyKind.None,
WorkPerformed = false
},
ct: CancellationToken.None);
}
catch (OperationCanceledException) { }
catch (Exception) { /* Logged by SafeExecutor */ }
}
Source:
src/Tools/Structural/SGT/Shell/Commands/ContextualRibbonInjector.cs:113
Step 3: Inject UI into Contextual Tabs
private void EnsureInjected(UIApplication uiapp)
{
var ribbon = ComponentManager.Ribbon;
if (ribbon == null) return;
var targetTab = TryGetContextualTab(ribbon);
if (targetTab == null) return;
var panel = EnsurePanel(targetTab);
var btn = BuildButton();
btn.CommandHandler = new RibbonButtonCommand(OnButtonClick);
panel.Source.Items.Add(btn);
}
Source:
src/Tools/Structural/SGT/Shell/Commands/ContextualRibbonInjector.cs:221
Hook Execution Order
Registration Order
Hooks are resolved in the order they were registered:
AppHookModuleregistersViewActivatedHookHandler(core)- Tool modules register their
IViewActivatedTaskimplementations - Tool modules register
IContextualRibbonInjectorimplementations
Dispatch Order
flowchart LR
subgraph Startup["App Startup"]
A1[Attach Hook Host]
A2[Initialize Contextual Injectors]
A3[Subscribe ViewActivated]
end
subgraph Runtime["Runtime Events"]
R1[ViewActivated fires]
R2[For each handler in order]
R3[For each task in order]
end
subgraph Shutdown["App Shutdown"]
S1[Detach Hook Host]
S2[Shutdown Contextual Injectors]
S3[Unsubscribe events]
end
A1 --> A2 --> A3
A3 -.->|"User navigates"| R1
R1 --> R2 --> R3
R3 -.->|"App closes"| S1
S1 --> S2 --> S3
Priority Considerations
Currently, hooks execute in registration order without explicit priority. If you need guaranteed ordering:
- Register in a specific order in
DbtServiceBootstrapper - Or coordinate within a single handler that manages multiple sub-tasks
Common Patterns
Auto-Update on View Change
The most common pattern for tools like Elevation Tags:
// 1. Define settings with IAutoUpdateSettings
public class MySettings : IAutoUpdateSettings
{
public bool AutoUpdateEnabled { get; set; } = true;
public bool HasWarning { get; set; }
}
// 2. Create task extending ViewActivatedTaskBase
public class MyAutoUpdateTask : ViewActivatedTaskBase<MySettings>
{
protected override Task<bool> ShouldRunInternalAsync(...)
{
// Filter by view type
return Task.FromResult(view.ViewType == ViewType.FloorPlan);
}
public override Task ExecuteAsync(...)
{
// Perform update logic
}
}
// 3. Register in ToolModule
services.AddSingleton<IViewActivatedTask>(...);
Contextual Ribbon Button
For tools that need UI when specific elements are selected:
// 1. Implement IContextualRibbonInjector
public class MyInjector : IContextualRibbonInjector
{
// Track subscribed state
private bool _subscribed;
// Track injected buttons for visibility control
private readonly Dictionary<string, RibbonButton> _buttons = new();
// Evaluate selection and show/hide button
private void OnSelectionChanged(...)
{
bool hasMyElements = EvaluateSelection(uiapp);
ToggleButtons(visible: hasMyElements);
}
}
// 2. Register hook
registry.RegisterHook<IContextualRibbonInjector, MyInjector>();
Warning Integration
Feature warnings are for actionable prerequisite/configuration failures (e.g., missing shared parameters, invalid settings). They should not be activated for transient/runtime exceptions.
// Example: set warning only for actionable prerequisite failure
var eval = MyPrereqEvaluator.Evaluate(uiapp, doc, tx, logger);
if (eval.Kind == PrereqKind.Invalid)
{
foreach (var warningId in WarningIdsToSetOnFailure)
await warnings.SetWarningAsync(warningId, true, context: null, ct);
return;
}
// Runtime exceptions: show a banner once + disable for this document session (do NOT set warnings)
try { updater.Run(); }
catch (Exception ex)
{
RecordRuntimeFailure(doc);
ShowRuntimeFailureBannerOnce(doc, ex.GetBaseException().Message, logger);
}
The warning IDs must be defined in the tool's settings pack and match entries in manifest.yml.
Creating New Hook Types
To add a new hook type (advanced):
Step 1: Define the Interface
// In DBTools.Core/Tools/
public interface IDocumentClosingHookHandler
{
Task OnDocumentClosingAsync(
UIControlledApplication application,
DocumentClosingEventArgs args,
CancellationToken ct);
}
Step 2: Register as Supported in DbtHookHost
private static readonly HashSet<Type> _supportedHookInterfaces = new(new[]
{
typeof(IViewActivatedHookHandler),
typeof(IContextualRibbonInjector),
typeof(IDocumentClosingHookHandler) // Add new type
});
Step 3: Add Resolution and Subscription
public void Attach(UIControlledApplication application)
{
// ... existing code ...
var closingHandlers = ResolveHandlers<IDocumentClosingHookHandler>();
if (closingHandlers.Count > 0)
application.DocumentClosing += OnDocumentClosing;
_closingHandlers = closingHandlers;
}
private async void OnDocumentClosing(object? sender, DocumentClosingEventArgs args)
{
if (_closingHandlers == null || _closingHandlers.Count == 0) return;
try
{
await _executor.RunAsync(
async () =>
{
foreach (var handler in _closingHandlers)
await handler.OnDocumentClosingAsync(_application!, args, CancellationToken.None);
},
_logger,
notifier: null,
opts: new SafeExecutor.SafeExecuteOptions { /* ... */ },
ct: CancellationToken.None);
}
catch { /* Suppress to protect Revit */ }
}
Step 4: Add Detach Logic
public void Detach(UIControlledApplication application)
{
// ... existing code ...
try { _application.DocumentClosing -= OnDocumentClosing; }
catch (Exception ex) { _logger.LogWarning(ex, "Failed to detach document-closing hook."); }
_closingHandlers = null;
}
Performance Considerations
Don't Block the UI Thread
Hooks run in response to Revit events. Blocking causes UI freezes:
// BAD - blocks UI
public Task ExecuteAsync(...)
{
Thread.Sleep(5000); // Never do this!
return Task.CompletedTask;
}
// GOOD - async work
public async Task ExecuteAsync(...)
{
await SomeAsyncOperation();
}
Use SafeExecutor Options Wisely
For frequent hooks like view activation, suppress banners:
new SafeExecutor.SafeExecuteOptions
{
SuppressCompletionBanner = true,
NoBannerOnSuccess = true,
NotifyKind = SafeExecutor.NotifyKind.Error, // Only show errors
WorkPerformed = false // Don't log "no work" for frequent events
}
Filter Early with ShouldRunAsync
Avoid expensive work when the hook shouldn't run:
protected override Task<bool> ShouldRunInternalAsync(...)
{
// Quick checks first
var view = uiapp.ActiveUIDocument?.Document?.ActiveView;
if (view == null) return Task.FromResult(false);
if (view.ViewType != ViewType.FloorPlan) return Task.FromResult(false);
// Only then check more expensive conditions
return Task.FromResult(true);
}
Batch Operations in Transactions
When modifying elements, batch changes in a single transaction:
public override Task ExecuteAsync(...)
{
using var tx = new Transaction(doc, "Auto Update");
tx.Start();
foreach (var element in elementsToUpdate)
UpdateElement(element);
tx.Commit();
return Task.CompletedTask;
}
Real Examples
ElevationTags Auto-Update
The Elevation Tags tool uses a view-activated task to update tag values when entering floor plans:
public sealed class ElevationTagsViewActivatedTask : ViewActivatedTaskBase<ElevationTagsSettings>
{
public ElevationTagsViewActivatedTask(
ISettingsProvider settings,
IDbtLoggingHost loggingHost,
ILogger<ElevationTagsViewActivatedTask> logger,
IReadOnlyCollection<string> warningIds)
: base(settings, logger, "structural.elevation_tags.updater", "Elevation Tags", warningIds)
{
_loggingHost = loggingHost;
}
protected override Task<bool> ShouldRunInternalAsync(...)
{
var view = uiapp.ActiveUIDocument?.Document?.ActiveView;
return Task.FromResult(
view?.ViewType == ViewType.FloorPlan ||
view?.ViewType == ViewType.CeilingPlan);
}
public override Task ExecuteAsync(...)
{
var updater = new ElevationTagsAutoUpdater(tx, doc, view, logger, settings);
updater.Run();
return Task.CompletedTask;
}
}
Source:
src/Tools/Common/ElevationTags/Hooks/ElevationTagsViewActivatedTask.cs:20-66
SGT Contextual Ribbon
The Super Girt Tool injects an "Edit" button into the contextual "Modify | Structural Framing" tab:
public sealed class ContextualRibbonInjector : IContextualRibbonInjector
{
public Task InitializeAsync(UIControlledApplication application, CancellationToken ct)
{
return _gate.RunAsync(app =>
{
EnsureEditEventCreated(); // ExternalEvent.Create in API-safe context
app.SelectionChanged += OnSelectionChanged;
_subscribed = true;
EnsureInjected(app);
EvaluateAndToggle(app);
}, ct);
}
private void EvaluateAndToggle(UIApplication uiapp)
{
var (hasGirts, systemIds) = EvaluateSelection(uiapp);
UpdateSystemIds(systemIds);
ToggleButtons(hasGirts, hasGirts);
}
private void EnsureInjected(UIApplication uiapp)
{
var targetTab = TryGetContextualModifyStructuralTab(ribbon);
var panel = EnsurePanel(targetTab);
var btn = BuildButton();
panel.Source.Items.Add(btn);
}
}
Source:
src/Tools/Structural/SGT/Shell/Commands/ContextualRibbonInjector.cs:27
AppHookModule Registration
The core hook module that wires up view-activated handling:
public sealed class AppHookModule : DbtToolModule
{
public override void RegisterServices(IServiceCollection services, DbtToolManifest manifest)
{
services.AddSingleton<ViewActivatedHookHandler>();
}
public override void RegisterHooks(DbtToolRegistry registry, DbtToolManifest manifest)
{
registry.RegisterHook<IViewActivatedHookHandler, ViewActivatedHookHandler>();
}
}
Source:
src/DBTools.App/Features/Hooks/AppHookModule.cs:7-22
Debugging Hooks
Enable Debug Logging
Hook handlers log at debug level. Enable in your development configuration:
{
"Serilog": {
"MinimumLevel": {
"Override": {
"DBTools.App.Features.Hooks": "Debug"
}
}
}
}
Common Issues
| Symptom | Possible Cause | Solution |
|---|---|---|
| Hook never fires | Not registered in RegisterHooks |
Verify RegisterHook<>() call |
| Handler not resolved | Service not registered | Add AddSingleton<>() in RegisterServices |
| UI thread deadlock | Sync-waiting on async in hook | Use async void for event handlers, never .Wait() |
| Hook runs but no effect | ShouldRunAsync returning false |
Check settings (AutoUpdateEnabled, HasWarning) |
| Exception in hook | Various | Check logs; ISafeExecutor logs all errors |
See Also
- Creating a New Tool - Complete guide to building DBTools tools
- Architecture Overview - System architecture and component interactions
- Settings System - How tools integrate with the settings infrastructure