Constrain PackageReference upgrade version when update-package run - c#

Under .NET's older packages.config system for NuGet, I could constrain the possible versions of a package that are considered when packages are updated by using the allowedVersions attribute on the Package element
<package id="Newtonsoft.Json" version="10.0.3" allowedVersions="[10.0.3]" />
When update-package is run within Visual studio for a project including the above, no update will occur for Newtonsoft.Json because I've pinned to 10.0.3 using the allowedVersions attribute.
How can I achieve this under PackageReference? Applying semver syntax to the Version attribute only affects the version restored - it doesn't constrain updates. So if I specify the below PackageReference and run update-package, I will for example be upgraded to 11.0.1 if 11.0.1 is in my NuGet repository.
<PackageReference Include="Newtonsoft.Json" Version="[10.0.3]" />
Background
We rely on command line tooling to update packages because we have both fast moving internal packages (updated multiple times a day) and more stable low moving packages (eg: ASP.NET). On large codebases updating each dependency by hand in .csproj files is simply not scalable for us (and error prone). Under packages.config we can 'pin' the third party packages which we don't want upgraded and also update to the latest fast moving dependencies.

From this answer:
At the moment, this is not possible. See this GitHub issue for tracking.
The cli commands for adding references however support updating single packages in a project by re-running dotnet add package The.Package.Id.
From GitHub Issue 4358:
There is no PackageReference replacement for update yet, the command to modify references is only in dotnet.
You might want to weigh in on the open feature request GitHub issue 4103 about this (4358 was closed as a duplicate). Microsoft hasn't put a high priority on this feature (it was originally opened in October, 2016).
Possible Workarounds
Option 1
It is possible to "update" a dependency by removing and adding the reference. According to this post, specifying the version explicitly with the command will install the exact version, not the latest version. I have also confirmed you can add version constraints with the command:
dotnet remove NewCsproj.csproj package Newtonsoft.Json
dotnet add NewCsproj.csproj package Newtonsoft.Json -v [10.0.3]
What you could do with these commands:
Keep version numbers of packages around in a text file (perhaps just keep it named packages.config).
Use a script to create your own "update" command that reads the text file and processes each dependency in a loop using the above 2 commands. The script could be setup to be passed a .sln file to process each of the projects within it.
Option 2
Use MSBuild to "import" dependencies from a common MSBuild file, where you can update the versions in one place.
You can define your own <IncludeDependencies> element to include specific dependencies to each project.
SomeProject.csproj
<Project Sdk="Microsoft.NET.Sdk">
<IncludeDependencies>Newtonsoft.Json;FastMoving</IncludeDependencies>
<Import Project="..\..\..\Dependencies.proj" />
...
</Project>
Dependencies.proj
<Project>
<ItemGroup>
<PackageReference Condition="$(IncludeDependencies.Contains('Newtonsoft.Json'))" Include="Newtonsoft.Json" Version="[10.0.3]" />
<PackageReference Condition="$(IncludeDependencies.Contains('FastMoving'))" Include="FastMoving" Version="3.332.0" />
</ItemGroup>
</Project>

This has now been implemented as of https://github.com/NuGet/NuGet.Client/pull/2201. If you are using any version of NuGet 5, PackageReference semver constraints should now work as expected.

Pinning - Yet another workaround
This doesn't prevent the update but triggers a build error if one did and update. It won't help much with the use case of automated updates but it may help others that do manual updates and need some way of pinning.
<ItemGroup>
<PackageReference Include="MongoDB.Driver" Version="2.13.*" GeneratePathProperty="true" />
</ItemGroup>
<Target Name="CheckPkgVersions" AfterTargets="AfterBuild">
<Error Condition="!$(PkgMongoDB_Driver.Contains('2.13.'))" Text="MongoDB.Driver must remain at version 2.13.* to be compatible with MongoDB 3.4.21" />
</Target>

Related

Nuget package creation and file removal on uninstall

I am creating a package for use in dotnet core web applications.
As of recent, it would appear that Powershell scripts are no longer used to populate and remove files on Nuget package installation/ uninstallation. It would appear that it is all managed using MsBuild. I have successful migrations on installation. The files are populated on a Build target like so:
<Target Name="CopyMyAssets" BeforeTargets="Build">
<ItemGroup>
<MyAssets Include="$(MyAssetsPath)" />
</ItemGroup>
<Copy SourceFiles="#(MyAssets)" DestinationFiles="#(MyAssets->'$(MSBuildProjectDirectory)\App_Plugins\MyPlugin\%(RecursiveDir)%(Filename)%(Extension)')" SkipUnchangedFiles="true" />
</Target>
And have a removal method with a clean target:
<Target Name="ClearMyAssets" BeforeTargets="Clean">
<ItemGroup>
<MyPluginDir Include="$(MSBuildProjectDirectory)\App_Plugins\MyPlugin\" />
</ItemGroup>
<RemoveDir Directories="#(MyPluginDir)" />
</Target>
My question is, on package uninstall through the Nuget package manager, how do I ensure that these files are removed during the uninstall process? The user may not remember to clean before uninstalling, which will leave these orphaned files, and with no package to tell the project what to do, they will no longer be removed on subsequent cleans. Do I need to somehow invoke a project clean on uninstallation, or is there a way of scripting file removal? I have read the MsBuild documentation and I see no uninstall target or replacement for the old uninstall.ps1 scripts that used to be used.
Edit: Additional context. This is a package designed for Umbraco CMS 9 and up. I generated the project using Umbraco templates, but it doesn't differ hugely from the usual template. You use a .targets file to determine file transfers on build.

How to control output of a nuget package dependencies during build

I would like to support backward compatibility in my application.
Simply saying - one app needs to work using different versions of a dll depending on a flag which the app get's during runtime.
I've simplified everything and created a test solution with 2 projects in it.
Each project has it's own version of the same nuget package.
I picked System.Drawing.Common cause it has no dependencies.
ClassLibrary1 contains System.Drawing.Common of version 4.5.0.
ClassLibrary2 contains System.Drawing.Common of version 6.0.0.
Both projects have same output path:
<OutputPath>..\DEBUG\</OutputPath>
When I build my solution I get just one System.Drawing.Common.dll in my output folder:
Cause both dlls have one name and only version is different.
The desired behavior on the pictures below:
Distribute the nuget package dependencies into different folders according to versions.
Add suffix to the nuget package dependencies according to versions.
The idea is in controlling output of the nuget package dependencies.
Do you have any idea how I can achieve that ?
P.S. all other logic - resolving dependencies according versions etc is out of scope of this question.
It's possible.
First you need to add GeneratePathProperty to PackageReference element in csproj file
<ItemGroup>
<PackageReference Include="System.Drawing.Common">
<Version>4.5.0</Version>
<GeneratePathProperty>true</GeneratePathProperty>
</PackageReference>
</ItemGroup>
It allows us using $(PkgSystem_Drawing_Common) variable which contains a path to the nuget package.
Then we need to create a msbuild targets file
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="CopyNugetDll" BeforeTargets="BeforeCompile" Outputs="System.Drawing.Common.dll">
<XmlPeek XmlInputPath="$(ProjectPath)" Query="Project/ItemGroup/PackageReference[#Include='System.Drawing.Common']/Version/text()">
<Output TaskParameter="Result" PropertyName="NugetPackageVersion" />
</XmlPeek>
<ItemGroup>
<NugetrDll Include="$(PkgSystem_Drawing_Common)\lib\net461\System.Drawing.Common.dll" />
</ItemGroup>
<Message Text="Copying #(NugetrDll) to $(OutDir)" Importance="high" />
<Exec Command="copy $(PkgSystem_Drawing_Common)\lib\net461\System.Drawing.Common.dll $(OutDir)\System.Drawing.Common.$(NugetPackageVersion).dll" />
</Target>
</Project>
Here using xpath we select version from project.assets.json file and save it in NugetPackageVersion variable. Exec copy is used to copy the dll to a specific location with a specific prefix which contains a value from NugetPackageVersion variable.
Lastly you need to include msbuild targets file to a project
<Import Project="CopyDll.targets" />
This just isn't how package resolution works in .NET, you get one version of each package which is decided at restore time.
There may be some funky options if you have a very niche problem, but it sounds like maybe you're trying to solve a common problem in an uncommon way which is generally a bad idea.
Typically for the problem of backwards compatibility the onus is on the publisher of the library rather than the consumer of the library to make sure it all works by not making breaking API changes.

Specifying files to add to a nuget package in .csproj file

I am creating a nuget package from some code, but also need to deploy some tools with the package.
In a .nuspec file, I can do this with the <files> element, and this all works well.
However when using a .nuspec file, the packageReferences from the csproj file aren't included, and I am seeing some problems when including them manually (with the <dependencies> element).
The package created also always seems to restore as a .net framework package, even though it is targetting .net, as in this question.
I am hoping that all these problems would go away if I moved to using the .csproj format for specifying the nuget package details, but having read the docs I can't find out how to do it.
Does anyone know how it is done?
If not, can anyone shed any light on created a .net framework / .net core nuget package from a .nuspec file, that restores to the correct target version and respects package dependencies?
It's not easy to find/discover, but NuGet's MSBuild tasks docs page has a section called "including content in a package", which tells you about the PackagePath metadata on MSBuild items, that NuGet uses to copy files into the package.
So, in your csproj, you could have something like this:
<ItemGroup>
<None Include="..\MyTool\Tool.exe" PackagePath="tools" Pack="true" />
</ItemGroup>
and then your package will contain tools\Tool.exe. The Pack="true" attribute is required for None elements.
You can use MSBuild's globbing to copy entire directories, if that's easier. Include="..\MyTool\*". My MSBuild skills are not so advanced, so I don't know how to glob ..\MyTool\**\*, which means all files in all subdirectories, while maintaining the correct directory layout in the PackagePath="???" metadata. So the best I can suggest is one glob per directory.

Getting "System.Data.SqlClient is not supported on this platform" when launched as dotnet cli tool

We have a simple netcore 2.2 console application using DbContext from Microsoft.EntityFrameworkCore. When launched from console as is it works as expected.
However we decided to utilize it as a dotnet CLI tool. It's .csproj file contains:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.2</TargetFramework>
<AssemblyName>dotnet-dbupdate</AssemblyName>
<Title>Db Updater</Title>
<Version>1.0.1</Version>
<PackageId>DbUpdater</PackageId>
<Product>DbUpdater</Product>
<PackageVersion>1.0.1</PackageVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="2.0.0" />
</ItemGroup>
</Project>
We pack it to our Nuget server with dotnet pack. Then in a target folder we've got the following .csproj file:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.2</TargetFramework>
</PropertyGroup>
<ItemGroup>
<DotNetCliToolReference Include="DbUpdater" Version="1.0.1" />
</ItemGroup>
</Project>
From this folder we restore it and exec:
dotnet restore
dotnet dbupdate
And suddenly, on DbSet's ToList method invocation we receive:
System.Data.SqlClient is not supported on this platform
Definetely there is an issue with launching it as a dotnet CLI tool. However yet we failed to get what this issue is and how to solve it. Searching on the web did not give us any ideas what to try.
Try to specify the .NET version to use for CLI:
dotnet dbupdate --fx-version 2.2.4
If the above does not work, also try with the other version you have installed (2.2.2).
In essence the version of .NET used to run from CLI, the target SDK of the console app and the package versions of the dependencies all have to match for incompatibility issues to be avoided.
Just added compatibility v2 to local.settings.json:
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"FUNCTIONS_WORKER_RUNTIME": "dotnet",
"FUNCTIONS_V2_COMPATIBILITY_MODE": "true"
}
}
I have come across the same issue, this is happening because of the wrong version of System.Data.SqlClient is used, in my case application is built with .NET 4.6 and the class library in .Net Standard 2.0 which has a dependency on System.Data.SqlClient.
The issue is resolved after replacing the appropriate version of System.Data.SqlClient(.NET 4.6 in my case) in the production environment.
If you want to run it on Linux OS just make sure to publish your solution using the correct Target Runtime. For example pick linux-x64 if you want to run it on Red Hat Enterprise Linux.
Note that the ToList() method fires the SQL command, say, the first time which he uses the corresponding DLL. So, simply your error says that There's a dependency mismatch for Microsoft.EntityFrameworkCore.SqlServer or System.Data.SqlClient, you know that the second one is a dependency for the first one. There are some dependencies for the second one too, however I think your problem doesn't come from it.
Check out default references (versions) after packing and try to change to a suitable one. Unfortunately, We can't reproduce your problem, so try this solution and let us know the result.
Edit
According to dotnet-pack documentation :
NuGet dependencies of the packed project are added to the .nuspec
file, so they're properly resolved when the package is installed.
Project-to-project references aren't packaged inside the project.
Currently, you must have a package per project if you have
project-to-project dependencies.
Web projects aren't packable by default. To override the default
behavior, add the <IsPackable>true</IsPackable> property (inside
<PropertyGroup>) to your .csproj file.
I think you need to include the following inside your .csproj file :
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="2.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="2.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="2.0.0" />
<PackageReference Include="System.Data.SqlClient" Version="4.4.0"/>
</ItemGroup>
Also, no more need to use dotnet restore based on the following from the dotnet-pack documentation :
Starting with .NET Core 2.0, you don't have to run dotnet restore
because it's run implicitly by all commands, such as dotnet build and
dotnet run, that require a restore to occur. It's still a valid
command in certain scenarios where doing an explicit restore makes
sense.
If you are developing a project with Asp.net core 2.2 to pay attention to the following issues
The compiler cannot move the dll files to the destination folder when you change the target project settings (launch settings to cli) or type (as class lib to standard lib) or deleted some dipendecies from nuget section. (Compiler bug and microsoft corrected this in vs 2019 last 2 revision) You can try to move it manually but it's not guaranteed. Downgrading maybe a resulation in this case.
Asp.net framework core compiler looks primarily at the project file and other referenced modules take less priority.
dotnet restore and dotnet update could'nt provide the settings as according our changes. For example if you remove a package and made a -dotnet restore command, after look to the nuget dependecy part of the visual studio. They maybe still there.
For this reason, you try to place the Microsoft.EntityFrameworkCore.SqlServer to the final file of the final project.
solution with above
Self Contained application.
In this case, if you are using a DLL related to another application (such as a SQLBase driver), or you can put other nuget dependencies to your application by isolating these DLLs. This gives you the flexibility to work from other resources in the system. In your case you should do this for Microsoft.EntityFrameworkCore.SqlServer
<AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>
Please write comment if you dont agree

How can I use Microsoft.Net.Compilers at solution level?

I want to start using Microsoft.Net.Compilers to simplify work with our build server. However, I could only get it to work at a per-project level, by adding the package to all projects.
This is problematic because the package would have to be added to each new created project. This could lead to a case where the code compiles on the developer's machine (which has the latest compiler), but would fail on the build server. We have many projects (over 100), so this is relatively common.
Is there a way of using Microsoft.Net.Compilers at solution level?
If there is no supported way, is there a command line tool I would not have to install on the build server? Alternatively, is this not an intended usage of these tools?
If in VS 2017 (update 1, build numbers >= 15.1.*) you can use the MSBuild integrated PackageReference mechanism instead of packages.config which was previously only available for .net core and .net standard project types. See the PackageReference documentation as well as the NuGet blog post announcing the support, especially the section "What about other project types that are not .NET Core?".
The idea is to switch from installing a package and adding it to packages.config for restore to just specifying an MSBuild items in the csproj file. This can be set up for new projects in VS:
(animation is from the NuGet blog post linked above)
A new feature of MSBuild 15 is that it supports automatically including files in the directory hierarchy that have special names. Those are Directory.Build.props and Directory.Build.targets which will get included before (props) and after (targets) your project file's content (there is a bug with the .targets version for multi targeting projects for which a fix is about to be released).
If you create a Directory.Build.props file with the following content at the solution level, all projects in the directory hierarchy below it will inherit it's content and you can force a NuGet dependency onto each project:
<Project>
<ItemGroup>
<PackageReference Include="Microsoft.Net.Compilers" Version="2.1.0"/>
</ItemGroup>
</Project>
For existing projects in the solution:
Right-click your solution > Manage NuGet Packages for Solution...
... Or:
Tools > Library Package Manager > Manage NuGet Packages for Solution...
Then add it to all the projects by browsing for your package, then checking the top checkbox, and clicking install.
Source : https://stackoverflow.com/a/8653312/7007466
For the new projects:
Create a template mimicking the original one for each type of project you need (Console, Library etc...) and adding the package to it.
Create a project.
Note:
Use only valid identifier characters when naming a project that will be the source for a template. A template exported from a project named with invalid characters can cause compilation errors in future projects based on the template.
Edit the project until it is ready to be exported as a template.
As appropriate, edit the code files to indicate where parameter replacement should take place.
On the File menu, click Export Template. The Export Template wizard opens.
Click Project Template.
If you have more than one project in your current solution, select the projects you want to export to a template.
Click Next.
Select an icon and a preview image for your template. These will appear in the New Project dialog box.
Enter a template name and description.
Click Finish. Your project is exported into a .zip file and placed in the specified output location, and, if selected, imported into Visual Studio.
If you have the Visual Studio SDK installed, you can wrap the finished template in a .vsix file for deployment by using the VSIX Project template.
Source : https://msdn.microsoft.com/en-us/library/xkh1wxd8.aspx
If someone has an easier way than creating templates, I'll gladly take it.
On solution level, create CommonProjectBuildProperties.targets:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"
<ItemGroup>
<PackageReference Include="Microsoft.Net.Compilers" Version="4.2.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>
on each project, add:
<Import Project="../CommonProjectBuildProperties.targets"/>
this way you can manged any changes in one place

Categories

Resources