Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Getting the path of a nuget package programmatically

Tags:

c#

.net

nuget

I'm installing a nuget package with an executable in it. To reference the executable from within the solution in a project file (more specifically in an mvc controller), at the moment I need to hardcode the folder path, which is particularly annyoing since it has a version number in it. So when I update the nuget package, I'd need to change all these references.

That seems silly

I'm still fairly new to the microsoft stack, but isn't there some way to just request the path of a nuget dependency?

like image 561
Julian Krispel-Samsel Avatar asked Feb 13 '23 10:02

Julian Krispel-Samsel


1 Answers

When using NuGet for package manager what happens when a new package is added is that NuGet pulls in the dependencies from the repository, adds the files to a specific package directory (the location of which depends on the values in the solution local NuGet configuration) and adds all the files in the package 'lib' folder as references to the project. Files in other directories (e.g. tools, src etc.) are used for other purposes but they will not be added as references (unless manually done in a install.ps1 script).

One of the difference with an interpreted language like node.js becomes important when you build your binaries. During the build process the compiler copies the references to the output directory. This means that the location of your packages is mostly irrelevant when you execute the actual code because this code lives in a binary in a different location (and in general you only copy the binaries and not the entire solution folder to the server).

So depending on where the desired executable is in the package there are several scenario's:

  • If the executable is the package 'lib' folder then it should be referenced by your ASP.MVC project. In that case the executable will be copied to the output directory. In that case you can get hold of the executable path by executing the following code:

    // This returns the URI of the assembly containing the 'MyClass' type, 
    // e.g.: file:///C:\temp\myassembly.dll
    var codeBase = typeof(MyClass).Assembly.CodeBase;
    
    // This return the file path without any of the URI qualifiers
    var location = new Uri(codeBase).LocalPath;
    
  • If the executable is not in the package 'lib' folder in which case it is not referenced and thus won't be in the binary folder. Which means you need to copy the binaries to your binary folder. An easy way to do this is to use the pre- or post-build events. The drawback of using this is that you need to update the build event every time you update your package because the folder name includes the package version and will thus change. If you don't want to do this then with the use of some MsBuild magic you can set up a system that allows you to handle the changing folder name.

    • First you need to be able to find the folder that holds your executable. The following MsBuild script can be used to search your package directory (the one where NuGet unpacks the packages) for a given file (in your case the executable file). Put this script in a location where you can reference it from your project file (e.g. in your solution directory)

      <Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003' 
               ToolsVersion="4.0">
          <!--
              Finds the full path for a tool that was installed by NuGet
              Define:
              - PackagesDir: Directory that contains the 'installed' packages.
              - FileToLocate: Name of the executable file for which the path should be located.
              - Path: Return variable containing the full path of the executable file.
          -->
          <UsingTask TaskName="FindToolDirectoryFromPackages" 
                     TaskFactory="CodeTaskFactory" 
                     AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
              <ParameterGroup>
                  <PackagesDir ParameterType="System.String" Required="true" />
                  <FileToLocate ParameterType="System.String" Required="true" />
                  <Path ParameterType="System.String" Output="true" />
              </ParameterGroup>
              <Task>
                  <Using Namespace="System.Linq" />
                  <Code Type="Fragment" Language="cs">
                      <![CDATA[
                          Path = System.IO.Directory.EnumerateFiles(PackagesDir, FileToLocate, SearchOption.AllDirectories)
                              .OrderBy(k => System.IO.Path.GetDirectoryName(k))
                              .Select(k => System.IO.Path.GetDirectoryName(k))
                              .LastOrDefault();
                      ]]>  
                  </Code>
              </Task>
          </UsingTask>
      </Project>
      
    • Next you need to update your ASP.MVC project file. If you added the above MsBuild script to the solution folder then first add the following line to the very first PropertyGroup of your project file

      <SolutionDir Condition="'$(SolutionDir)' == '' or '$(SolutionDir)' == '*undefined*'">$(MSBuildProjectDirectory)\..</SolutionDir>
      
    • At the bottom of your project file are two commented targets, one called BeforeBuild and one called AfterBuild. Uncomment one, it doesn't really matter which one. Then add the following code:

      <FindToolDirectoryFromPackages PackagesDir="$(SolutionDir)\..\RELATIVE_PATH_TO_PACKAGE_DIRECTORY" 
                                     FileToLocate="FILE_NAME_OF_EXECUTABLE">
          <Output TaskParameter="Path" PropertyName="DirMyExecutableFile" />
      </FindToolDirectoryFromPackages>
      
      <ItemGroup>
          <Files Include="$(DirMyExecutableFile)\**\*.*" />
      </ItemGroup>
      
      <!-- OutputPath should be the directory to which the compiler will copy your binaries -->
      <MakeDir Directories="$(OutputPath)" 
               Condition="!Exists('$(OutputPath)')" />
      <Copy SourceFiles="@(Files)" 
            DestinationFolder="$(OutputPath)\%(RecursiveDir)" />
      

With all this done every time you build your project the executable should be copied to your output directory and then you can call the executable from your code.

like image 133
Petrik Avatar answered Feb 16 '23 02:02

Petrik