Izzy Rodriguez
Izzy Rodriguez

Reputation: 2185

Revit API - Possible NewtonSoft.Json conflict

I'm trying to create an extension that goes to the cloud and retrieve data, simple request. It works flawless when using the Add-In Manager, but when I distribute to other users, there seems to be a conflict with the version of the NewtonSoft.Json I'm using (7.0.1) and the version I found in Revit Program Files (5.0.8).

Full error message is:

[Window Title] Command Failure for External Command

[Main Instruction] Revit could not complete the external command. Contact the provider for assistance. Information they provided to Revit about their identity: icubY.

[Expanded Information] Revit encountered a System.IO.FileNotFoundException: Could not load file or assembly 'Newtonsoft.Json, Version=6.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed' or one of its dependencies.

This is a shared DLL that encapsulates all this comes and goes from the API to the APP (with security token, headers, cookies), so a separated version would be a real pain.

Do I really need to use the older version for this to work? Any other idea?

Upvotes: 1

Views: 1771

Answers (5)

Moustafa Khalil
Moustafa Khalil

Reputation: 164

I recently encountered a similar DLL conflict issue in Revit, particularly with dependencies like System.Runtime.CompilerServices.Unsafe.dll. This led me to explore how AppDomains could be leveraged to manage DLL isolation and avoid conflicts. By isolating conflicting libraries in a separate AppDomain, Revit's primary dependencies remain undisturbed while allowing third-party libraries to load their required versions. This solution sidesteps the limitations of Revit’s default assembly loading behavior and offers a practical approach to dependency management.

I documented the entire process, challenges, and solutions in an article: Using AppDomains to Resolve DLL Conflicts in Revit Plugins. The article breaks down the AppDomain setup and demonstrates how to create isolated environments within Revit, addressing common issues like conflicting DLL versions.

For those interested in seeing the code in action, I also shared my implementation on GitHub.

Upvotes: 0

Andy
Andy

Reputation: 133

So I similarly had this issue in a project that I'm working on. After a substantial amount of digging on the issue I feel like I have a strong grasp on what is happening.

As was stated, Revit loads assemblies using the Assembly.LoadFile command. This means that all the add-in assemblies are loaded into the same AppDomain. That's not ideal given if you want to add an event handler to AppDomain.CurrentDomain.AsseblyResolve in order to prevent conflict in your add-in. This is because AssemblyResolve is an unusual event which requires an Assembly as the return item. That means that if you have multiple event handlers attached to AssemblyResolve then the first event handler that returns a non null Assembly will be the event handler that is used and thus cause conflict if multiple add-ins are attempting to resolve the same Assembly (such in the case of a common Assembly like Newtonsoft.Json).

So for instance, and example that would likely be common among programmers, if you have Dynamo installed, Dynamo ALSO adds an event handler to AssemblyResolve that attempts to resolve Newtonsoft.Json and loads it from the Dynamo program files directory when called. In my case, I needed Newtonsoft.Json version 7.0.0.0 but with Dynamo 0.9 installed on my computer it was returning Newtonsoft.Json version 4.5.0.0. This was incredibly confusing given I had theoretically handled the resolution perfectly but my assembly resolve event was not firing when I needed it to.

With all that said, there isn't too much that you can do in this certain instance. A few round-about ways to defeat this problem are:

-Find a way to load your add-in prior to Dynamo (or whichever Assembly is resolving the event that you wish to resolve) and handle the resolution better (i.e. make sure it's your application that is currently being run prior to resolving the assembly issue). NOTE: Having looked at the procmon.exe output at the startup of Revit it appears that Revit loads addins from the "C:\ProgramData\Autodesk\Revit\Addins\" folder first, in alphabetical order, AND THEN from the "C:\ProgramData\Autodesk\ApplicationPlugins" folder in alphabetical order.

-Contact the conflicting add-in manufacturer and ask them kindly to fix the resolution.

-Specific to my issue, simply upgrading dynamo to version 1.2 solved the conflict.

I've read about creating your own AppDomain and loading all the assemblies that you need into it and then perform the operations you want without conflict, but I couldn't get it to work for myself.

Note: Don't waste time messing with the application.config file for your specific library. Revit will only check the Revit.exe.config for the Assembly probing path and dependent assembly information. I also wouldn't recommend modifying that.

Upvotes: 2

devonp
devonp

Reputation: 11

I did a bit more research because I did not fully understand the solution above. I thought a really simple solution was explained here

You just set the reference (newtonsoft.json) to specific version = true in your visual studio project, and then copy over that reference into the add-in folder with your revit add-in .dll

When you distribute to users they would need the newtonsoft.json library as well as your add-in .dll which isn't ideal but workable (just tested and this worked for me)

Upvotes: 1

Colin Stark
Colin Stark

Reputation: 601

I manually load all of my dependencies in the IExternalApplication.OnStartup method. The problem you're experiencing is because Revit looks in the folder containing Revit.exe for any dependencies and not in your addin directory.

If you can't get the AssemblyResolve event to work, then try something like this:

Retrieve the directory of your addin by using string location = Assembly.GetAssembly(typeof(YourIExternalApplicationClass)).Location;, then use string dir = Path.GetDirectoryName(location);

You can then get the path of each dependency by using string dllPath = Path.Combine(dir, "DLLName.dll");

Finally, load each DLL by using Assembly.LoadFrom(dllPath);

Upvotes: 1

Maxence
Maxence

Reputation: 13296

First, I will start with the Fuslogvw.exe tool to see what happens when your assembly is loaded.

AddInManager use Assembly.LoadFile to load the addin, then hook into the AppDomain.CurrentDomain.AssemblyResolve event to resolve the dependencies. May be you can use the same technique to load your Newtonsoft.Json assembly.

Upvotes: 2

Related Questions