DBTools.Loader
Purpose: Revit entry point assembly that bootstraps DBTools.
Output: DBTools.Loader.dll
Target Frameworks: net48, net8.0-windows
Overview
DBTools.Loader is the critical bootstrap component that Revit loads directly via the .addin manifest. Its sole responsibility is to load DBTools.dll (the main application assembly) and install the assembly resolver infrastructure that handles embedded dependencies.
This project is intentionally minimal to reduce startup failure risk. All application logic lives in DBTools.App; the loader exists only to:
- Find and load
DBTools.dll - Install the
EmbeddedAssemblyResolver - Forward Revit lifecycle calls to
DBTools.App.AddinEntry
Source:
src/DBTools.Loader/Addin/AddinEntry.cs:24
Responsibilities
| Responsibility | Implementation |
|---|---|
| Revit entry point | IExternalApplication implementation (decorated with [AppLoader] for AppLoader discovery) |
| Load main assembly | LoadMainAssembly() loads DBTools.dll from deployed directory |
| Install assembly resolver | EmbeddedAssemblyResolver.Install() handles runtime resolution |
| Pre-load critical assemblies | net48 only: Configuration and Serilog assemblies pre-loaded to beat GAC |
| Forward lifecycle | OnStartup/OnShutdown delegated to inner DBTools.App.AddinEntry |
Source:
src/DBTools.Loader/Addin/AddinEntry.cs:17
Key Components
AddinEntry (Revit Entry Point)
The AddinEntry class is the Revit-facing entry point. It implements IExternalApplication and is decorated with [AppLoader] from ricaun.Revit.UI so AppLoader can discover and invoke it. Hot-reload/unload semantics only apply on net8 builds (Revit 2025+).
[AppLoader]
public sealed class AddinEntry : IExternalApplication
{
private IExternalApplication? _inner;
public Result OnStartup(UIControlledApplication application)
{
// Bootstrap sequence here...
}
}
Source:
src/DBTools.Loader/Addin/AddinEntry.cs:17-20
Key Fields:
_inner: Stores the instantiatedDBTools.App.AddinEntryfor forwarding lifecycle calls
Source:
src/DBTools.Loader/Addin/AddinEntry.cs:22
EmbeddedAssemblyResolver
Static class that installs hooks into the CLR's assembly resolution mechanism. Handles loading dependencies from embedded resources within DBTools.dll.
internal static class EmbeddedAssemblyResolver
{
private const string ResourcePrefix = "DBTools.EmbeddedAssemblies.";
private static int _installed;
private static string? _deployedDir;
public static void Install(Assembly mainAssembly, string? deployedDir = null)
{
// Installs AssemblyResolve (net48) or ALC.Resolving (net8) handler
}
}
Source:
src/DBTools.Loader/AssemblyResolution/EmbeddedAssemblyResolver.cs:14-38
LoaderCompat
Provides cross-TFM compatibility utilities for the loader. Abstracts differences between .NET Framework and .NET 8.
internal static class LoaderCompat
{
internal static string VendorTfm =>
#if NET8_0_OR_GREATER
"net8.0-windows";
#else
"net48";
#endif
internal static Assembly LoadAssemblyFromPath(string path)
{
#if NET8_0_OR_GREATER
return AssemblyLoadContext.Default.LoadFromAssemblyPath(path);
#else
return Assembly.LoadFrom(path);
#endif
}
}
Source:
src/DBTools.Loader/Compat/LoaderCompat.cs:10-35
SharedParametersDeployer
Deploys the embedded shared parameters file to %APPDATA%/DBTools/ on first run. This file defines Revit shared parameters used by DBTools features.
internal static class SharedParametersDeployer
{
private const string ResourceName = "DBTools.sharedparameters.txt";
public static void EnsureDeployed()
{
// Copies embedded resource to %APPDATA%/DBTools/DBTools.sharedparameters.txt
// Skips if file already exists
}
}
Source:
src/DBTools.Loader/Addin/SharedParametersDeployer.cs:8-49
Behavior:
- Creates
%APPDATA%/DBTools/directory if needed - Skips deployment if file already exists (idempotent)
- Logs errors to
%LOCALAPPDATA%/DBTools/Logs/Loader.logon failure - Best-effort: failures don't prevent application startup (installer also deploys this file)
Bootstrap Sequence
OnStartup Flow
The complete startup sequence with line numbers:
1. Validate application parameter
> Source: AddinEntry.cs:19
2. Get deployed directory (where DBTools.Loader.dll lives)
> Source: AddinEntry.cs:21
3. Load DBTools.dll from deployed directory
> Source: AddinEntry.cs:22
4. Verify embedded payload is present
> Source: AddinEntry.cs:23
5. Install EmbeddedAssemblyResolver
> Source: AddinEntry.cs:24
6. [net48 only] Pre-load Configuration assemblies
> Source: AddinEntry.cs:26
7. [net48 only] Pre-load Serilog assemblies
> Source: AddinEntry.cs:27
8. Resolve DBTools.App.AddinEntry type via reflection
> Source: AddinEntry.cs:30-31
9. Create instance of DBTools.App.AddinEntry
> Source: AddinEntry.cs:33-34
10. Store instance and forward OnStartup call
> Source: AddinEntry.cs:36-37
Source:
src/DBTools.Loader/Addin/AddinEntry.cs:17-38
Sequence Diagram
Revit DBTools.Loader DBTools.dll (DBTools.App)
| | |
|--OnStartup(app)----------->| |
| | |
| GetDeployedDirectory() |
| LoadMainAssembly() |
| |--------load-------------->|
| | |
| EnsureEmbeddedPayloadPresent() |
| EmbeddedAssemblyResolver.Install()|
| | |
| [net48: PreloadEmbedded*()] |
| | |
| Activator.CreateInstance()------->|
| | |
| |------OnStartup(app)------>|
| | |
|<-------Result.Succeeded----|<-------Result-------------|
Assembly Loading Strategy
net48 Strategy (ILRepack + Embedded)
On .NET Framework 4.8, dependencies are handled via two mechanisms:
1. ILRepack (Merged into DBTools.dll):
- DBTools.Core
- Serilog, Microsoft.Extensions., YamlDotNet, System. polyfills
- These types exist directly in DBTools.dll; no resolution needed
2. Embedded Resources (for ricaun.Revit.*):
- Stored as
DBTools.EmbeddedAssemblies.<name>.dll - Loaded via
Assembly.Load(byte[])at runtime - ILRepack corrupts some IL, so these stay as resources
Source:
src/DBTools.Loader/AssemblyResolution/EmbeddedAssemblyResolver.cs:223-263
3. Pre-loaded Assemblies:
Configuration assemblies are pre-loaded before any code uses them to ensure DBTools' versions load before GAC/Revit's conflicting versions:
var configAssemblies = new[]
{
"Microsoft.Extensions.Primitives",
"Microsoft.Extensions.FileProviders.Abstractions",
"Microsoft.Extensions.FileProviders.Physical",
"Microsoft.Extensions.Configuration.Abstractions",
"Microsoft.Extensions.Configuration",
"Microsoft.Extensions.Configuration.FileExtensions",
"Microsoft.Extensions.Configuration.Json",
"Microsoft.Extensions.Configuration.Binder"
};
Source:
src/DBTools.Loader/Addin/AddinEntry.cs:111-121
Serilog assemblies are also pre-loaded in dependency order:
var serilogAssemblies = new[]
{
"Microsoft.Extensions.Logging.Abstractions",
"Microsoft.Extensions.DependencyInjection.Abstractions",
"Microsoft.Extensions.Logging",
"Serilog",
"Serilog.Extensions.Logging",
"Serilog.Enrichers.Thread",
"Serilog.Sinks.File"
};
Source:
src/DBTools.Loader/Addin/AddinEntry.cs:154-170
Why Pre-load?
On net48, AssemblyResolve only fires when an assembly cannot be found. If the GAC or another add-in already loaded a different version, our resolver never runs. Pre-loading ensures our versions are loaded first.
Source:
src/DBTools.Loader/Addin/AddinEntry.cs:104-108
net8.0-windows Strategy (All Embedded)
On .NET 8, all dependencies are embedded as resources and loaded via AssemblyLoadContext:
alc.Resolving += (context, name) => ResolveNet8(context, mainAssembly, name);
Source:
src/DBTools.Loader/AssemblyResolution/EmbeddedAssemblyResolver.cs:33-34
Resolution loads from embedded resources via stream:
using var stream = mainAssembly.GetManifestResourceStream(resourceName);
if (stream != null)
{
using var ms = new MemoryStream();
stream.CopyTo(ms);
ms.Position = 0;
return alc.LoadFromStream(ms);
}
Source:
src/DBTools.Loader/AssemblyResolution/EmbeddedAssemblyResolver.cs:168-176
WPF Assembly Fallback
WPF resource assemblies that use pack:// URIs must be loaded from files because URI resolution requires Assembly.Location.
In DBTools, this includes DBTools.HandyControl (vendored) and DBTools.Themes.
Search Order:
%APPDATA%/DBTools/vendor/<type>/<tfm>/(traditional install)- Deployed directory (next to DBTools.Loader.dll)
Source:
src/DBTools.Loader/AssemblyResolution/EmbeddedAssemblyResolver.cs:101-129
Vendor Type Detection:
private static string? GetVendorTypeForAssembly(string requestedName)
{
if (requestedName.Equals("DBTools.HandyControl", ...))
return "handycontrol";
return null;
}
Source:
src/DBTools.Loader/AssemblyResolution/EmbeddedAssemblyResolver.cs:131-139
Duplicate Load Prevention
Both resolution paths check for already-loaded assemblies first to prevent type identity mismatches:
// Critical for ricaun.Revit.* which AppLoader may have already loaded
var alreadyLoaded = AppDomain.CurrentDomain.GetAssemblies()
.FirstOrDefault(a => string.Equals(a.GetName().Name, requestedName, ...));
if (alreadyLoaded != null)
return alreadyLoaded;
Source:
src/DBTools.Loader/AssemblyResolution/EmbeddedAssemblyResolver.cs:50-61
Dependencies
NuGet Packages
| Package | Purpose |
|---|---|
| ricaun.Revit.UI | Provides [AppLoader] attribute for enhanced Revit loading |
Source:
src/DBTools.Loader/DBTools.Loader.csproj:20
Note: ricaun.Revit.UI is a compile-time dependency that ensures ricaun is loaded EARLY when Revit loads DBTools.Loader.dll. This is required for RevitTaskService to work correctly in DBTools.App.
Source:
src/DBTools.Loader/DBTools.Loader.csproj:16-18
Revit API References
Conditional based on target framework:
net48:
<Reference Include="RevitAPI">
<HintPath>$(REVIT2024_DIR)\RevitAPI.dll</HintPath>
<Private>false</Private>
</Reference>
Source:
src/DBTools.Loader/DBTools.Loader.csproj:23-34
net8.0-windows:
<Reference Include="RevitAPI" Condition="'$(REVIT_NET8_DIR)'!=''">
<HintPath>$(REVIT_NET8_DIR)\RevitAPI.dll</HintPath>
<Private>false</Private>
</Reference>
Source:
src/DBTools.Loader/DBTools.Loader.csproj:36-47
Runtime Dependencies
- DBTools.dll - Must be present in same directory as DBTools.Loader.dll
Source:
src/DBTools.Loader/Addin/AddinEntry.cs:67-69
Error Handling
Startup Errors
The loader has minimal error handling - it must either succeed or fail fast:
Missing DBTools.dll:
if (!File.Exists(mainPath))
throw new FileNotFoundException("DBTools.dll not found next to DBTools.Loader.dll.", mainPath);
Source:
src/DBTools.Loader/Addin/AddinEntry.cs:68-69
Missing Embedded Payload (net8 only):
var hasCore = Array.Exists(resources, r =>
string.Equals(r, "DBTools.EmbeddedAssemblies.DBTools.Core.dll", ...));
if (!hasCore)
{
throw new InvalidOperationException(
"DBTools.dll is missing embedded dependencies...");
}
Source:
src/DBTools.Loader/Addin/AddinEntry.cs:89-96
Type Resolution Failure:
var type = assembly.GetType("DBTools.App.AddinEntry", throwOnError: true)
?? throw new InvalidOperationException("DBTools.App.AddinEntry type not found.");
Source:
src/DBTools.Loader/Addin/AddinEntry.cs:30-31
Silent Failures (Intentional)
Pre-load failures during net48 startup are logged to Debug output but don't fail startup:
catch (Exception ex)
{
// Silent continue - the EmbeddedAssemblyResolver will handle on-demand loading
System.Diagnostics.Debug.WriteLine($"[DBTools.Loader] Preload failed for {name}: {ex.Message}");
}
Source:
src/DBTools.Loader/Addin/AddinEntry.cs:192-196
This is acceptable because the EmbeddedAssemblyResolver can still load these assemblies on-demand if pre-loading fails.
Build Configuration
Project Properties
<TargetFrameworks>$(DBT_RevitTargetFrameworks)</TargetFrameworks>
<AssemblyName>DBTools.Loader</AssemblyName>
<RootNamespace>DBTools.Loader</RootNamespace>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
Source:
src/DBTools.Loader/DBTools.Loader.csproj:3-13
CopyLocalLockFileAssemblies: Set to true to copy NuGet dependencies (ricaun) to output for AppLoader/Debug scenarios.
Source:
src/DBTools.Loader/DBTools.Loader.csproj:13
Suppressed Warnings
<NoWarn>$(NoWarn);MSB3277</NoWarn>
MSB3277 (assembly version conflicts) is suppressed because Revit host references surface unavoidable conflicts.
Source:
src/DBTools.Loader/DBTools.Loader.csproj:10-11
File Structure
src/DBTools.Loader/
+-- DBTools.Loader.csproj
+-- Addin/
| +-- AddinEntry.cs # IExternalApplication entry point
| +-- SharedParametersDeployer.cs # Deploys shared parameters file to %APPDATA%
+-- Assets/
| +-- DBTools.sharedparameters.txt # Embedded shared parameters definition
+-- AssemblyResolution/
| +-- EmbeddedAssemblyResolver.cs # Runtime assembly loading
+-- Compat/
+-- LoaderCompat.cs # Cross-TFM utilities
Integration with AppLoader
DBTools.Loader uses ricaun's [AppLoader] attribute so AppLoader can discover and invoke the loader entry point.
On net8 builds (Revit 2025+), AppLoader can additionally provide:
- Automatic Revit version detection
- Enhanced error reporting
- Integration with ricaun.RevitTest for automated testing
- Hot-reload support on Revit 2025+ when loaded through AppLoader's net8 host
The AppLoader may have already loaded shared assemblies (like ricaun.Revit.*). The resolver explicitly checks for already-loaded assemblies to prevent duplicate loading which would cause type identity mismatches.
Source:
src/DBTools.Loader/AssemblyResolution/EmbeddedAssemblyResolver.cs:51-53
Hot-Reload (Revit 2025+ / net8) and AssemblyLoadContext Rules
On Revit 2025+ (net8), AppLoader hot-reload relies on unloading and recreating a collectible AssemblyLoadContext (ALC).
For hot-reload to work, DBTools must avoid loading or resolving DBTools.* assemblies/types outside the current ALC (for example, via process-wide AppDomain scans or Assembly.Load(...)).
DBTools enforces this by:
- Loading the main assembly (
DBTools.dll) into the same ALC asDBTools.Loader.dll - Loading/looking up
DBTools.*assemblies in an ALC-scoped way when resolving tool assemblies or fallback type resolution
Source:
src/DBTools.Loader/Addin/AddinEntry.cs:17
Source:src/DBTools.Core/Tools/DbtToolAssemblyLoader.cs:39
Source:src/DBTools.Core/Tools/DbtTypeResolver.cs:35
Source:src/DBTools.App/Bootstrap/AppBootstrapper.cs:331
For development reload stability, point AppLoader at a single-year output directory:
.artifacts/dist/Release/2025.artifacts/dist/Release/2026
Do not watch the multi-year parent (.artifacts/dist/Release) because DBTools uses the same add-in identity across years and should only be loaded once per Revit session.
Source:
build/BuildTargets.cs:857-860Source:build/Build.cs:555-599Source:build/ArtifactManagement.cs:50-76
Troubleshooting
"DBTools.dll not found" Error
Cause: DBTools.dll is not in the same directory as DBTools.Loader.dll.
Solution: Ensure the deployment copies both files together.
"Missing embedded dependencies" Error (net8)
Cause: DBTools.dll was built without the embedded assembly step.
Solution: Rebuild using bash build.sh BuildAll.
Source:
src/DBTools.Loader/Addin/AddinEntry.cs:93-96
Assembly Resolution Failures
Symptoms: FileNotFoundException or TypeLoadException for dependency types.
Diagnosis:
- Check if the assembly is in embedded resources (
DBTools.EmbeddedAssemblies.<name>.dll) - For WPF assemblies, check
%APPDATA%/DBTools/vendor/or deployed directory - Enable assembly binding logging (
FUSLOGVW.exe) on net48
Related Documentation
- Architecture Overview - High-level system architecture
- Project References - How projects relate
- DBTools.Core - Core library documentation
Verification Status
| Check | Status |
|---|---|
| Source anchors for all claims | Yes |
| UNVERIFIED markers where needed | Yes (addin manifest) |
| Cross-references added | Yes |
| Examples tested | N/A |
| No assumptions without evidence | Yes |
Verified by: docs-run-20260124-020412
Date: 2026-01-24