I have a c# project that needs to load some yaml resources to work, I have set them with "build action":"content" and "copy to Output directory":"Copy if newer"
To load them I use simply a relative path, and everything works inside the project.
I realized xunit project that loads the library and everything still works fine.
Now I released the library on nuget and I am trying to reference on an Asp.Net Core, but when I try to load my files, the application tries to find them in the wrong folder:
The files are searched in relative path from the root asp.net project, but files are in C:\Users\[name]\.nuget\packages\[pakage name]\[version]\content
So I am wondering how can I get the "content" folder of the assembly in C#?
UPDATE: I TRY TO EXPLAIN BETTER
I have a visual studio solution, we call it foo.sln and a C# project that we call foo.csproj
The project is a netstandard 2.0 library and it's built to be consumed by other applications: cli, asp.net, core, wpf, etc...
The library is built to be multiplatform, so could run on Windows, Mac and on Linux
Inside the root of foo project there is a folder we call it YamlDefinitions
Inside of that folder we have a lot of .yaml files that needs to be loaded and parsed by my foo library.
So foo.csproj has something like:
<Content Include="YamlDefinitions\Definition1.yaml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="YamlDefinitions\Definition2.yaml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="YamlDefinitions\Definition3.yaml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="YamlDefinitions\Definition4.yaml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
These files are immutable, so I load them as content instead as resource
Now inside the c# code of my foo library, I have:
var filePaths = Directory.GetFiles("YamlDefinitions", "*.yaml", SearchOption.TopDirectoryOnly)
foreach (var filePath in filePaths)
{
using (var reader = new StreamReader(filePath))
{
// Load the stream
var yamlStream = new YamlStream();
yamlStream.Load(reader);
yaml = yamlStream.Documents.FirstOrDefault();
}
}
That code search, loads and parse all yaml files that can find in the specified folder.
Everything seems ok, so now to test I create an xunit prject inside the foo.sln, we call it footest.csproj
Now I reference foo.csproj inside footest.csproj
<ProjectReference Include="..\Foo\foo.csproj" />
All tests passed so I pack the library and publish the nuget package: foo.nuget
Now I want try to use my nuget package, so I create a new console application with Net Core 2.2, we call it MyConsoleApp.csproj
I install my nuget package foo.nuget:
<PackageReference Include="Foo" Version="1.0.0"/>
the package is installed and all yaml files are placed in
C:\Users\UserName\\.nuget\packages\Foo\1.0.0\content\YamlDefinitions
and in
C:\Users\UserName\\.nuget\packages\Foo\1.0.0\contentFiles\any\netstandard2.0\YamlDefinitions
The files are also visible inside MyConsoleApp.csproj as linked files, but they are not physically present in the project.
Now I launch my MyConsoleApp.exe, and I get an error, because it's unable to find these yaml files, because the application tries to load them in the same path as MyConsoleApp.exe
Workaround: from visual studio I select all yaml files that are linked inside MyConsoleApp.csproj and I set as CopyIfNewer
Now inside cproj visual studio adds these lines of codes:
<Content Include="C:\Users\User\.nuget\packages\Foo\1.0.0\contentFiles\any\netstandard2.0\YamlDefinitions\Definition1.yaml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
I run again MyConsoleApp.exe and everything works.
This workaround is bad because the end user should be able to use the nuget library without specifying manually to copy files.
If I release a new version of my library that contains new definitions, the end user should not be aware that these must be included in the project.
Anyway, there is a bigger problem:
I create a new ASP.NET Core 2.2 MVC Application, we call it MyWebApp.csproj
I install Foo.nuget and I try to use it.
I set manually to copy all Yaml definitions in output folder, and when I build they are automatically copied in MyWebApp\bin\Release\netcoreapp2.2\YamlDefinitions
I launch the web app and I get again an error, because my foo libray tried to find files in MyWebApp\YamlDefinitions, but files are in bin folder, so nothing works.
How can I fix my library and and publish as nuget in a way that all consumer applications can use it?
I can't find a C# function that retrieves the nuget folder where content files are stored and I doubt that could exist, but I found this working approach:
In foo.csproj file you need to specify that each file should be copied in output folder, this can be done with:
<Content Include="YamlDefinitions\Definition1.yaml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<PackageCopyToOutput>true</PackageCopyToOutput>
</Content>
You need to add PackageCopyToOutput to make working with nuget, unfortunately, this can't be done from visual studio UI and you have to edit .csproj manually.
In C# you have to use an absolute path because relative path is not the same in all kind of application (in a .NET cli is different from ASP.NET)
So you have to use a code like this:
var directory = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "YamlDefinitions");
var filePaths = Directory.GetFiles(directory, "*.yaml", SearchOption.TopDirectoryOnly)
Where AppDomain.CurrentDomain.BaseDirectory return the output directory where content files are copied
Related
We have a c# framework (not .core) solution with several projects in it. It is build by TFS. Sometimes (not all the times) I got a build error:
error MSB3030: Could not copy the file
"c:\BuildAgent_work\27\b\Console.Admin\Product.Admin.exe.manifest"
because it was not found.
[c:\BuildAgent_work\27\s\Console.Admin\Console.Admin.csproj]
The app.manifest file is added to the project Properties folder, and I checked it exists on buildagent source folder. I checked it is not exists on the binaries folder. I don't know why it is not copied there during the build.
In fact I don't know if I need this manifest thing at all. I think I don't. This whole thing was added to the project by one of my collegaue for a reason is unknown for me. Is it required to create publish package for web projects? In this case why it is required for a console project? For what reason is it added?
The msbuild parameters (for building the solution) are the following, and the [x] Clean option is checked in the TFS build solution step.
/p:OutDir=$(Build.BinariesDirectory)
/p:GenerateProjectSpecificOutputFolder=true
/p:DeployOnBuild=true /
p:PackageAsSingleFile=true
/p:GenerateDocumentation=true
/p:DisableAllVSGeneratedMSDeployParameter=true
/t:Clean,Build,Publish
/p:RunCodeAnalysis=$(CodeAnalysis.Run);CodeAnalysisRuleSet=$(CodeAnalysis.RuleSet).ruleset;CodeAnalysisIgnoreGeneratedCode=true
/p:IncludeAppPool=true
/p:PrecompileBeforePublish=true;EnableUpdateable=false
An application manifest is an XML file that describes and identifies the shared and private side-by-side assemblies that an application should bind to at run time. These should be the same assembly versions that were used to test the application. Application manifests may also describe metadata for files that are private to the application. You can refer to this document.
So, you first need to check in csproj for any additional operations on the manifest. When the MSBuild creates the publish files it copies files base on the build definition , you can try to edit the .csproj file as bellow which copied the relevant files. app.manifest is your file path .
<ItemGroup>
<None Update="app.manifest">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
I have created a WebApi project and deployed this as a serverless application to AWS Lambda. It all works fine except that for one method, I need to read from a file. I can see that this file is deployed as it's in the zip file that gets pushed up but for some reason, when I come to read from it in the code, it can't find it.
I've tried both embedded resource and content for build action but neither seem to work. Is there something in AWS that needs configuring so that my serverless application can access local files?
For anyone that needs more detail. Add your embedded files into your .csproj file as below (resources is your folder, note the "resources\" in .csproj and "resources/" in .cs):
<ItemGroup>
<Content Include="resources\*.*">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
In your .cs file, to get the file path:
var path = Path.Combine(Directory.GetCurrentDirectory(), "resources/example.txt");
I generate a nuget package from a project with this command in the post-build event. the variable %conf% is set to the right configuration (debug or release) and %1 is the project name (e.g. "MyCompany.MyProject").
nuget pack -Prop Configuration=%conf% "%1.csproj" -exclude *.sql -IncludeReferencedProjects
This package is for our own usage only, it will never be published on nuget. It ends in our private repository.
In the project, there is a file that is set to generate action : content and copy local : always. (My Visual Studio is in French, so I'm not 100% sure of the translation). Let's name it importantfile.xml.
In the generated package, I end up with this structure :
- content
- importantfile.xml
- lib
-net45 (.NetFramework,Version=v4.5)
-MyCompany.MyProject.dll
Which is fine, I want importantfile.xml to be deployed in the package, because, well, this file is important!
When I install the package in another project, importantfile.xml is deployed at the root of the project. That's OK. But it is not set to copy local : always.
I need importantfile.xml to be copy local : always in this project where I install my package.
How can I achieve that?
Notes :
I can set copy local : always on the file just after installing the package, that's no big deal. I would live with it if later updates of the package would let this property as-is, which is not the case. When updating the package, copy local is reset to never (as stated here).
There's a nuspec file in the project's folder, here it is :
<?xml version="1.0"?>
<package >
<metadata>
<id>$id$</id>
<version>$version$</version>
<title>$title$</title>
<authors>$author$</authors>
<owners>$author$</owners>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>$description$</description>
<copyright>Copyright 2014</copyright>
<tags>some random tags</tags>
</metadata>
</package>
Instead of using a PowerShell script another approach is to use an MSBuild targets or props file with the same name as the package id:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<None Include="$(MSBuildThisFileDirectory)importantfile.xml">
<Link>importantfile.xml</Link>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
In the nuspec file then, instead of adding the required files to the Content directory, add them to the Build directory along with the targets file.
Build
importantfile.xml
MyPackage.targets
lib
net45
MyAssembly.dll
If you require different content for different architectures then you can add architecture folders under Build also each with their own targets file.
Benefits to using a targets file over the PowerShell script with NuGet Content directory:
required content files aren't shown in the project in Visual Studio
content files are linked to rather than copied into the directory of each project which references the NuGet package (preventing there being multiple copies and keeping behaviour the same as for assemblies / libraries from NuGet packages)
PowerShell scripts only work in Visual Studio and aren't run when NuGet is run from the commandline (build servers, other IDEs and other OS), this approach will work everywhere
PowerShell install scripts are not supported in NuGet 3.x project.json system.
I know you guys got a working solution to this but it didn't work for me so I'm going to share what I pulled out of the NLog.config NuGet package install.ps1 (github source here).
NOTE: this is not my code, this is the content of the install.ps1 from
the NLog.config nuget package just sharing the knowledge.
It seems a little more straight forward to me and just hoping to help others that will likely stumble upon this.
You can find the accepted int values for BuildAction here and the accepted values for CopyToOutputDirectory here.
if the link breaks again
Fields
prjBuildActionCompile 1
The file is compiled.
prjBuildActionContent 2
The file is included in the Content project output group (see Deploying Applications, Services, and Components)
prjBuildActionEmbeddedResource 3
The file is included in the main generated assembly or in a satellite assembly as a resource.
prjBuildActionNone 0
No action is taken.
param($installPath, $toolsPath, $package, $project)
$configItem = $project.ProjectItems.Item("NLog.config")
# set 'Copy To Output Directory' to 'Copy if newer'
$copyToOutput = $configItem.Properties.Item("CopyToOutputDirectory")
# Copy Always Always copyToOutput.Value = 1
# Copy if Newer copyToOutput.Value = 2
$copyToOutput.Value = 2
# set 'Build Action' to 'Content'
$buildAction = $configItem.Properties.Item("BuildAction")
$buildAction.Value = 2
I have made this which copies files from my build folder to the output folder (bin/debug or bin/release). Works like a charm for me.
Nuspec file:
<package>
<files>
<file src="\bin\Release\*.dll" target="lib" />
<file src="\bin\Release\x64\*.dll" target="build\x64" />
<file src="\bin\Release\x86\*.dll" target="build\x86" />
<file src="MyProject.targets" target="build\" />
</files>
</package>
MyProject.targets
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<NativeLibs Include="$(MSBuildThisFileDirectory)**\*.dll" />
<None Include="#(NativeLibs)">
<Link>%(RecursiveDir)%(FileName)%(Extension)</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
You can use PowerShell and the Install.ps1 hook provided by NuGet.
See the documentation.
Via PowerShell you have to 'search' for the content element which includes your importantfile.xml in an attribute. When the script found it, it has to add <CopyToOutputDirectory>Always</CopyToOutputDirectory> as a child element.
<Content Include="importantfile.xml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
You can find some PowerShell snippets here. Just take a look at the .ps1 files.
You could try the following (not tested). The file has to be named Install.ps1 and copied into the tools folder:
param($installPath, $toolsPath, $package, $project)
# Load project XML.
$doc = New-Object System.Xml.XmlDocument
$doc.Load($project.FullName)
$namespace = 'http://schemas.microsoft.com/developer/msbuild/2003'
# Find the node containing the file. The tag "Content" may be replace by "None" depending of the case, check your .csproj file.
$xmlNode = Select-Xml "//msb:Project/msb:ItemGroup/msb:Content[#Include='importantfile.xml']" $doc -Namespace #{msb = $namespace}
#check if the node exists.
if($xmlNode -ne $null)
{
$nodeName = "CopyToOutputDirectory"
#Check if the property already exists, just in case.
$property = $xmlNode.Node.SelectSingleNode($nodeName)
if($property -eq $null)
{
$property = $doc.CreateElement($nodeName, $namespace)
$property.AppendChild($doc.CreateTextNode("Always"))
$xmlNode.Node.AppendChild($property)
# Save changes.
$doc.Save($project.FullName)
}
}
You should also check if everything is removed completely when uninstalling the package.
Note by Jonhhy5
When updating the package via update-package, Visual Studio warns that the project is modified "outside the environnment". That's caused by $doc.Save($project.FullName). If I click reload before the command is fully terminated, it sometimes causes errors. The trick is to leave the dialog there until the process finishes, and then reload the projects.
I have written a little tool called NuGetLib to automatically add files to the nuget package after build.
create a tools folder with your Install.ps1 script
build your nugetPackage
add the tools folder to the built nugetPackage
https://stackoverflow.com/a/47134733/6229375
I generate a nuget package from a project with this command in the post-build event. the variable %conf% is set to the right configuration (debug or release) and %1 is the project name (e.g. "MyCompany.MyProject").
nuget pack -Prop Configuration=%conf% "%1.csproj" -exclude *.sql -IncludeReferencedProjects
This package is for our own usage only, it will never be published on nuget. It ends in our private repository.
In the project, there is a file that is set to generate action : content and copy local : always. (My Visual Studio is in French, so I'm not 100% sure of the translation). Let's name it importantfile.xml.
In the generated package, I end up with this structure :
- content
- importantfile.xml
- lib
-net45 (.NetFramework,Version=v4.5)
-MyCompany.MyProject.dll
Which is fine, I want importantfile.xml to be deployed in the package, because, well, this file is important!
When I install the package in another project, importantfile.xml is deployed at the root of the project. That's OK. But it is not set to copy local : always.
I need importantfile.xml to be copy local : always in this project where I install my package.
How can I achieve that?
Notes :
I can set copy local : always on the file just after installing the package, that's no big deal. I would live with it if later updates of the package would let this property as-is, which is not the case. When updating the package, copy local is reset to never (as stated here).
There's a nuspec file in the project's folder, here it is :
<?xml version="1.0"?>
<package >
<metadata>
<id>$id$</id>
<version>$version$</version>
<title>$title$</title>
<authors>$author$</authors>
<owners>$author$</owners>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>$description$</description>
<copyright>Copyright 2014</copyright>
<tags>some random tags</tags>
</metadata>
</package>
Instead of using a PowerShell script another approach is to use an MSBuild targets or props file with the same name as the package id:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<None Include="$(MSBuildThisFileDirectory)importantfile.xml">
<Link>importantfile.xml</Link>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
In the nuspec file then, instead of adding the required files to the Content directory, add them to the Build directory along with the targets file.
Build
importantfile.xml
MyPackage.targets
lib
net45
MyAssembly.dll
If you require different content for different architectures then you can add architecture folders under Build also each with their own targets file.
Benefits to using a targets file over the PowerShell script with NuGet Content directory:
required content files aren't shown in the project in Visual Studio
content files are linked to rather than copied into the directory of each project which references the NuGet package (preventing there being multiple copies and keeping behaviour the same as for assemblies / libraries from NuGet packages)
PowerShell scripts only work in Visual Studio and aren't run when NuGet is run from the commandline (build servers, other IDEs and other OS), this approach will work everywhere
PowerShell install scripts are not supported in NuGet 3.x project.json system.
I know you guys got a working solution to this but it didn't work for me so I'm going to share what I pulled out of the NLog.config NuGet package install.ps1 (github source here).
NOTE: this is not my code, this is the content of the install.ps1 from
the NLog.config nuget package just sharing the knowledge.
It seems a little more straight forward to me and just hoping to help others that will likely stumble upon this.
You can find the accepted int values for BuildAction here and the accepted values for CopyToOutputDirectory here.
if the link breaks again
Fields
prjBuildActionCompile 1
The file is compiled.
prjBuildActionContent 2
The file is included in the Content project output group (see Deploying Applications, Services, and Components)
prjBuildActionEmbeddedResource 3
The file is included in the main generated assembly or in a satellite assembly as a resource.
prjBuildActionNone 0
No action is taken.
param($installPath, $toolsPath, $package, $project)
$configItem = $project.ProjectItems.Item("NLog.config")
# set 'Copy To Output Directory' to 'Copy if newer'
$copyToOutput = $configItem.Properties.Item("CopyToOutputDirectory")
# Copy Always Always copyToOutput.Value = 1
# Copy if Newer copyToOutput.Value = 2
$copyToOutput.Value = 2
# set 'Build Action' to 'Content'
$buildAction = $configItem.Properties.Item("BuildAction")
$buildAction.Value = 2
I have made this which copies files from my build folder to the output folder (bin/debug or bin/release). Works like a charm for me.
Nuspec file:
<package>
<files>
<file src="\bin\Release\*.dll" target="lib" />
<file src="\bin\Release\x64\*.dll" target="build\x64" />
<file src="\bin\Release\x86\*.dll" target="build\x86" />
<file src="MyProject.targets" target="build\" />
</files>
</package>
MyProject.targets
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<NativeLibs Include="$(MSBuildThisFileDirectory)**\*.dll" />
<None Include="#(NativeLibs)">
<Link>%(RecursiveDir)%(FileName)%(Extension)</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
You can use PowerShell and the Install.ps1 hook provided by NuGet.
See the documentation.
Via PowerShell you have to 'search' for the content element which includes your importantfile.xml in an attribute. When the script found it, it has to add <CopyToOutputDirectory>Always</CopyToOutputDirectory> as a child element.
<Content Include="importantfile.xml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
You can find some PowerShell snippets here. Just take a look at the .ps1 files.
You could try the following (not tested). The file has to be named Install.ps1 and copied into the tools folder:
param($installPath, $toolsPath, $package, $project)
# Load project XML.
$doc = New-Object System.Xml.XmlDocument
$doc.Load($project.FullName)
$namespace = 'http://schemas.microsoft.com/developer/msbuild/2003'
# Find the node containing the file. The tag "Content" may be replace by "None" depending of the case, check your .csproj file.
$xmlNode = Select-Xml "//msb:Project/msb:ItemGroup/msb:Content[#Include='importantfile.xml']" $doc -Namespace #{msb = $namespace}
#check if the node exists.
if($xmlNode -ne $null)
{
$nodeName = "CopyToOutputDirectory"
#Check if the property already exists, just in case.
$property = $xmlNode.Node.SelectSingleNode($nodeName)
if($property -eq $null)
{
$property = $doc.CreateElement($nodeName, $namespace)
$property.AppendChild($doc.CreateTextNode("Always"))
$xmlNode.Node.AppendChild($property)
# Save changes.
$doc.Save($project.FullName)
}
}
You should also check if everything is removed completely when uninstalling the package.
Note by Jonhhy5
When updating the package via update-package, Visual Studio warns that the project is modified "outside the environnment". That's caused by $doc.Save($project.FullName). If I click reload before the command is fully terminated, it sometimes causes errors. The trick is to leave the dialog there until the process finishes, and then reload the projects.
I have written a little tool called NuGetLib to automatically add files to the nuget package after build.
create a tools folder with your Install.ps1 script
build your nugetPackage
add the tools folder to the built nugetPackage
https://stackoverflow.com/a/47134733/6229375
I am working on a project which has many dependencies which are developed on a separate team from me. We use TFS 2010. Many of my applications depend on libraries and xml files which are under active development, so I want to keep them up to date. I also don't want to create separate copies of the dll's and xml files for each application/project, but rather source them from their respective locations within the same source control repository. This should be possible using a relative path.
I tried putting the following in my .csproj file
<ItemGroup>
<Dependencies Include="..\..\Driver\Driver.dll">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Dependencies>
</ItemGroup>
this doesn't work, either on my workstation or on the build server, however, the files show up as dependencies in the Solution Explorer, and it allows me to change the copy to output property and shows the full path the to files, which is valid.
Another thing I tried was just running xcopy as a pre-build event, which works on my local machine but does NOT copy the files to the output/TFS drop folder, so it isn't picking it up as a dependency.
Try using the Private Element instead, set to True.
<ItemGroup>
<Dependencies Include="..\..\Driver\Driver.dll">
<Private>True</Private>
</Dependencies>
</ItemGroup>
See http://msdn.microsoft.com/en-us/library/bb629388.aspx