Visual Studio - building different releases for assemblies with the same name - c#

I've got a plugin for a third party software, and I reference their assembly - let's call it Api.dll.
Each year, Api.dll is updated. I'd like to support versions from past years.
Since the assembly name is the same, I can't just add them all and use a compile time flag - I have to manually remove, add, rebuild.
How can I automate this build process better, so I can build all the versions that I need at the same time, pointing to the appropriate version of the same-named .dll?

I would do this with build configurations. If you create a config for each version, you can edit your csproj file like this:
<Reference Include="v1/Api.dll" Condition="'$(Configuration)'=='Release Api v1'" />
<Reference Include="v2/Api.dll" Condition="'$(Configuration)'=='Release Api v2'" />
<Reference Include="v3/Api.dll" Condition="'$(Configuration)'=='Release Api v3'" />
Doing this will change the library being referenced depending on the configuration.
Additionally, if you need specific code per version, you could add in some compilation symbols for each configuration and use #if ...

Related

How can I solve Nuget DLL Hell - whatever I do VS inists that the dll version is different to that in the package

I have been struggling for over a week now with DLL hell after a Nuget update. I have managed to solve most of the problems by manually editing solution, project, and package files. However, whatever I do VS insists that the version of the dll is different to the one installed by Nuget. The library in question is MailKit, which worked fine until the update. Now the installed version is 2.4.1 but the property grid despite showing the correct location insists that the version 2.4.0:
I have tried all of the following with no success at all:
Update-Package -Reinstall
Manually deleting the Nuget cache
Manually deleting every single copy of the 2.4.0 dll on the computer
Searching the entire code base for any reference to 2.4.0 and manually editing the code to remove the reference or renumber to the correct version
Removing the Nuget cache from the command line
Everything else I could find suggested as a solution on StackOverflow
Whatever I do, it still shows the wrong version, so when the app runs it throws an exception since the correct version is copied local.
Can anyone suggest how I might resolve this please?
EDIT:
Package Config:
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="BouncyCastle" version="1.8.5" targetFramework="net452" />
<package id="JetBrains.Annotations" version="2019.1.3" targetFramework="net452" />
<package id="MailKit" version="2.4.1" targetFramework="net452" />
<package id="MimeKit" version="2.4.1" targetFramework="net452" />
</packages>
Project File (only references to the MailKit and MimeKit, the rest redacted as unnecessary, let me know if more is needed):
<Reference Include="MailKit, Version=2.4.1, Culture=neutral, PublicKeyToken=4e064fe7c44a8f1b, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\MailKit.2.4.1\lib\net45\MailKit.dll</HintPath>
</Reference>
<Reference Include="MethodExtensions, Version=1.0.4469.11621, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\Libraries\MethodExtensions\bin\Debug\MethodExtensions.dll</HintPath>
</Reference>
<Reference Include="MimeKit, Version=2.4.1, Culture=neutral, PublicKeyToken=bede1c8a46c66814, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\MimeKit.2.4.1\lib\net45\MimeKit.dll</HintPath>
</Reference>
Further Edit:
For clarification, the dll version installed by the package is clearly 2.4.1:
Here is the runtime error message confirming that it is looking for 2.4.0 which is no longer on the computer following the Nuget update:
The Solution
The answer from #zivkan pointed me in the right direction. Firstly I was looking for entirely the wrong thing, assuming that the version in the Property Grid was incorrect when in fact it was perfectly fine. The reference is located in a plugin module which itself compiles to a library, which means that when compiling the MailKit dll not only has to be copied local but then copied to the main application folder using a post build event command line. It turns out that there was an error in the post build event code so that MailKit was not copied and MimeKit was copied twice. Not taking the blame for that one, but I do take the blame for not recognising the problem!
Thanks, #zirkan, for your help and explanation which finally got me to the resolution...
when the app runs it throws an exception since the correct version is copied local.
Do you mean incorrect? I don't see why an exception will be thrown if the correct version is copied. In any case, it would have been more helpful if you showed us the actual error you got. I'm going to spend the rest of this answer explaining why everything else is working as designed, meaning you haven't given us enough information to understand your problem, so it's hard to suggest a good solution.
Anyway, a NuGet package is just a zip file (renamed .nupkg) that contains some files, usually some .NET assemblies. So, giving away the fundemental issue, what happens if the different assemblies in the package have different versions? The answer is nothing because assembly version and package version are independent. Often they're similar, or (almost) exactly the same, but since they're independent there's no reason they can't be different.
An assembly also has multiple versions. Firstly, if you find the .dll in Windows explorer, right click, select properties and go to the details tab, you'll see that all (or most) dlls have a product version and a file version. This is not specific to .NET, all windows executables have this metadata. If you use ILSpy or something similar to inspect the .NET assembly, you'll see there are AssemblyFileVersion, AssemblyInformationalVersion and AssemblyVersion attributes. So all together there are at least 5 different version metadata, and they're all independent and therefore can be different. That's before we consider NuGet package version is a 6th independent version number.
Now, the assembly in the properties window doesn't know anything about NuGet. It's showing you the path to the file, and since NuGet extracts packages to a path that includes the package version, we can see it in the path. But the version shows in the properties window is the assembly version. So, in this case the MailKit v2.4.1 package contains MailKit.dll with assembly version 2.4.0.0. Visual Studio's property window is showing you the correct information, there is no problem.
My answer is already long enough, so I won't go into a lot of details, but package authors may choose to use the same assembly version across multiple versions of their packages to minimise the number of times binding redirects are needed. However, this only works when the different product veresions are compatible. When they're incompatible and two different assemblies both have a dependency to the same assembly version of a dependency, it's impossible to load the two different versions at the same time to work around the problem (yes, when the same assembly has different assembly versions, it's possible to load both at the same time, it just takes extra effort).

support multiple versions of third party library

I created a C# application (MyAppV1) that requires a third party API. My application needs to work with multiple versions of this API, but only a single version at one time. I have setup my solution to change the reference and using statement for different build configurations and I create multiple executable files that each target a different API version.
Presently I have this situation:
MyAppV1_ThirdPartyV1.exe uses ThirdPartyV1.dll
MyAppV1_ThirdPartyV2.exe uses ThirdPartyV2.dll
MyAppV1_ThirdPartyV2_5.exe uses ThirdPartyV2.dll (they didn't change
the library name for the minor version of their software)
MyAppV1_ThirdPartyV3.exe uses ThirdPartyV3.dll
I would like to be able to maintain a list of the versions, perhaps in an App.config and load the appropriate dll library at runtime. I'm having trouble knowing where to begin with this. Is this an appropriate strategy? I'm not sure how best to handle this situation. Multiple versions of my application the only differ with the referenced library seems very clunky to me.
Much of the information I find is related to supporting multiple frameworks, handling the requirement of two versions of the same library downstream at the same time, or needing to load both at the same time. I can't find information on how to handle my particular situation.
This is possible on project level. You can build different configurations in solution and when you add references as below, it will take the desired DLLs
<Choose>
<When Condition="'$(Configuration)|$(Platform)'=='YourSpecialConfiguration1|x64'"><!-- attention here -->
<ItemGroup>
<Reference Include="your.dllv1.name">
<HintPath>yourDllPath_v1\your.dllv1.dll</HintPath><!-- attention here -->
<Private>true</Private>
</Reference>
<!-- more references here -->
</ItemGroup>
</When>
<When Condition="'$(Configuration)|$(Platform)'=='YourSpecialConfiguration2|x64'"><!-- attention here -->
<ItemGroup>
<Reference Include="your.dllv2.name">
<HintPath>yourDllPath_v2\your.dllv2.dll</HintPath><!-- attention here -->
<Private>true</Private>
</Reference>
<!-- more references here -->
</ItemGroup>
</When>
<Otherwise>
<ItemGroup>
<Reference Include="your.dllname">
<HintPath>yourRegularPath\your.dllname.dll</HintPath><!-- attention here -->
<Private>true</Private>
</Reference>
<!-- AND more references here -->
</ItemGroup>
</Otherwise>
</Choose>
What you see above - option 1.
Option 2 - Different projects for each version. Downside - if you add a file or reference , you need to add to each project
Option 3 - Add all references but declare different namespace aliases (in reference property window) for each. Then in code do conditional compilation like
ISomething myVar;
#if V1
myVar = new namespace1.ClassX();
#elif V2
myVar = new namespace2.ClassX();
#else
. . . .
#endif
And lastly:
"I would like to be able to maintain a list of the versions, perhaps in an App.config and load the appropriate dll library at runtime."
- you probably don't need non of these. You just need to produce your packages with different versions. Loading at runtime will require more coding work while still supplying all DLLs because you don't know what you going to load next time.

How to reference a project out of solution?

I have a Visual Studio C# solution which consists of some projects. One of the projects needs to reference another project which is not part of the solution.
At the beginning I was referencing dlls:
<ItemGroup>
<Reference Include="ExternalProj1, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\Proj1\ExternalProj1.dll</HintPath>
</Reference>
</ItemGroup>
However I must reference projects so that those will generate their dlls. In fact If I reference dlls and they have not been created, I need to build those projects separately.
However when referencing projects:
<ItemGroup>
<ProjectReference Include="..\..\Proj1\ExternalProj1">
<Project>{3341b552-a569-4313-aabc-34452fff60ac}</Project>
<Name>ExternalProj1</Name>
</ProjectReference>
</ItemGroup>
However when building the compiler cannot find those assemblies. The strange thing is that building process is reported as completed successfully but the error window reports one warning:
The referenced component ExternalProj could not be found.
So, what am I doing wrong? Thank you
I see you are using ProjectReference, which is what I'm familiar with in plain (non-NET) C++ projects. The Include attribute needs to name the file, not just the base name; e.g.
<ProjectReference Include="..\..\Proj1\ExternalProj1.vcxproj">
That is, ProjectReference is not Reference. See Common MSBuild Project Items
Also, the metadata that determines whether to link the LIB automatically is determined via the supplied props files if it is not specified for that item. Is a managed project even producing a LIB? So this should (with the filename correct) cause the nominated project to be built also as a dependent, doing something with its products is another issue altogether.
Try building from MSBuild.exe command line, not the IDE, to see the pure behavior before the IDE messes things up or adds more issues to figure out. And, feed it the specific proj file you are wanting, not the "solution" file. The .sln file is a strange beast and not only is it possible to have project references not present in the sln, there is no inherent concept of a sln file at all. Other than a list of projects to show in the IDE, it is a magic file converted into a master proj on the fly that lets you name various targets individually without having to know which proj file (or the path to it) which is handy enough, but mainly there for compatibility with VSBuild according to The Books. So avoid it, at least to simplify things to get the behavior you want during the exploration stage. Then add any complications back in if you still want them :) .

Fast way to globally change project references in Visual Studio

I have a solution with 20+ projects. Pretty much every project references two third party assemblies called foo.v.4.5.dll. I got a new version of the third party application today called foo.v.5.0.dll. I have to visit every project and change the references to the new assemblies. If they don't work well then I have to revisit and undo the change. This is time consuming and a pain.
Is there an easier way to do this? Are there any tools that can help me change the project references globally or is this the only way?
Each project has a .csproj file.
These files contains the references to the assemblies.
You can create a code to open all those files, search for the current assembly text and substitute for the new assembly text.
There goes an example:
<Reference Include="Autodesk.AutoCAD.Interop.Common, Version=18.1.0.0, Culture=neutral, PublicKeyToken=eed84259d7cbf30b, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<EmbedInteropTypes>True</EmbedInteropTypes>
<HintPath>..\..\..\..\..\..\ObjectARX 2011\inc-win32\Autodesk.AutoCAD.Interop.Common.dll</HintPath>
</Reference>
So a XML parsing would be probably the best way

Locate and add project reference for .Net assembly containing a given type?

I'm working with a large (270+ project) VS.Net solution. Yes, I know this is pushing the friendship with VS but it's inherited and blah blah. Anyway, to speed up the solution load and compile time I've removed all projects that I'm not currently working on... which in turn has removed those project references from the projects I want to retain. So now I'm going through a mind numbing process of adding binary references to the retained projects so that the referenced Types can be found.
Here's how I'm working at present;
Attempt to compile, get thousands of
errors, 'type or namespace missing'
Copy the first line of the error
list to the clipboard
Using a perl script hooked up to a
hotkey (AHK) I extract the type name from
the error message and store it in the windows clipboard
I paste the type name into source
insight symbol browser and note the
assembly containing the Type
I go back to VS and add that
assembly as a binary reference to
the relevant project
So now, after about 30 mins I'm thinking there's just got to be a quicker way...
These solutions come to my mind:
You can try to use Dependency Walker or similar program to analyze dependecies.
Parse MSBuild files (*.csproject) to get list of dependencies
EDIT:
Just found 2 cool tools Dependency Visualizer & Dependency Finder on codeplex I think they can help you greatly.
EDIT:
#edg, I totally misread your question, since you lose references from csproj files you have to use static analysis tool like NDepend or try to analyze dependencies in run time.
No, there currently isn't a built-in quicker way.
I would suggest not modifying the existing solution and create a new solution with new projects that duplicate (e.g. rename and edit) the projects you want to work on. If you find that the solution with the hundreds of projects is an issue for you then you'll likely just need to work on a subset. Start with a couple of new projects, add the binary (not project) reference and go from there.
One thing you can try is opening up the old .csproj file in notepad and replacing the ProjectReference tags with Reference tags. If you can write a parser, feel free to share. :)
Entry in .csproj file if it is a project reference
<ItemGroup>
<ProjectReference Include="..\WindowsApplication2\WindowsApplication2.csproj">
<Project>{7CE93073-D1E3-49B0-949E-89C73F3EC282}</Project>
<Name>WindowsApplication2</Name>
</ProjectReference>
</ItemGroup>
Entry in .csproj file if it is an assembly reference
<ItemGroup>
<Reference Include="WindowsApplication2, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<ExecutableExtension>.dll</ExecutableExtension>
<HintPath>..\WindowsApplication2\bin\Release\WindowsApplication2.dll</HintPath>
</Reference> </ItemGroup>
Instead of removing the project files from the solution, you could unload the projects you aren't working on (right-click the project and select Unload Project). As long as the unloaded project has been built once, any other project with a reference to it will be able to find the assembly in the project's output directory and build with it.

Categories

Resources