Revit Command and Application
Derive add-in entry points from the Nice3point.Revit.Toolkit base classes.
They replace the raw interface boilerplate (the full Execute(commandData, ref message, elements) signature, manual assembly resolution) with a simplified override and ready-made context properties.
When to use
- Authoring an
IExternalCommand,IExternalApplication, orIExternalDBApplicationentry point. - Reviewing an entry point that reimplements the raw interface by hand.
When not to use
- Building the ribbon inside
OnStartup— that isrevit-ribbon. - Invoking the API from a modeless window or background thread — that is
revit-external-events.
Workflow
Step 1: Derive an external command
Override Execute(); reach the context through the inherited Application, not by unpacking commandData.
[Transaction(TransactionMode.Manual)]
public class DeleteWallsCommand : ExternalCommand
{
public override void Execute()
{
var document = Application.ActiveUIDocument.Document;
}
}
ExternalCommand exposes high-level Application, View, JournalData, and ElementSet properties that IExternalCommand provides.
Result defaults to Succeeded; set Result and ErrorMessage only when the command must return a canceled or failed outcome.
Step 2: Derive an external application
public class Application : ExternalApplication
{
public override void OnStartup()
{
var panel = Application.CreatePanel("Commands", "RevitAddin");
panel.AddPushButton<DeleteWallsCommand>("Delete walls");
}
}
Use ExternalDBApplication for a database-only application.
Override OnShutdown() only when the application has session cleanup to perform; it is optional.
The ribbon helpers (CreatePanel, AddPushButton<T>) come from Nice3point.Revit.Extensions — see revit-ribbon.
Step 3: Use the async base classes for long-running work
Derive from AsyncExternalCommand and override ExecuteAsync(), or from AsyncExternalApplication and override OnStartupAsync()/OnShutdownAsync().
The sealed base pumps the message loop on Revit's main thread; await keeps the UI responsive while the continuation still runs in the API context — no external event needed.
public class Application : AsyncExternalApplication
{
public override async Task OnStartupAsync()
{
var settings = await LoadSettingsAsync(); // long-running work; the UI stays responsive
CreateRibbon(settings);
}
}
Step 4: Rely on automatic dependency resolution
The base classes open an assembly-resolve scope automatically; add-in dependencies load from the add-in folder without a manual AssemblyResolve handler.
For dependencies loaded outside an entry point, open a scope explicitly with ResolveHelper.BeginAssemblyResolveScope.
Step 5: Verify
Test the observable model behavior on the Revit thread. Leave the default successful result unless the command must cancel or fail.
Validation
- The entry point derives from a Toolkit base class, not a hand-written interface implementation.
- Context comes from the inherited properties, not manual
commandDataunpacking. - An application overrides
OnShutdown()only when it has session cleanup to perform. - Long-running UI work uses
AsyncExternalCommand. - The command has the expected observable behavior in Revit.
-
ResultandErrorMessageare set only for a canceled or failed command.
Common Pitfalls
| Pitfall | Correct approach |
|---|---|
Implementing Execute(commandData, ref message, elements) by hand |
Derive from ExternalCommand and override Execute(). |
Manual AppDomain.AssemblyResolve for add-in dependencies |
The base classes resolve automatically; else open a ResolveHelper.BeginAssemblyResolveScope. |
| Blocking the UI in a long command | Use AsyncExternalCommand and ExecuteAsync(). |
ExternalCommand not found |
The Nice3point.Revit.Toolkit package is not referenced. |