encapsulating addins with costura.fody

image by Red Crane Media

 

OK, so I think I got a little ahead of myself here. Costura.Fody is great, but the issue of conflicting assemblies persists. I did some more testing on different packages, and realized that this is not a sure fire solution. Why? Because according to Costura.Fody’s source code, it still checks the AppDomain for the already loaded assemblies before resolving it from the embedded resources:

What a bummer. That kind of sucks. Anyways it was worth a try, and apologies for speaking too early. I am not sure why, but when I was first testing it, my errors about Xceed assemblies went away, but the EO.WebBrowser one persists.

Costura.Fody is still insanely useful tool to have in your arsenal so please do read ahead on how to use it.

  • Install Costura.Fody from Nuget Pacakges. You can simply right click on References, and go to Manage Nuget Packages, then search for Costura.Fody, and install it.
  • Add FodyWeavers.xml file to your project. This is a settings file for Fody. You can use it to specify any changes to the default settings that Fody ships with. Here’s mine, that specifically excludes all RevitAPI assemblies. Why? Just in case. But it’s redundant. Probably. All you have to do to exclude an assembly from being embedded by Fody, is to set its CopyLocal property to false. However, I have been experiencing some weird issues where these DLLs were still getting embedded, regardless. So just to be safe, I am calling them all out.

  • That’s it!

If you build your solution now, it should contain all of the packages that you need. How do I know. You can check out your DLL with ILSpy or dotPeek from JetBrains:

From the image above, you can see all of the references marked CopyLocal = True to all of them being embedded in the HOK.Core.dll under resources. There is also another thing that Fody did for you, that is create an AssemblyLoader and an event handler called ResolveAssembly to handle accessing these resources from withing the file, rather than looking in the “public basket”. Those can be seen under a new Namespace:

OK. So that was it? Anything else? 

Maybe. If you have some Post Build Commands, like I usually do in my project, then it might be time to migrate them to a MSBuild Target. I usually have some post build commands that copy built assemblies to a location on a drive, like Revit/Addins folder etc. Well, since Fody, also uses the post built to merge the assemblies into your main assembly, you have to make sure that your post builds get executed after Fody is finished, or they will run into Access Denied issues since files are being used. What I ended up doing was just ditch the Post Build Command, and create a new Target:

Ps. If you insist on using the Command Line, you can easily translate the post build commands to a Exec task. Just replace all quotation marks with character code " like in the code above. I am actually keeping the Command line of signing my DLLs with the signtool.exe.

Ps2. MSBuild tasks take the environmental variables but you have to call them with $() rather than %25%25 like in a command line.

Ps3. The message tasks are there just for debugging. They print a message to the console so I know that my builds didn’t fail.

Ps4. One thing to keep in mind, is that Fody will not embed 2nd level dependencies. Imagine that you have a project1, that references project2. Project2 has a dependancy on Newtonsoft.Json.dll, but that will not be merged into project1 unless you reference it directly into project1. Just keep that in mind.

Ps5. MSBuild Targets are a much better option than using Post Build Commands. I know that I have advertised these commands before in one of my posts, but as is, I realized that with these MSBuilds routines a lot of the work is done for us. There are some nice utility methods setup for us, that ship with MSBuild that we can use to sign plug-ins and copy files around. We can also simply call command line routines so basically we get all of the functionality that Post Build Commands offer and some more.

Cheers!

-K

3 Comments

  1. Jason says:

    Hi Konrad, could you not simply omit / comment out those lines from the assembly resolver so that it *never* checks the current app domain? Or perhaps only checks the current app domain if the assembly is on that exclusion list (i.e. the Revit API dlls)?

    Since Revit doesn’t support strong naming I feel like overwriting the assembly resolver is still the key to avoiding the duplicated dll issue.

    • I don’t think that would work. So Microsoft suggests to not load the same assembly twice (different versions) into the same context as that can potentially cause issues. So yeah, in theory you can load it in again, but then, you might have an unstable application, that can throw exceptions that are impossible to debug. It’s a little bit more complicated problem that I don’t have an answer for at the moment.

      • Jason says:

        You’re right, I suppose that if you were to do that, any other assembly that references one with the same name but doesn’t have it’s own overwritten assembly resolver would risk getting delivered your embedded copy instead.

        It’s too bad because this is a problem that Microsoft solved with dll Strong Naming, but Autodesk refuses to support it.

Leave a Comment