Sandbox Validator Architecture
Overview
The Sandbox Validator is a critical build-time validation system that verifies the integrity of DBTools dist outputs before deployment. It runs as part of the BuildAll target and catches XAML errors, manifest misconfigurations, binding failures, and assembly merge issues that would otherwise only manifest at runtime inside Revit.
The validator operates in two modes:
- Headless mode (
--headless): Used by the build pipeline for automated validation - Interactive mode: A UI gallery for manual testing and development
Source:
src/DBTools.Sandbox/App.xaml.cs:21-77
Architecture Diagram
NUKE BuildAll
|
v
+-------------------+
| ValidateDist |
| (BuildTargets) |
+-------------------+
|
| --headless --dist-dir <path>
v
+-------------------+
| DBTools.Sandbox |
| (net48/net8) |
+-------------------+
|
+-----------------------------+-----------------------------+
| | |
v v v
+---------------+ +------------------+ +------------------+
| DistValidator | | ManifestValidator| | ToolWindowValidator|
+---------------+ +------------------+ +------------------+
| | |
| - Theme validation | - Manifest parsing | - Window instantiation
| - Core window tests | - ModuleType checks | - Design-time VM binding
| - Merge validation | - RibbonTool validation | - Tab interaction tests
| - Binding error capture | - Assembly resolution | - Row expansion tests
v v v
+---------------+ +------------------+ +------------------+
| WindowGhost | | Assembly | | DbtSandboxCatalog|
| Validator | | MetadataInspector| | (Discovery) |
+---------------+ +------------------+ +------------------+
Source:
build/BuildTargets.cs:1080-1189
Key Components
DistValidator
The main orchestrator that coordinates all validation steps. It:
- Resolves and validates the dist directory structure
- Ensures the validator runtime matches the dist target (net48 for Revit 2024, net8 for 2025+)
- Installs assembly resolvers for dist and Revit dependencies
- Activates sandbox mode to enable design-time ViewModels
- Runs theme, core window, manifest, and tool validations
Source:
src/DBTools.Sandbox/Validation/DistValidator.cs:15-327
Required Dist Layout
The validator expects these files in the dist directory:
| File | Purpose |
|---|---|
DBTools.Loader.dll |
Add-in entry point loader |
DBTools.dll |
Main merged assembly |
DBTools.Themes.dll |
Theme resource dictionaries |
DBTools.HandyControl.dll |
UI control library |
Source:
src/DBTools.Sandbox/Validation/DistValidator.cs:149-167
ManifestValidator
Validates that all tool manifests are correctly formed and that their referenced types exist:
- Loads manifest entries via
DbtToolManifestLoader - Verifies
moduleTypederives fromDbtToolModule - Validates
commandTypeimplementsIExternalCommand - Validates
availabilityTypeimplementsIExternalCommandAvailability - Uses metadata inspection (not runtime loading) to avoid Revit API dependencies
Source:
src/DBTools.Sandbox/Validation/ManifestValidator.cs:10-178
ToolWindowValidator
Validates tool UI by instantiating windows with design-time ViewModels:
- Discovers sandbox windows via
DbtSandboxCatalog - Instantiates each window type and its design-time ViewModel
- Validates window layout via
WindowGhostValidator - Tests interactive behaviors:
- Tab cycling: Switches through all tabs in
TabControl - Row expansion: Toggles
DataGridrow details visibility - Preview modes: Cycles through preview modes for tools like SGT
- Tab cycling: Switches through all tabs in
Source:
src/DBTools.Sandbox/Validation/ToolWindowValidator.cs:13-369
WindowGhostValidator
Performs "ghost" window validation by forcing layout passes without showing the window:
- Fixed size pass: Measures/arranges at 800x600 (typical window size)
- Infinite size pass: Catches controls that crash calculating "desired" size
- Content validation: Separately validates the window's content element
- Error localization: Walks visual tree to identify the failing element
Source:
src/DBTools.Sandbox/Validation/WindowGhostValidator.cs:9-129
BindingErrorListener
Captures WPF data binding errors that normally fail silently:
// Listens to WPF's binding trace source
PresentationTraceSources.DataBindingSource.Listeners.Add(listener);
PresentationTraceSources.DataBindingSource.Switch.Level = SourceLevels.Error;
These silent failures are a major source of "ghost UI" issues - buttons that don't work, missing text, broken controls. The listener converts them into hard build failures.
Source:
src/DBTools.Sandbox/Validation/BindingErrorListener.cs:13-118
MergeValidator
Validates the assembly merge/embedding strategy differs by target framework:
net48 (ILRepack merge):
- Verifies sentinel types from merged assemblies exist in
DBTools.dll - Checks that assemblies like
DynamicData.dlldon't exist separately - Validates internalization exclusions preserved required public types
net8 (Embedded payloads):
- Verifies
DBTools.EmbeddedAssemblies.DBTools.Core.dllresource exists - Checks resource streams are loadable and have reasonable size
Source:
src/DBTools.Sandbox/Validation/MergeValidator.cs:17-222
Sandbox Window Configuration
Tools opt into sandbox validation by adding sandboxWindows entries to their manifest.yml:
# Example: src/Tools/Common/GM/manifest.yml
id: DBTools.GM
assembly: DBTools
moduleType: DBTools.GM.GmToolModule
order: 0
sandboxWindows:
- id: DBTools.GM.Main
displayName: "Global Mapper"
group: "Common"
windowType: "DBTools.GM.Shell.UI.Views.GmWindow"
designTimeViewModelType: "DBTools.GM.Shell.DesignTime.GmShellDesignTimeViewModel"
- id: DBTools.GM.CommitReview
displayName: "Global Mapper - Commit Review"
group: "Common"
windowType: "DBTools.GM.Shell.Views.CommitReviewWindow"
designTimeViewModelType: "DBTools.GM.Shell.DesignTime.CommitReviewDesignTimeViewModel"
Source:
src/Tools/Common/GM/manifest.yml:1-27
Required Fields
| Field | Description |
|---|---|
id |
Unique identifier for the sandbox entry |
displayName |
Human-readable name shown in the sandbox gallery |
group |
Category for grouping related windows |
windowType |
Fully-qualified type name of the WPF Window |
designTimeViewModelType |
ViewModel providing mock data for validation |
assembly |
(Optional) Assembly containing the types; defaults to manifest's assembly |
Source:
src/DBTools.Core/Tools/DbtSandboxCatalog.cs:24-46
Discovery Mechanism
The DbtSandboxCatalog discovers sandbox windows by:
- Loading manifests embedded as resources in
DBTools.dll - Parsing
sandboxWindowsentries from each manifest - Validating required fields and uniqueness of IDs
- Building
DbtSandboxWindowSpecobjects for runtime use
public static IReadOnlyList<DbtSandboxWindowSpec> Discover(Assembly rootAssembly)
{
var entries = DbtToolManifestLoader.LoadEntries(rootAssembly);
var specs = new List<DbtSandboxWindowSpec>();
foreach (var entry in entries)
{
foreach (var window in entry.Manifest.SandboxWindows ?? new List<>())
{
// Validate and create spec...
specs.Add(new DbtSandboxWindowSpec(...));
}
}
return specs;
}
Source:
src/DBTools.Core/Tools/DbtSandboxCatalog.cs:8-59
SandboxMode Activation
SandboxMode is a global flag that tells tool windows they're running outside Revit:
namespace DBTools.Core.Compat;
public static class SandboxMode
{
private static bool _isActive;
public static bool IsActive => _isActive;
public static void Activate() => _isActive = true;
}
Windows check this flag to:
- Skip Revit API calls
- Use design-time ViewModels
- Disable features requiring document context
- Skip
UIErrorProtection.Attachin Loaded handlers
Source:
src/DBTools.Core/Compat/SandboxMode.cs:1-24
The SandboxModeActivator ensures the flag is set in all loaded assembly copies (handles ILMerge scenarios where multiple SandboxMode types exist):
Source:
src/DBTools.Sandbox/Validation/SandboxModeActivator.cs:9-119
Build Pipeline Integration
The ValidateDist target in NUKE runs the validator:
Target ValidateDist => _ => _
.Description("Validate dist outputs via DBTools.Sandbox headless runner")
.DependsOn(BuildSandbox)
.After(PromoteToDist)
.OnlyWhenDynamic(() => Configuration == "Release")
.OnlyWhenDynamic(() => !SkipValidation)
.Executes(() =>
{
foreach (var year in RevitYears)
{
var tfm = year == 2024 ? "net48" : "net8.0-windows";
var validatorExe = ArtifactsDir / "sandbox" / Configuration / tfm / "DBTools.Sandbox.exe";
var args = new List<string>
{
"--headless",
"--dist-dir", distDir,
ValidateManifests ? "--validate-manifests" : "--skip-validate-manifests",
ValidateTools ? "--validate-tools" : "--skip-validate-tools"
};
// Execute with 3-minute timeout...
}
});
Source:
build/BuildTargets.cs:1080-1189
Command-Line Options
| Flag | Description |
|---|---|
--headless |
Run in validation mode (no UI) |
--dist-dir <path> |
Path to dist year folder (e.g., .artifacts/dist/Release/2026) |
--validate-manifests / --skip-validate-manifests |
Toggle manifest validation |
--validate-tools / --skip-validate-tools |
Toggle tool UI validation |
--screenshot --tool-id <id> --output <path> |
Capture tool window screenshot |
--list |
List available tool IDs for screenshot mode |
Source:
src/DBTools.Sandbox/Validation/SandboxValidateOptions.cs:7-113
Validation Checks Summary
Always Run (No Skip Flags)
| Check | Description |
|---|---|
| Dist layout | Required files exist |
| Runtime match | net48 validator for 2024, net8 for 2025+ |
| Theme validation | DbtThemeValidator.ValidateOrThrow() |
| Core windows | AlertWindow, SettingsWindow, LoggerWindow |
| Binding errors | Silent WPF binding failures |
| Merge/embed | ILRepack (net48) or embedded payload (net8) |
With --validate-manifests
| Check | Description |
|---|---|
| Manifest parsing | YAML structure, required keys |
| ModuleType | Derives from DbtToolModule |
| RibbonTool commandType | Implements IExternalCommand |
| RibbonTool availabilityType | Implements IExternalCommandAvailability |
With --validate-tools
| Check | Description |
|---|---|
| Window instantiation | Parameterless constructor works |
| ViewModel binding | DataContext assignment succeeds |
| Layout passes | Fixed and infinite size measure/arrange |
| Tab cycling | All tabs can be selected |
| Row expansion | DataGrid row details toggle |
| Preview modes | Mode switching (e.g., SGT elevation/section/3D) |
Troubleshooting Validation Failures
"Window layout validation failed"
The window crashed during Measure, Arrange, or UpdateLayout. Common causes:
- Missing StaticResource: A resource key referenced in XAML doesn't exist in the merged resource dictionaries
- Constructor throws: The window constructor requires Revit context
- Binding converter crash: A value converter throws when given design-time data
Solution: Check the exception message for the failing element name. Look for XAML bindings or resources that assume runtime context.
"WPF binding errors detected"
Silent binding failures were captured. The error message lists each failure:
System.Windows.Data Error: 40 : BindingExpression path error:
'PropertyName' property not found on 'object' 'DesignTimeViewModel'
Solution: Ensure design-time ViewModels expose all properties the XAML binds to.
"ILRepack merge validation failed"
Types that should have been merged exist as separate DLLs, or expected types are missing.
Solution: Check build/ilrepack.txt for the merge whitelist configuration.
"Sandbox window '' failed to instantiate"
The window or ViewModel couldn't be created. Check:
- Does the window have a parameterless constructor?
- Does the ViewModel have a parameterless constructor?
- Is
SandboxMode.IsActivechecked before Revit API calls?
Orphaned Validator Processes
The build system kills orphaned DBTools.Sandbox.exe processes before validation to prevent stale state:
Source:
build/BuildTargets.cs:1222-1244
If validation hangs, check Task Manager for zombie sandbox processes.
Related Documentation
- Test Pipeline - How tests use sandbox infrastructure
- Build Pipeline - Overall build and validation flow
- ILRepack & Embedding - Assembly merge strategy validated here
Documentation Status: Complete
Last Updated: January 2026