Obviously, I wrote about this topic some time ago. Here’s the original post: Post. Basically, the issue was that I needed to maintain multiple versions of my Revit plugin, to match multiple versions of Revit that I wanted to support. At the time I thought that Configurations was the right way to go, but as you can guess, I no longer think that’s true. The only reason I am even touching this subject is because I have recently saw a post on Speckle Blog, that discussed this same approach, and they opted for Configurations. Now, don’t take this as I am telling you that Matteo is wrong. Far from it. This in fact might be the best solution for their unique problem, but I thought that I share another approach that I have learned recently.
Shared Projects. This has been added as a new feature back in Visual Studio 2015, but I haven’t really heard of it until few months ago. Let’s first examine what a Shared Project is. From the sound of it, you might think that it’s some DLL that is shared across multiple projects, and that it contains code that would be used by these projects. That’s not the case. A Shared Project, is basically shared source code. The way it works is that each project (DLL) that references the Shared Project would have the code contained in it, incorporated right into it. How is that different than Configurations:
- Configurations build different versions of the same code, based on the selected Configuration. There is still just a single project (DLL), that is compiled and post processed based on whatever Configuration is currently selected.
- Shared Project approach actually allows you to build different DLLs for each version, where each DLL has the Shared Project embedded into it, but now instead of using Configurations to control Post Build events, References, Dependencies, Setup projects etc. you can just do that in different projects.
Let me show you an example. I have recently switched Archi-lab.net Dynamo package to use this approach. Now, whether this is the “correct” approach for Dynamo is a completely different discussion, because as we all know it, Dynamo doesn’t make anything easy, but let’s assume it was the correct approach. What does that look like:
As you can see above there is an “archilabSharedProject” project, and there are different projects for “archilab2021”, “archilab2020” etc. The idea is that all shared code, is contained in the archilabSharedProject, but all resources that are unique to specific version would be contained in that version’s project. This approach makes it easy for me to have post build events that copy resources (*.addin manifest files) to specific location, and I don’t have to use “if Configuration == 2020 copy to 2020 location” type code. Someone might say: “Well that’s nothing special. If statements are not that hard. Is there a real benefit to this approach?”
The answer is, YES! OK, so I agree, a Post Build event or Target FrameworkVersion configuration that looks like the one below might not be sexy, but it’s not end of the world.
The real benefit of Shared Projects comes into play when we start dealing with things like Setup projects aka. installers (MSIs). These allow you to reference an asset or a project. If you are using things like Configurations, then you really have just a single DLL/project, but you might want to have your installer reference different versions as you would want it to copy them to different locations. That’s the whole point of an installer. Now, you have a pickle because you have to do that in two steps:
- build all of your Configurations, copy these DLLs to different locations based on a Configuration
- build your MSI, referencing assets (DLLs) from folders on a drive, rather then dynamically linking them from Visual Studio solution directly.
Another thing that greatly annoyed me with Configurations was the fact that now I basically either had to give up the idea of having a Debug/Release Configurations, or I had to create them for each version of Revit. That might be as many as 8 Configurations if I need to carry Debug/Release for 2021/2020/2019/2018. Why do I care for Debug/Release? You might want to do things like disable XML/PDB file creation for Release builds. If you are not planning on debugging them, then why clutter that bin folder with them? Also, every now and then I am asked to obfuscate the code (I know, don’t ask why), but if that’s part of the Release routine, then I CAN’T debug the Release code, and that necessitates that I have Debug and Release configurations.
Now, you might also be wondering, but can I still do the good old “#if Release2015 #else #endif” statements? The answer is, yes, of course. You can put all of the version specific code into the Shared Project. It works just like the old Configuration’s method.
In summary, the Shared Project approach has all of the same benefits that Configurations do, plus it avoids potential issues with things like MSI’s and Debug/Release Configurations. In my opinion it also makes it explicit that there is support for different versions of Revit, which then makes all version specific code and resources obvious and much easier to maintain. It might not be a revolutionary approach, but I think it’s a little bit better than Configurations.
Again, I used it with archi-lab.net project here. Please check it out, and let me know if you have any questions.
that makes sense, I have recently been struggling with having to reference two different version of cef sharp between revit 2019 and 2020 (revit forces a specific version of cef sharp to be used by any addin). I assume you can install one version of cef in the shared project and then install any other version for the individual versions? Thanks as again
You would actually install all of the references (different version of cef) in individual projects. So plugin2021 would have a reference to revitapi2021, while plugin2020 would have a reference to revitapi2020. You only keep CODE in the shared project.
Personally I found a lot of the difficulties of maintaining different versions in one code base to just not be worth it.
I’ve switched to just using git branches when there’s breaking api changes and then use git’s merging / cherry picking features if I ever need to bring code between them.
Great post as always! I just converted my project to use this approach and it has made it much easier to create my installer that handles multiple versions. Thank you!
You are welcome. I do have to mention that using Shared Project might create some issues like sharing resources between different versions that are not embedded. For example WPF resources like images, that are not Embedded into the assembly, cannot really be stored inside of the Shared Project. You will want to move them to another DLL, and access from there. So this approach might have added an extra DLL. Just FYI.
I’ve just run into this problem.
It’d be great if there was a youtube tutorial on how to set this up for a simple hello world across multiple revit versions in c#.
Regardless, Thank you for posting. It’s encouraging to know there are solutions out there.
I don’t think I will be doing a video on this subject. I hope that my [multiple] posts were clear enough, but if you still have concerns please post your questions and I will try and help.
How can i add WPF element to a shared project?
(Without add it first to a normal project and then drag and drop it to the shared project)
Yeah, the shared project doesn’t have the same templates as regular projects. I usually add the WPF user control as that is usually available or do what you did which is add it to a normal project, and then drag it over.
You mentioned obfuscating your source code at another’s request. Are you able to share any guidance on this topic as there has been little published with reference to obfuscating Revit addins in the last 10-15 years. I need to implement some obfuscation and the choice of obfuscator and method of doing so seems to be different for Revit addins when compared to standalone applications. Your help would be greatly appreciated if you have time.