Table of Contents

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:

  1. Headless mode (--headless): Used by the build pipeline for automated validation
  2. 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:

  1. Resolves and validates the dist directory structure
  2. Ensures the validator runtime matches the dist target (net48 for Revit 2024, net8 for 2025+)
  3. Installs assembly resolvers for dist and Revit dependencies
  4. Activates sandbox mode to enable design-time ViewModels
  5. 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:

  1. Loads manifest entries via DbtToolManifestLoader
  2. Verifies moduleType derives from DbtToolModule
  3. Validates commandType implements IExternalCommand
  4. Validates availabilityType implements IExternalCommandAvailability
  5. 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:

  1. Discovers sandbox windows via DbtSandboxCatalog
  2. Instantiates each window type and its design-time ViewModel
  3. Validates window layout via WindowGhostValidator
  4. Tests interactive behaviors:
    • Tab cycling: Switches through all tabs in TabControl
    • Row expansion: Toggles DataGrid row details visibility
    • Preview modes: Cycles through preview modes for tools like SGT

Source: src/DBTools.Sandbox/Validation/ToolWindowValidator.cs:13-369

WindowGhostValidator

Performs "ghost" window validation by forcing layout passes without showing the window:

  1. Fixed size pass: Measures/arranges at 800x600 (typical window size)
  2. Infinite size pass: Catches controls that crash calculating "desired" size
  3. Content validation: Separately validates the window's content element
  4. 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.dll don't exist separately
  • Validates internalization exclusions preserved required public types

net8 (Embedded payloads):

  • Verifies DBTools.EmbeddedAssemblies.DBTools.Core.dll resource 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:

  1. Loading manifests embedded as resources in DBTools.dll
  2. Parsing sandboxWindows entries from each manifest
  3. Validating required fields and uniqueness of IDs
  4. Building DbtSandboxWindowSpec objects 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.Attach in 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:

  1. Missing StaticResource: A resource key referenced in XAML doesn't exist in the merged resource dictionaries
  2. Constructor throws: The window constructor requires Revit context
  3. 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:

  1. Does the window have a parameterless constructor?
  2. Does the ViewModel have a parameterless constructor?
  3. Is SandboxMode.IsActive checked 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.


Documentation Status: Complete

Last Updated: January 2026