Skip to main content

Command Palette

Search for a command to run...

How to Use AssemblyLoadContext for Isolating Vault Addin Dependencies

Solve version conflicts once and for all.

Updated
15 min read
How to Use AssemblyLoadContext for Isolating Vault Addin Dependencies

Autodesk Vault previously allowed add-ins to use NuGet package/assembly versions that differ from the ones bundled with Vault. However, with Autodesk Vault 2026 and the framework upgrade to .NET 8, this no longer worked out of the box and caused version conflicts.

This article will demonstrate one way to resolve the version conflict by isolating the add-in’s dependencies in a separate container using AssemblyLoadContext.

A full sample application can be found on GitHub.

What is AssemblyLoadContext?

  • It's the runtime's provider for locating and loading dependencies. Whenever a dependency is loaded, an AssemblyLoadContext instance is invoked to locate it.
  • AssemblyLoadContext provides a service of locating, loading, and caching managed assemblies and other dependencies.
  • To support dynamic code loading and unloading, it creates an isolated context for loading code and its dependencies in their own AssemblyLoadContext instance.

How to update the add-in?

Add IsolatedIExplorerExtension.cs

This is used as the new entry point into the add-in. It is a wrapper around the IExplorerExtension. It adds additional functionality behind the scenes but still exposes all methods called automatically by Vault.

  • OnStartup(IApplication application) => Startup()
  • OnLogOn(IApplication application) => LogOn()
  • OnLogOff(IApplication application) => LogOff()
  • OnShutdown(IApplication application) => Shutdown()
  • CommandSites() => OnCommandSites()
  • CustomEntityHandlers() => OnCustomEntityHandlers()
  • DetailTabs() => OnDetailTabs()
  • DockPanels() => OnDockPanels() (For Vault 2026+)
  • HiddenCommands() => OnHiddenCommands()
using Autodesk.Connectivity.Explorer.Extensibility;
using System.ComponentModel;

namespace IsolatedVaultAddin.Isolation;

/// <summary>
///    <see cref="IExplorerExtension" /> is the entry point of the Vault addin.
///    This class is extended to have fully isolated addin dependency container.
///    Inherit this class and add custom logic to overrides of <see cref="Startup" />, <see cref="LogOn" />, <see cref="LogOff" />, <see cref="Shutdown" />, <see cref="OnCommandSites" />, <see cref="OnCustomEntityHandlers" />, <see cref="OnDetailTabs" />, <see cref="OnDockPanels" />, or <see cref="OnHiddenCommands" />.
/// </summary>
public abstract class IsolatedIExplorerExtension : IExplorerExtension
{
#if NETCOREAPP
    private object? _isolatedInstance;
#endif

    /// <summary>
    ///    Reference to the parameter in <see cref="IExplorerExtension.OnStartup" />, <see cref="IExplorerExtension.OnLogOn" />, <see cref="IExplorerExtension.OnLogOff" />, <see cref="IExplorerExtension.OnShutdown" />.
    /// </summary>
    public IApplication Application { get; private set; } = default!;

    [EditorBrowsable(EditorBrowsableState.Never)]
    public void OnStartup(IApplication application)
    {
        Type currentType = GetType();

#if NETCOREAPP
        if (AddinLoadContext.CheckIfCustomContext(currentType) is false)
        {
            AddinLoadContext dependenciesProvider = AddinLoadContext.GetDependenciesProvider(currentType);
            _isolatedInstance = dependenciesProvider.CreateAssemblyInstance(currentType);

            AddinLoadContext.Invoke(_isolatedInstance, nameof(OnStartup), application);
            return;
        }
#endif

        Application = application;

#if NETCOREAPP
        Startup();
#else
        try
        {
            ResolveHelper.BeginAssemblyResolve(currentType);
            Startup();
        }
        finally
        {
            ResolveHelper.EndAssemblyResolve();
        }
#endif
    }

    [EditorBrowsable(EditorBrowsableState.Never)]
    public void OnLogOn(IApplication application)
    {
        Type currentType = GetType();

#if NETCOREAPP
        if (AddinLoadContext.CheckIfCustomContext(currentType) is false)
        {
            AddinLoadContext dependenciesProvider = AddinLoadContext.GetDependenciesProvider(currentType);
            _isolatedInstance = dependenciesProvider.CreateAssemblyInstance(currentType);

            AddinLoadContext.Invoke(_isolatedInstance, nameof(OnLogOn), application);
            return;
        }
#endif

        Application = application;

#if NETCOREAPP
        LogOn();
#else
        try
        {
            ResolveHelper.BeginAssemblyResolve(currentType);
            LogOn();
        }
        finally
        {
            ResolveHelper.EndAssemblyResolve();
        }
#endif
    }

    [EditorBrowsable(EditorBrowsableState.Never)]
    public void OnLogOff(IApplication application)
    {
        Type currentType = GetType();

#if NETCOREAPP
        if (AddinLoadContext.CheckIfCustomContext(currentType) is false)
        {
            AddinLoadContext dependenciesProvider = AddinLoadContext.GetDependenciesProvider(currentType);
            _isolatedInstance = dependenciesProvider.CreateAssemblyInstance(currentType);

            AddinLoadContext.Invoke(_isolatedInstance, nameof(OnLogOff), application);
            return;
        }
#endif

        Application = application;

#if NETCOREAPP
        LogOff();
#else
        try
        {
            ResolveHelper.BeginAssemblyResolve(currentType);
            LogOff();
        }
        finally
        {
            ResolveHelper.EndAssemblyResolve();
        }
#endif
    }

    [EditorBrowsable(EditorBrowsableState.Never)]
    public void OnShutdown(IApplication application)
    {
        Type currentType = GetType();

#if NETCOREAPP
        if (AddinLoadContext.CheckIfCustomContext(currentType) is false)
        {
            AddinLoadContext dependenciesProvider = AddinLoadContext.GetDependenciesProvider(currentType);
            _isolatedInstance = dependenciesProvider.CreateAssemblyInstance(currentType);

            AddinLoadContext.Invoke(_isolatedInstance, nameof(OnShutdown), application);
            return;
        }
#endif

        Application = application;

#if NETCOREAPP
        Shutdown();
#else
        try
        {
            ResolveHelper.BeginAssemblyResolve(currentType);
            Shutdown();
        }
        finally
        {
            ResolveHelper.EndAssemblyResolve();
        }
#endif
    }

    [EditorBrowsable(EditorBrowsableState.Never)]
    public IEnumerable<CommandSite>? CommandSites()
    {
        Type currentType = GetType();

#if NETCOREAPP
        if (AddinLoadContext.CheckIfCustomContext(currentType) is false)
        {
            AddinLoadContext dependenciesProvider = AddinLoadContext.GetDependenciesProvider(currentType);
            _isolatedInstance = dependenciesProvider.CreateAssemblyInstance(currentType);

            return AddinLoadContext.Invoke<CommandSite>(_isolatedInstance, nameof(CommandSites));
        }
#endif

#if NETCOREAPP
        return OnCommandSites();
#else
        try
        {
            ResolveHelper.BeginAssemblyResolve(currentType);
            return OnCommandSites();
        }
        finally
        {
            ResolveHelper.EndAssemblyResolve();
        }
#endif
    }

    [EditorBrowsable(EditorBrowsableState.Never)]
    public IEnumerable<CustomEntityHandler>? CustomEntityHandlers()
    {
        Type currentType = GetType();

#if NETCOREAPP
        if (AddinLoadContext.CheckIfCustomContext(currentType) is false)
        {
            AddinLoadContext dependenciesProvider = AddinLoadContext.GetDependenciesProvider(currentType);
            _isolatedInstance = dependenciesProvider.CreateAssemblyInstance(currentType);

            return AddinLoadContext.Invoke<CustomEntityHandler>(_isolatedInstance, nameof(CustomEntityHandlers));
        }
#endif

#if NETCOREAPP
        return OnCustomEntityHandlers();
#else
        try
        {
            ResolveHelper.BeginAssemblyResolve(currentType);
            return OnCustomEntityHandlers();
        }
        finally
        {
            ResolveHelper.EndAssemblyResolve();
        }
#endif
    }

    [EditorBrowsable(EditorBrowsableState.Never)]
    public IEnumerable<DetailPaneTab>? DetailTabs()
    {
        Type currentType = GetType();

#if NETCOREAPP
        if (AddinLoadContext.CheckIfCustomContext(currentType) is false)
        {
            AddinLoadContext dependenciesProvider = AddinLoadContext.GetDependenciesProvider(currentType);
            _isolatedInstance = dependenciesProvider.CreateAssemblyInstance(currentType);

            return AddinLoadContext.Invoke<DetailPaneTab>(_isolatedInstance, nameof(DetailTabs));
        }
#endif

#if NETCOREAPP
        return OnDetailTabs();
#else
        try
        {
            ResolveHelper.BeginAssemblyResolve(currentType);
            return OnDetailTabs();
        }
        finally
        {
            ResolveHelper.EndAssemblyResolve();
        }
#endif
    }

#if VAULT_HAS_DOCK_PANELS
    [EditorBrowsable(EditorBrowsableState.Never)]
    public IEnumerable<DockPanel>? DockPanels()
    {
        Type currentType = GetType();

#if NETCOREAPP
        if (AddinLoadContext.CheckIfCustomContext(currentType) is false)
        {
            AddinLoadContext dependenciesProvider = AddinLoadContext.GetDependenciesProvider(currentType);
            _isolatedInstance = dependenciesProvider.CreateAssemblyInstance(currentType);

            return AddinLoadContext.Invoke<DockPanel>(_isolatedInstance, nameof(DockPanels));
        }
#endif

#if NETCOREAPP
        return OnDockPanels();
#else
        try
        {
            ResolveHelper.BeginAssemblyResolve(currentType);
            return OnDockPanels();
        }
        finally
        {
            ResolveHelper.EndAssemblyResolve();
        }
#endif
    }
#endif

    [EditorBrowsable(EditorBrowsableState.Never)]
    public IEnumerable<string>? HiddenCommands()
    {
        Type currentType = GetType();

#if NETCOREAPP
        if (AddinLoadContext.CheckIfCustomContext(currentType) is false)
        {
            AddinLoadContext dependenciesProvider = AddinLoadContext.GetDependenciesProvider(currentType);
            _isolatedInstance = dependenciesProvider.CreateAssemblyInstance(currentType);

            return AddinLoadContext.Invoke<string>(_isolatedInstance, nameof(HiddenCommands));
        }
#endif

#if NETCOREAPP
        return OnHiddenCommands();
#else
        try
        {
            ResolveHelper.BeginAssemblyResolve(currentType);
            return OnHiddenCommands();
        }
        finally
        {
            ResolveHelper.EndAssemblyResolve();
        }
#endif
    }

    /// <summary>
    ///    Overload this method to execute custom logic when the Vault addin is loaded and <see cref="IExplorerExtension.OnStartup" /> method is executed.
    /// </summary>
    public abstract void Startup();

    /// <summary>
    ///    Overload this method to execute custom logic when the Vault is logged into and <see cref="IExplorerExtension.OnLogOn" /> method is executed.
    /// </summary>
    public abstract void LogOn();

    /// <summary>
    ///    Overload this method to execute custom logic when the Vault is logged out of and <see cref="IExplorerExtension.OnLogOff" /> method is executed.
    /// </summary>
    public abstract void LogOff();

    /// <summary>
    ///    Overload this method to execute custom logic when the Vault addin is unloaded and <see cref="IExplorerExtension.OnShutdown" /> method is executed.
    /// </summary>
    public abstract void Shutdown();

    /// <summary>
    ///    Overload this method to execute custom logic when the Vault addin ... and <see cref="IExplorerExtension.CommandSites" /> method is executed.
    /// </summary>
    public abstract IEnumerable<CommandSite>? OnCommandSites();

    /// <summary>
    ///    Overload this method to execute custom logic when the Vault addin ... and <see cref="IExplorerExtension.CustomEntityHandlers" /> method is executed.
    /// </summary>
    public abstract IEnumerable<CustomEntityHandler>? OnCustomEntityHandlers();

    /// <summary>
    ///    Overload this method to execute custom logic when the Vault addin ... and <see cref="IExplorerExtension.DetailTabs" /> method is executed.
    /// </summary>
    public abstract IEnumerable<DetailPaneTab>? OnDetailTabs();

#if VAULT_HAS_DOCK_PANELS
    /// <summary>
    ///    Overload this method to execute custom logic when the Vault addin ... and <see cref="IExplorerExtension.DockPanels" /> method is executed.
    /// </summary>
    public abstract IEnumerable<DockPanel>? OnDockPanels();
#endif    

    /// <summary>
    ///    Overload this method to execute custom logic when the Vault addin ... and <see cref="IExplorerExtension.HiddenCommands" /> method is executed.
    /// </summary>
    public abstract IEnumerable<string>? OnHiddenCommands();
}

Add IsolatedIWebServiceExtension .cs

This is used as the new entry point into the add-in. It is a wrapper around the IWebServiceExtension. It adds additional functionality behind the scenes but still exposes all methods called automatically by Vault.

  • OnLoad() => Load()
using Autodesk.Connectivity.WebServices;
using System.ComponentModel;

namespace IsolatedVaultAddin.Isolation;

/// <summary>
///    <see cref="IWebServiceExtension" /> is the entry point of the Vault addin.
///    This class is extended to have fully isolated addin dependency container.
///    Inherit this class and add custom logic to overrides of <see cref="Load" />.
/// </summary>
public abstract class IsolatedIWebServiceExtension : IWebServiceExtension
{
#if NETCOREAPP
    private object? _isolatedInstance;
#endif

    [EditorBrowsable(EditorBrowsableState.Never)]
    public void OnLoad()
    {
        Type currentType = GetType();

#if NETCOREAPP
        if (AddinLoadContext.CheckIfCustomContext(currentType) is false)
        {
            AddinLoadContext dependenciesProvider = AddinLoadContext.GetDependenciesProvider(currentType);
            _isolatedInstance = dependenciesProvider.CreateAssemblyInstance(currentType);

            AddinLoadContext.Invoke(_isolatedInstance, nameof(OnLoad));
            return;
        }
#endif

#if NETCOREAPP
        Load();
#else
        try
        {
            ResolveHelper.BeginAssemblyResolve(currentType);
            Load();
        }
        finally
        {
            ResolveHelper.EndAssemblyResolve();
        }
#endif
    }

    /// <summary>
    ///    Overload this method to execute custom logic when the Vault addin is loaded and <see cref="IWebServiceExtension.OnLoad" /> method is executed.
    /// </summary>
    public abstract void Load();
}

Add IsolatedIJobHandler.cs

This is used as the new entry point into the add-in. It is a wrapper around the IJobHandler. It adds additional functionality behind the scenes but still exposes all methods called automatically by Vault.

  • Execute(IJobProcessorServices context, IJob job) => OnExecute()
  • CanProcess(string jobType) => OnCanProcess()
  • OnJobProcessorStartup(IJobProcessorServices context) => JobProcessorStartup()
  • OnJobProcessorShutdown(IJobProcessorServices context) => JobProcessorShutdown()
  • OnJobProcessorWake(IJobProcessorServices context) => JobProcessorWake()
  • OnJobProcessorSleep(IJobProcessorServices context) => JobProcessorSleep()
using Autodesk.Connectivity.Explorer.Extensibility;
using Autodesk.Connectivity.JobProcessor.Extensibility;
using System.ComponentModel;

namespace IsolatedVaultAddin.Isolation;

/// <summary>
///    <see cref="IJobHandler" /> is the entry point of the Vault job processor addin.
///    This class is extended to have fully isolated addin dependency container.
///    Inherit this class and add custom logic to overrides of <see cref="OnExecute" />, <see cref="OnCanProcess" />, <see cref="JobProcessorStartup" />, <see cref="JobProcessorShutdown" />, <see cref="JobProcessorWake" />, or <see cref="JobProcessorSleep" />.
/// </summary>
public abstract class IsolatedIJobHandler : IJobHandler
{
#if NETCOREAPP
    private object? _isolatedInstance;
#endif

    /// <summary>
    ///    Reference to the parameter in <see cref="IExplorerExtension.Execute" />, <see cref="IExplorerExtension.OnJobProcessorStartup" />, <see cref="IExplorerExtension.OnJobProcessorShutdown" />, <see cref="IExplorerExtension.OnJobProcessorWake" />, <see cref="IExplorerExtension.OnJobProcessorSleep" />.
    /// </summary>
    public IJobProcessorServices Context { get; private set; } = default!;

    /// <summary>
    ///    Reference to the parameter in <see cref="IExplorerExtension.Execute" />.
    /// </summary>
    public IJob Job { get; private set; } = default!;

    /// <summary>
    ///    Reference to the parameter in <see cref="IExplorerExtension.CanProcess" />.
    /// </summary>
    public string JobType { get; private set; } = default!;

    [EditorBrowsable(EditorBrowsableState.Never)]

    public JobOutcome Execute(IJobProcessorServices context, IJob job)
    {
        Type currentType = GetType();

#if NETCOREAPP
        if (AddinLoadContext.CheckIfCustomContext(currentType) is false)
        {
            AddinLoadContext dependenciesProvider = AddinLoadContext.GetDependenciesProvider(currentType);
            _isolatedInstance = dependenciesProvider.CreateAssemblyInstance(currentType);

            return AddinLoadContext.Invoke(_isolatedInstance, nameof(Execute), context, job);
        }
#endif

        Context = context;
        Job = job;

#if NETCOREAPP
        return OnExecute();
#else
        try
        {
            ResolveHelper.BeginAssemblyResolve(currentType);
            return OnExecute();
        }
        finally
        {
            ResolveHelper.EndAssemblyResolve();
        }
#endif
    }

    [EditorBrowsable(EditorBrowsableState.Never)]
    public bool CanProcess(string jobType)
    {
        Type currentType = GetType();

#if NETCOREAPP
        if (AddinLoadContext.CheckIfCustomContext(currentType) is false)
        {
            AddinLoadContext dependenciesProvider = AddinLoadContext.GetDependenciesProvider(currentType);
            _isolatedInstance = dependenciesProvider.CreateAssemblyInstance(currentType);

            return AddinLoadContext.Invoke(_isolatedInstance, nameof(CanProcess), jobType);
        }
#endif

        JobType = jobType;

#if NETCOREAPP
        return OnCanProcess();
#else
        try
        {
            ResolveHelper.BeginAssemblyResolve(currentType);
            return OnCanProcess();
        }
        finally
        {
            ResolveHelper.EndAssemblyResolve();
        }
#endif
    }

    [EditorBrowsable(EditorBrowsableState.Never)]
    public void OnJobProcessorStartup(IJobProcessorServices context)
    {
        Type currentType = GetType();

#if NETCOREAPP
        if (AddinLoadContext.CheckIfCustomContext(currentType) is false)
        {
            AddinLoadContext dependenciesProvider = AddinLoadContext.GetDependenciesProvider(currentType);
            _isolatedInstance = dependenciesProvider.CreateAssemblyInstance(currentType);

            AddinLoadContext.Invoke(_isolatedInstance, nameof(OnJobProcessorStartup), context);
            return;
        }
#endif

        Context = context;

#if NETCOREAPP
        JobProcessorStartup();
#else
        try
        {
            ResolveHelper.BeginAssemblyResolve(currentType);
            JobProcessorStartup();
        }
        finally
        {
            ResolveHelper.EndAssemblyResolve();
        }
#endif
    }

    [EditorBrowsable(EditorBrowsableState.Never)]
    public void OnJobProcessorShutdown(IJobProcessorServices context)
    {
        Type currentType = GetType();

#if NETCOREAPP
        if (AddinLoadContext.CheckIfCustomContext(currentType) is false)
        {
            AddinLoadContext dependenciesProvider = AddinLoadContext.GetDependenciesProvider(currentType);
            _isolatedInstance = dependenciesProvider.CreateAssemblyInstance(currentType);

            AddinLoadContext.Invoke(_isolatedInstance, nameof(OnJobProcessorShutdown), context);
            return;
        }
#endif

        Context = context;

#if NETCOREAPP
        JobProcessorShutdown();
#else
        try
        {
            ResolveHelper.BeginAssemblyResolve(currentType);
            JobProcessorShutdown();
        }
        finally
        {
            ResolveHelper.EndAssemblyResolve();
        }
#endif
    }

    [EditorBrowsable(EditorBrowsableState.Never)]
    public void OnJobProcessorWake(IJobProcessorServices context)
    {
        Type currentType = GetType();

#if NETCOREAPP
        if (AddinLoadContext.CheckIfCustomContext(currentType) is false)
        {
            AddinLoadContext dependenciesProvider = AddinLoadContext.GetDependenciesProvider(currentType);
            _isolatedInstance = dependenciesProvider.CreateAssemblyInstance(currentType);

            AddinLoadContext.Invoke(_isolatedInstance, nameof(OnJobProcessorWake), context);
            return;
        }
#endif

        Context = context;

#if NETCOREAPP
        JobProcessorWake();
#else
        try
        {
            ResolveHelper.BeginAssemblyResolve(currentType);
            JobProcessorWake();
        }
        finally
        {
            ResolveHelper.EndAssemblyResolve();
        }
#endif
    }

    [EditorBrowsable(EditorBrowsableState.Never)]
    public void OnJobProcessorSleep(IJobProcessorServices context)
    {
        Type currentType = GetType();

#if NETCOREAPP
        if (AddinLoadContext.CheckIfCustomContext(currentType) is false)
        {
            AddinLoadContext dependenciesProvider = AddinLoadContext.GetDependenciesProvider(currentType);
            _isolatedInstance = dependenciesProvider.CreateAssemblyInstance(currentType);

            AddinLoadContext.Invoke(_isolatedInstance, nameof(OnJobProcessorSleep), context);
            return;
        }
#endif

        Context = context;

#if NETCOREAPP
        JobProcessorSleep();
#else
        try
        {
            ResolveHelper.BeginAssemblyResolve(currentType);
            JobProcessorSleep();
        }
        finally
        {
            ResolveHelper.EndAssemblyResolve();
        }
#endif
    }

    /// <summary>
    ///    Overload this method to execute custom logic when the Vault job processor is ... and <see cref="IJobHandler.ExecuteAsync" /> method is executed.
    /// </summary>
    public abstract JobOutcome OnExecute();

    /// <summary>
    ///    Overload this method to execute custom logic when the Vault job processor is ... and <see cref="IJobHandler.CanProcess" /> method is executed.
    /// </summary>
    public abstract bool OnCanProcess();

    /// <summary>
    ///    Overload this method to execute custom logic when the Vault job processor is ... and <see cref="IJobHandler.OnJobProcessorStartup" /> method is executed.
    /// </summary>
    public abstract void JobProcessorStartup();

    /// <summary>
    ///    Overload this method to execute custom logic when the Vault job processor is ... and <see cref="IJobHandler.OnJobProcessorShutdown" /> method is executed.
    /// </summary>
    public abstract void JobProcessorShutdown();

    /// <summary>
    ///    Overload this method to execute custom logic when the Vault job processor is ... and <see cref="IJobHandler.OnJobProcessorWake" /> method is executed.
    /// </summary>
    public abstract void JobProcessorWake();

    /// <summary>
    ///    Overload this method to execute custom logic when the Vault job processor is ... and <see cref="IJobHandler.OnJobProcessorSleep" /> method is executed.
    /// </summary>
    public abstract void JobProcessorSleep();
}

Add AddinLoadContext.cs

This is a custom implementation of the AssemblyLoadContext specifically for the format of a Vault add-in.

#if NETCOREAPP
using Autodesk.Connectivity.Explorer.Extensibility;
using Autodesk.Connectivity.JobProcessor.Extensibility;
using Autodesk.Connectivity.WebServices;
using System.Reflection;
using System.Runtime.Loader;

namespace IsolatedVaultAddin.Isolation;

/// <summary>
///    Isolated addin dependency container.
/// </summary>
/// <remarks>
///    References:
///    <a href="https://learn.microsoft.com/en-us/dotnet/core/tutorials/creating-app-with-plugin-support">
///    Microsoft/Tutorials/Plugins
///    </a>
///    <a href="https://github.com/dotnet/coreclr/blob/v2.1.0/Documentation/design-docs/assemblyloadcontext.md">
///    GitHub/DotNet/CoreCLR
///    </a>
/// </remarks>
internal sealed class AddinLoadContext : AssemblyLoadContext
{
    /// <summary>
    ///   Add-ins contexts storage.
    /// </summary>
    private static readonly Dictionary<string, AddinLoadContext> _dependenciesProviders = new(1);

    private readonly AssemblyDependencyResolver _resolver;

    private const BindingFlags MethodSearchFlags = BindingFlags.Public | BindingFlags.Instance;

    private AddinLoadContext(Type type, string addinName) : base(addinName)
    {
        string addinLocation = type.Assembly.Location;

        _resolver = new AssemblyDependencyResolver(addinLocation);
    }

    /// <summary>
    ///   Resolve and load dependency any time one is loaded if it exists in the isolated addin dependency container.
    /// </summary>
    protected override Assembly? Load(AssemblyName assemblyName)
    {
        string? assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName);

        return assemblyPath is not null
             ? LoadFromAssemblyPath(assemblyPath)
             : null;
    }

    /// <summary>
    ///   Resolve and load unmanaged native dependency any time one is loaded if it exists in the isolated addin dependency container.
    /// </summary>
    protected override nint LoadUnmanagedDll(string assemblyName)
    {
        string? assemblyPath = _resolver.ResolveUnmanagedDllToPath(assemblyName);

        return assemblyPath is not null
             ? LoadUnmanagedDllFromPath(assemblyPath)
             : nint.Zero;
    }

    /// <summary>
    ///   Determine if the <see cref="AssemblyLoadContext" /> is custom or still the default context.
    /// </summary>
    public static bool CheckIfCustomContext(Type type)
    {
        AssemblyLoadContext? currentContext = GetLoadContext(type.Assembly);

        return currentContext != Default;
    }

    /// <summary>
    ///     Get or create a new isolated context for the type.
    /// </summary>
    public static AddinLoadContext GetDependenciesProvider(Type type)
    {
        // Assembly location used as context name and the unique provider key.
        string addinRoot = System.IO.Path.GetDirectoryName(type.Assembly.Location)!;
        if (_dependenciesProviders.TryGetValue(addinRoot, out var provider))
        {
            return provider;
        }

        string addinName = System.IO.Path.GetFileName(addinRoot);
        provider = new AddinLoadContext(type, addinName);
        _dependenciesProviders.Add(addinRoot, provider);

        return provider;
    }

    /// <summary>
    ///   Create new instance in the separated context.
    /// </summary>
    public object CreateAssemblyInstance(Type type)
    {
        string assemblyLocation = type.Assembly.Location;
        Assembly assembly = LoadFromAssemblyPath(assemblyLocation);

        return assembly.CreateInstance(type.FullName!)!;
    }

    /// <summary>
    ///   Execute <see cref="IExplorerExtension.OnStartup" />, <see cref="IExplorerExtension.OnLogOn" />, <see cref="IExplorerExtension.OnLogOff" />, <see cref="IExplorerExtension.OnShutdown" /> methods in the isolated context.
    /// </summary>
    /// <remarks>
    ///   Matches parameter format of <see cref="IExplorerExtension.OnStartup" />, <see cref="IExplorerExtension.OnLogOn" />, <see cref="IExplorerExtension.OnLogOff" />, <see cref="IExplorerExtension.OnShutdown" /> methods.
    /// </remarks>
    public static void Invoke(object instance, string methodName, IApplication application)
    {
        Type instanceType = instance.GetType();

        Type[] methodParameterTypes =
        [
             typeof(IApplication)
        ];

        object[] methodParameters =
        [
             application
        ];

        MethodInfo method = instanceType.GetMethod(methodName, MethodSearchFlags, null, methodParameterTypes, null)!;

        _ = method.Invoke(instance, methodParameters)!;
    }

    /// <summary>
    ///   Execute <see cref="IJobHandler.OnJobProcessorStartup" />, <see cref="IJobHandler.OnJobProcessorShutdown" />, <see cref="IJobHandler.OnJobProcessorWake" />, <see cref="IJobHandler.OnJobProcessorSleep" /> methods in the isolated context.
    /// </summary>
    /// <remarks>
    ///   Matches parameter format of <see cref="IJobHandler.OnJobProcessorStartup" />, <see cref="IJobHandler.OnJobProcessorShutdown" />, <see cref="IJobHandler.OnJobProcessorWake" />, <see cref="IJobHandler.OnJobProcessorSleep" /> methods.
    /// </remarks>
    public static void Invoke(object instance, string methodName, IJobProcessorServices context)
    {
        Type instanceType = instance.GetType();

        Type[] methodParameterTypes =
        [
             typeof(IJobProcessorServices)
        ];

        object[] methodParameters =
        [
             context!
        ];

        MethodInfo method = instanceType.GetMethod(methodName, MethodSearchFlags, null, methodParameterTypes, null)!;

        _ = method.Invoke(instance, methodParameters)!;
    }

    /// <summary>
    ///   Execute <see cref="IJobHandler.Execute" /> method in the isolated context.
    /// </summary>
    /// <remarks>
    ///   Matches parameter format of <see cref="IJobHandler.Execute" /> method.
    /// </remarks>
    public static JobOutcome Invoke(object instance, string methodName, IJobProcessorServices context, IJob job)
    {
        Type instanceType = instance.GetType();

        Type[] methodParameterTypes =
        [
             typeof(IJobProcessorServices),
             typeof(IJob)
        ];

        object[] methodParameters =
        [
             context!,
             job!
        ];

        MethodInfo method = instanceType.GetMethod(methodName, MethodSearchFlags, null, methodParameterTypes, null)!;

        return (JobOutcome)method.Invoke(instance, methodParameters)!;
    }

    /// <summary>
    ///   Execute <see cref="IJobHandler.CanProcess" /> method in the isolated context.
    /// </summary>
    /// <remarks>
    ///   Matches parameter format of <see cref="IJobHandler.CanProcess" /> method.
    /// </remarks>
    public static bool Invoke(object instance, string methodName, string jobType)
    {
        Type instanceType = instance.GetType();

        Type[] methodParameterTypes =
        [
             typeof(string)
        ];

        object[] methodParameters =
        [
             jobType
        ];

        MethodInfo method = instanceType.GetMethod(methodName, MethodSearchFlags, null, methodParameterTypes, null)!;

        return (bool)method.Invoke(instance, methodParameters)!;
    }

    /// <summary>
    ///   Execute <see cref="IWebServiceExtension.OnLoad" /> method in the isolated context.
    /// </summary>
    /// <remarks>
    ///   Matches parameter format of <see cref="IWebServiceExtension.OnLoad" /> method.
    /// </remarks>
    public static void Invoke(object instance, string methodName)
    {
        Type instanceType = instance.GetType();

        MethodInfo method = instanceType.GetMethod(methodName, MethodSearchFlags, null, [], null)!;

        _ = method.Invoke(instance, [])!;
    }

    /// <summary>
    ///   Execute <see cref="IExplorerExtension.CommandSites" /> method in the isolated context.
    /// </summary>
    /// <remarks>
    ///   Matches parameter format of <see cref="IExplorerExtension.CommandSites" /> method.
    /// </remarks>
    public static IEnumerable<T>? Invoke<T>(object instance, string methodName)
    {
        Type instanceType = instance.GetType();

        MethodInfo method = instanceType.GetMethod(methodName, MethodSearchFlags, null, [], null)!;

        return (IEnumerable<T>?)method.Invoke(instance, [])!;
    }
}
#endif

Add ResolveHelper.cs

This is a helper class used to resolve conflicting versions of a dependency.

using System.Reflection;
#if NETCOREAPP
using System.Runtime.Loader;
#endif

namespace IsolatedVaultAddin.Isolation;

/// <summary>
///    Used to resolve conflicting versions of a dependency.
/// </summary>
public static class ResolveHelper
{
    private static string? _moduleDirectory;
    private static object? _domainResolvers;

    /// <summary>
    ///    Subscribes the current domain to resolve dependencies for the type.
    /// </summary>
    /// <typeparam name="T">
    ///    Type, to search for dependencies in the directory where this type is defined.
    /// </typeparam>
    /// <remarks>
    ///    Dependencies are searched in a directory of the specified type.
    ///    At the time of dependency resolution, all other dependency resolution methods for the domain are disabled,
    ///    this requires calling <see cref="EndAssemblyResolve" /> immediately after executing user code where dependency failures occur.
    /// </remarks>
    public static void BeginAssemblyResolve<T>()
    {
        BeginAssemblyResolve(typeof(T));
    }

    /// <summary>
    ///    Subscribes the current domain to resolve dependencies for the type.
    /// </summary>
    /// <param name="type">
    ///    Type, to search for dependencies in the directory where this type is defined.
    /// </param>
    /// <remarks>
    ///    Dependencies are searched in a directory of the specified type.
    ///    At the time of dependency resolution, all other dependency resolution methods for the domain are disabled,
    ///    this requires calling <see cref="EndAssemblyResolve" /> immediately after executing user code where dependency failures occur.
    /// </remarks>
    public static void BeginAssemblyResolve(Type type)
    {
        if (_domainResolvers is not null)
            return;
        if (type.Module.FullyQualifiedName == "<Unknown>")
            return;

#if NETCOREAPP
        var loadContextType = typeof(AssemblyLoadContext);
        var resolversField = loadContextType.GetField("AssemblyResolve", BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.DeclaredOnly)!;
        var resolvers = resolversField.GetValue(null);
        resolversField.SetValue(null, null);
#else
        var domainType = AppDomain.CurrentDomain.GetType();
        var resolversField = domainType.GetField("_AssemblyResolve", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly)!;
        var resolvers = resolversField.GetValue(AppDomain.CurrentDomain);
        resolversField.SetValue(AppDomain.CurrentDomain, null);
#endif

        _domainResolvers = resolvers;
        _moduleDirectory = System.IO.Path.GetDirectoryName(type.Module.FullyQualifiedName);

        AppDomain.CurrentDomain.AssemblyResolve += OnAssemblyResolve;
    }

    /// <summary>
    ///    Unsubscribes the current domain to resolve dependencies for the type.
    /// </summary>
    public static void EndAssemblyResolve()
    {
        if (_domainResolvers is null)
            return;

#if NETCOREAPP
        var loadContextType = typeof(AssemblyLoadContext);
        var resolversField = loadContextType.GetField("AssemblyResolve", BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.DeclaredOnly)!;
        resolversField.SetValue(null, _domainResolvers);
#else
        var domainType = AppDomain.CurrentDomain.GetType();
        var resolversField = domainType.GetField("_AssemblyResolve", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly)!;
        resolversField.SetValue(AppDomain.CurrentDomain, _domainResolvers);
#endif

        _domainResolvers = null;
        _moduleDirectory = null;

        AppDomain.CurrentDomain.AssemblyResolve -= OnAssemblyResolve;
    }

    private static Assembly? OnAssemblyResolve(object? sender, ResolveEventArgs args)
    {
        string? assemblyName = new AssemblyName(args.Name).Name;

        string assemblyPath = System.IO.Path.Combine(_moduleDirectory!, $"{assemblyName}.dll");
        if (System.IO.File.Exists(assemblyPath) is false)
            return null;

        return Assembly.LoadFrom(assemblyPath);
    }
}

Update Project (.csproj) File

  • Must set Private to False on Vault-related assembly file reference.
  • Replace VaultVersion value of 2026 with the version of Vault being referenced.
<!-- Custom Variable -->
<PropertyGroup>
    <VaultVersion>2026</VaultVersion>
</PropertyGroup>

<PropertyGroup Condition="$(VaultVersion) >= 2026">
    <DefineConstants>$(DefineConstants);VAULT_HAS_DOCK_PANELS</DefineConstants>
</PropertyGroup>

<ItemGroup>
    <Reference Include="Autodesk.Connectivity.Explorer.Extensibility">
        <HintPath>$(ProgramFiles)\Autodesk\Vault Client $(VaultVersion)\Explorer\Autodesk.Connectivity.Explorer.Extensibility.dll</HintPath>
        <!-- Required For Isolated Addin -->
        <Private>False</Private>
    </Reference>
    <Reference Include="Autodesk.Connectivity.Extensibility.Framework">
        <HintPath>$(ProgramFiles)\Autodesk\Vault Client $(VaultVersion)\Explorer\Autodesk.Connectivity.Extensibility.Framework.dll</HintPath>
        <!-- Required For Isolated Addin -->
        <Private>False</Private>
    </Reference>
    <Reference Include="Autodesk.Connectivity.JobProcessor.Extensibility">
        <HintPath>$(ProgramFiles)\Autodesk\Vault Client $(VaultVersion)\Explorer\Autodesk.Connectivity.JobProcessor.Extensibility.dll</HintPath>
        <!-- Required For Isolated Addin -->
        <Private>False</Private>
    </Reference>
    <Reference Include="Autodesk.Connectivity.WebServices">
        <HintPath>$(ProgramFiles)\Autodesk\Vault Client $(VaultVersion)\Explorer\Autodesk.Connectivity.WebServices.dll</HintPath>
        <!-- Required For Isolated Addin -->
        <Private>False</Private>
    </Reference>
    <Reference Include="Autodesk.DataManagement.Client.Framework">
        <HintPath>$(ProgramFiles)\Autodesk\Vault Client $(VaultVersion)\Explorer\Autodesk.DataManagement.Client.Framework.dll</HintPath>
        <!-- Required For Isolated Addin -->
        <Private>False</Private>
    </Reference>
    <Reference Include="Autodesk.DataManagement.Client.Framework.Vault">
        <HintPath>$(ProgramFiles)\Autodesk\Vault Client $(VaultVersion)\Explorer\Autodesk.DataManagement.Client.Framework.Vault.dll</HintPath>
        <!-- Required For Isolated Addin -->
        <Private>False</Private>
    </Reference>
</ItemGroup>

Notes

  • This add-in format is compatible with Vault versions using .NET Framework 4.8 or .NET 8.

References

Isolated Autodesk Addins

Part 3 of 4

A technical series exploring dependency isolation for Autodesk add-ins, using `AssemblyLoadContext` to solve versioning conflicts.

Up next

How to Use AssemblyLoadContext for Isolating Inventor Addin Dependencies

Solve version conflicts once and for all.