Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Visual Studio 2010 DTE: How to make added DLL reference absolute and not copied

Summary:

We need to duplicate the behaviour of the Add Reference dialog, using DTE, when you add a specific DLL (it adds a Hint path entry to the reference in the CSProj file).

**Note: There is another related, but not duplicated, post from me here: https://stackoverflow.com/questions/6690655/visual-studio-2010-add-in-how-to-get-a-references-hint-path-property Please also read that one for more information about this issue. I have now added a decent bounty to get an answer to this and will happily spread up-votes over any decent answers :)*

The story so far:

I am converting a project reference to a direct DLL reference programmatically using DTE.

Assuming I have a simple solution with a Project2 (the parent project) which references a Project1 (the child project), I make the change like this:

project1Reference = FindProjectReference(project2.References, project1);
project1Reference.Remove();
Reference dllReference = project2.References.Add(project1DllPath);

where project1DllPath refers to the "c:\somewhere\Project1\Bin\Debug\Project1.dll" file.

The problem I cannot yet solve is that the new reference is not to "c:\somewhere\Project1\Bin\Debug\Project1.dll" but instead points to "c:\somewhere\Project2\Bin\Debug\Project1.dll" (and the file is copied there).

If I add the DLL directly/manually using the Add Reference menu, it does not do this copying.

How do I add a DLL reference to an existing project's DLL without it taking a copy and referencing that instead?

I have tried adding dllReference.CopyLocal = false; after the Add but aside from setting the flag it made no difference. There appear to be no options to modify the path after creation.

Update: I have also tried programmatically removing any Build dependency on Project1 from Project2, but that had no effect.

Below is the difference between the csproj files:

As a project:

  <ItemGroup>
    <ProjectReference Include="..\ClassLibrary1\ClassLibrary1.csproj">
      <Project>{86B3E118-2CD1-49E7-A180-C1346EC223B9}</Project>
      <Name>ClassLibrary1</Name>
    </ProjectReference>
  </ItemGroup>

As a DLL reference (path was lost completely):

 <ItemGroup>
    <Reference Include="ClassLibrary1, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
      <Private>False</Private>
    </Reference>
    ...
  </ItemGroup>

As a manually referenced DLL:

  <ItemGroup>
    <Reference Include="ClassLibrary1, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
      <HintPath>..\ClassLibrary1\bin\Debug\ClassLibrary1.dll</HintPath>
    </Reference>
    ...
  </ItemGroup>

It looks like being able to specify the hint path for the DLL reference is the key. How do you set a hint path on a DLL reference (assuming you only have a handle to the Reference property)?

More information (20 July 2011):

The suggestion from Muse VSExtensions below does not impact the DLLs in question, as a copy has already been made from the DLL's project BIN to the parent project's BIN folder. The parent project does not bother to make use of the reference path as it already has the child DLL in its output folder.

Also the Reference Paths of a project are saved to the project.csproj.user file and not to the project.csproj file.

like image 270
Gone Coding Avatar asked Jul 13 '11 15:07

Gone Coding


People also ask

How do I change the reference version in Visual Studio?

Open the project in Visual Studio. Right-click on the project's References folder and select Add Reference to open the Add Reference dialog box. Locate the new assembly version in the Add Reference dialog for each Infragistics assembly listed in your References folder.


2 Answers

I'm convinced that this is a new bug/feature in VS 2010 because I have an add-in that started showing similar behaviour a couple of days ago after I migrated it from VS 2008... Basically, if you add a reference to anything within VS's assembly search path, it will be added without the path hint.

I managed to find other VS add-ins that solved this problem (Power Tools, NuGet among others), and they all seem to use MsBuild. I don't know if MsBuild raises resource usage much - I myself haven't seen too big a slowdown, possibly because References.Add() is very slow to start with. But note that to get an instance of MsBuild project, a method called "GetLoadedProjects" is used, which may mean that it works on data already present in memory.

Below is the code I used to fix my add-in, it's a simplified version of what I found on the net... Essentially, the idea is to add the reference as usual, then use MsBuild to set the path hint. Setting the hint is a trivial operation, but finding the instance of the MsBuild project item to add the hint to is incredibly complicated. I tried to hack an alternative using MsBuild only, but ran into other problems... This one seems to work.

One other thing that may be of interest here: the code contains a sort of optimization - it doesn't add the hint to the new reference if the reference's path is equal to the path we wanted to add. This is good enough for the case in question, and detects correctly when VS decides to use the dll from the output folder instead of what we told it. But when I tried to add a reference to the dll already in the output folder (I use a single output folder for many related projects), the add-in didn't set the hint path and project seemed to switch to using some other dll in the path (in my case the one from its PublicAssemblies folder)... So it may be useful to remove that "if (!newRef.Path.Equals(..." line altogether and add the hint always. I'm still investigating this case, so any additional - uhm, hints or improvements of the code are welcome.

string newFileName = "the path to your.dll";
VSLangProj.VSProject containingProject = yourProject;

VSLangProj.Reference newRef;

newRef = containingProject.References.Add(newFileName);
if (!newRef.Path.Equals(newFileName, StringComparison.OrdinalIgnoreCase))
{
    Microsoft.Build.Evaluation.Project msBuildProj = Microsoft.Build.Evaluation.ProjectCollection.GlobalProjectCollection.GetLoadedProjects(containingProject.Project.FullName).First();
    Microsoft.Build.Evaluation.ProjectItem msBuildRef = null;

    AssemblyName newFileAssemblyName = AssemblyName.GetAssemblyName(newFileName);
    foreach(var item in msBuildProj.GetItems("Reference"))
    {
        AssemblyName refAssemblyName = null;
        try 
        {
            refAssemblyName = new AssemblyName(item.EvaluatedInclude);
        }
        catch {}

        if (refAssemblyName != null)
        {
            var refToken = refAssemblyName.GetPublicKeyToken();
            var newToken = newFileAssemblyName.GetPublicKeyToken();

            if
            (
                refAssemblyName.Name.Equals(newFileAssemblyName.Name, StringComparison.OrdinalIgnoreCase)
                && ((refAssemblyName.Version != null && refAssemblyName.Version.Equals(newFileAssemblyName.Version))
                    || (refAssemblyName.Version == null && newFileAssemblyName.Version == null))
                && (refAssemblyName.CultureInfo != null && (refAssemblyName.CultureInfo.Equals(newFileAssemblyName.CultureInfo))
                    || (refAssemblyName.CultureInfo == null && newFileAssemblyName.CultureInfo == null))
                && ((refToken != null && newToken != null && Enumerable.SequenceEqual(refToken, newToken))
                    || (refToken == null && newToken == null))
            )
            {
                msBuildRef = item;
                break;
            }
        }
    }

    if (msBuildRef != null)
    {
        Uri newFileUri = new Uri(newFileName);
        Uri projectUri = new Uri(Path.GetDirectoryName(containingProject.Project.FullName).TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar);

        Uri relativeUri = projectUri.MakeRelativeUri(newFileUri);
        msBuildRef.SetMetadataValue("HintPath", relativeUri.ToString());
    }
}
like image 127
bdrajer Avatar answered Sep 27 '22 22:09

bdrajer


Does this have to be solved using only DTE? You could use MSBuild automation to do this... it has classes that can parse the contents of a csproj file.

Take a look at: Microsoft.Build.Evaluation Namespace, there is some useful information on how to load the csproj file, and then how to change it. See also the Project Class.

like image 23
Miguel Angelo Avatar answered Sep 27 '22 23:09

Miguel Angelo