After creating a Roslyn analyzer package targeting .Net Standard 2.0, when I reference the package in another project, I receive the following error:
'C:\Users\username.nuget\packages\analyzer4\1.0.0.1\analyzers\dotnet\cs\Analyzer4.dll' depends on 'netstandard, Version=2.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51' but it was not found. Analyzers may not run correctly unless the missing assembly is added as an analyzer reference as well.
A repro of the project using the Analyzer is here. This repro is a vanilla .Net Core 2.0 console app with a reference to the analyzer and no other code whatsoever. The analyzer itself was built simply by creating the default Analyzer project in Visual Studio, changing it so that it targets netstandard2.0 instead of netstandard1.3, and then building in release mode to generate the .nupkg file. The analyzer does work properly, as the repro demonstrates, but the warning is generated.
There are discussions of this warning at various places on Github, such as here, but in those cases, the analyzer author had deliberately stripped out some local library code. In this case, the analyzer does not reference any other library.
I am not clear on what exactly it means to add an analyzer as an "analyzer reference" rather than just a regular project reference. I did try changing
<PackageReference Include="Analyzer4" Version="1.0.0.1" />
to
<Analyzer Include="..\LocalPackages\Analyzer4.1.0.0.1.nupkg" />
but that resulted in another error message ("PE image doesn't contain managed metadata").
Can anyone explain what this error means and ideally how to fix it?
Some background on this issue is here. When an analyzer depends on another assembly, then both must be listed as analyzers, but there is generally an exception for the core system assemblies. Unfortunately, it does not appear that .Net standard 2.0 has yet been added to the exceptions list; presumably, that will occur at some point in the future. I was able to make code changes to target the analyzer to .Net Standard 1.3 instead, thus avoiding the warning.
This problem will also arise when adding other assemblies (such as Newtonsoft.Json) into your analyzer. One solution to this is simply not to do so; for example, StyleCop eliminated its dependence on Newtonsoft.Json and simply includes the code for LightJson directly in its assembly. Other solutions might be (1) to manually copy the dll you are depending on (taking it from your packages folder if necessary) into the .nupkg file, recognizing that .nupkg is really just a .zip file; or (2) to use a tool like ILMerge to merge the dependency into your DLL. I have not yet experimented with these approaches, so if someone else can produce a step-by-step explanation of how to integrate this into a build for an analyzer, I will mark that as a correct answer.
You can use the approach in the Source Generators Cookbook (Thanks to #mbabramo for the link!).
<ItemGroup>
<PackageReference Include="Analyzer4" Version="1.0.0.1" />
</ItemGroup>
Becomes:
<ItemGroup>
<PackageReference Include="Analyzer4" Version="1.0.0.1" PrivateAssets="all" GeneratePathProperty="true" />
<None Include="$(PkgAnalyzer4)\lib\netstandard2.0\*.dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
</ItemGroup>
This should add the package dlls into your analyzer's folder, and it should work.
Related
I'm trying to experiment with a specific source generator library and I wanted to try and modify some parts of it. To do that cloned the repository for that library and referenced two .csproj files in it from my own .NET 7 project. This looks at first like it would work, VS Code recognized the imports and doesn't complain, but the actual source generator part does not seem to work.
The specific library I'm using is Mapperly and I referenced it in my .csproj file as following:
<ItemGroup>
<ProjectReference Include="..\mapperly\src\Riok.Mapperly.Abstractions\Riok.Mapperly.Abstractions.csproj" />
<ProjectReference Include="..\mapperly\src\Riok.Mapperly\Riok.Mapperly.csproj" />
</ItemGroup>
The error I get is
Partial method 'MyMapper.MapToReadModel(MyEntity)' must have an implementation part because it has accessibility modifiers.
This code compiles if I add the complete nuget package of this library, and it stops working with this error if I remove the nuget package and reference the projects as I've shown. My understanding of this error would be that it indicated that the source generator either did not generate any source, or the build process doesn't use the generated source for some reason. The way this library works is that you define partial methods for mapping, and the source generator fills in the actual code, so that last step seems to fail in this case.
Obviously the way I'm referencing this library makes it behave differently than what happens when I add the full nuget package. The repository has more .csproj file, but those are all tests or samples. It also has a .sln file, but it seems I can't reference that directly.
I'm also using VS Code while the information I could find on this often assume Visual Studio, not sure if there are any limitations here with VS Code only.
What is the proper way to reference a local library that contains a source generator so that it will work properly in VS Code? Or am I misunderstanding the problem here and the cause is something else entirely?
The library you are referencing is not just regular library but a source generator, you need to reference them in a special way. You can see an example in documentation for source generators. Also note how authors of this library reference their source generator project in their tests:
<!-- For local development, simply reference the projects. -->
<ItemGroup Condition="'$(MapperlyNugetPackageVersion)' == ''">
<ProjectReference Include="..\..\src\Riok.Mapperly\Riok.Mapperly.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
<ProjectReference Include="..\..\src\Riok.Mapperly.Abstractions\Riok.Mapperly.Abstractions.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="true" />
</ItemGroup>
I want to release a source generator package, and I want to include a private project dependency on the source generator project.
For example, assume that my projects are A.SourceGenerator and A.CodeAnalysis.Core. I want A.SourceGenerator to depend on A.CodeAnalysis.Core, but use ProjectReference instead of PackageReference.
This means that I want the following line or something similar in my A.SourceGenerator.csproj file:
<None Include="$(OutputPath)\A.CodeAnalysis.Core.dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
The issue is that this, or some similar variations, won't work and will produce a source generation error, under the CS8785 compiler warning, with the following error message:
Generator BenchmarkSourceGenerator failed to generate source. It will not contribute to the output and compilation errors may occur as a result.
Exception was of type FileNotFoundException with message:
Could not load file or assembly A.CodeAnalysis.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null or one of its dependencies. The system cannot find the file specified.
The above error is shown because the project's contents need to be locally copied on the directory that the source generator lies on. There is a trick for that when using PackageReference, as shown here, but it clearly does not apply for the case of ProjectReference.
I would prefer to avoid creating a brand new NuGet package that the source generator depends on, just to include the dependencies. The other solution is to not abstract that away and only have one source generator project including all source generators.
I don't have much knowledge about project files, but at least i can share some contents of my project file.
I had to reference other projects like this:
<ProjectReference Include="..\OtherProject.csproj" OutputItemType="Analyzer" />
I don't remember where i found this, but i hope it helps.
Update:
I had someone explain this to me and I hope I'll get it right:
Basically in order to not interfere with the rest of the Package, OutputItemType="Analyzer" causes C# Analyzers and SGs to be packed under analyzers/dotnet/cs.
But there it won't be able to locate it's normal dependencies.
So you add PackagePath="analyzers/dotnet/cs" to pack your package dependencies alongside your Analyzer/SG.
And to get your project dependencies packed to the right path, you can add OutputItemType="Analyzer" even though they might not be analyzers.
I have custom publish process which firstly merges some assemblies into one via ILRepack, then performs some other steps and finally cleans up publish directory - removes merged dependencies from APP_NAME.deps.json, relevant assemblies and symbol files.
In order to implement the last step, I've created a NuGet package with a custom MsBuild task.
According to Nate's blog post, I've set PrivateAssets="All" in order to ship all task's dependencies within the package:
<ItemGroup>
<PackageReference Include="JetBrains.Annotations" Version="2019.1.3" />
<PackageReference Include="Microsoft.Build.Utilities.Core" Version="15.9.20" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
<PackageReference Update="#(PackageReference)" PrivateAssets="All" />
</ItemGroup>
Package layout looks like:
Suddenly, during publish, this step fails with error:
task failed unexpectedly. Could not load file or assembly
'Newtonsoft.Json, Version=12.0.0.0, Culture=neutral,
PublicKeyToken=30ad4fe6b2a6aeed'. Could not find or load a specific
file. (0x80131621)
I can't understand why task tries to load version 12.0.0.0 while I have Newtonsoft.Json 12.0.2 (as specified in PackageReference).
Thank you for any help!
Updated:
According to this msbuild spec currently MSBuild tasks have serious limitations:
Tasks in MSBuild are dynamically loaded assemblies with potentially separate and colliding dependency trees. Currently MSBuild on .NET Core has no isolation between tasks and as such only one version of any given assembly can be loaded. Prime example of this is Newtonsoft.Json which has multiple versions, but all the tasks must agree on it to work.
As Lex Li mentioned in the comment, 12.0.2 is the NuGet package version which corresponds to the 12.0.0.0 assembly version. So the system attempts to load the right version of the assembly.
According to task-isolation-and-dependencies.md, related issue etc. custom MsBuild tasks have serious limitations.
MsBuild itself includes a dependency on Newtonsoft.Json 11.0.1, so custom tasks can't load any other version of this dependency.
Workarounds:
Use the same dependency version as MsBuild. I guess this approach is fragile and should not be used.
Create and package console application instead of a custom MsBuild task. I've chosen this approach because it is easily extensible and allows us to use any dependency version. Nate's blog post gives an overview of the approach.
As Martin Ullrich mentioned in the comment, we could use task with isolation boundaries. ContextAwareTask.cs demonstrates the approach.
I have a library project that extends some functionality on EntityFrameworkCore. I'm looking to support both 2.* and 3.*. My project is setup like so:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;netcoreapp3.0</TargetFrameworks>
[...]
</PropertyGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netcoreapp3.0' ">
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.0.0" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' ">
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="2.2.6" />
</ItemGroup>
[...]
</PropertyGroup>
</Project>
In the code I'm using the function EntityTypeExtensions.FindProperty(...). The signature of this function changes between 2.2.6 and 3.0.0.
The project's code (incorrectly?) uses the signature for 2.2.6. This compiles properly (which shouldn't be the case?) in both target frameworks.
I have a unit test project that multi-targets and has conditional references, much like the original project:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netcoreapp3.0;netcoreapp2.0</TargetFrameworks>
[...]
</PropertyGroup>
[...]
<ItemGroup Condition=" '$(TargetFramework)' == 'netcoreapp2.0' ">
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="2.2.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="2.2.6" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netcoreapp3.0' ">
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="3.0.0" />
</ItemGroup>
[...]
</Project>
All unit tests (incorrectly?) pass in both target frameworks.
Note that even though it builds and tests pass, when the library is used in a netcore3 project (which references efcore 3.0.0 directly) it throws the following exception. Which seems completely reasonable, I just don't understand why it allowed me to get to this point.
System.MissingMethodException: Method not found: 'Microsoft.EntityFrameworkCore.Metadata.IProperty Microsoft.EntityFrameworkCore.EntityTypeExtensions.FindProperty(Microsoft.EntityFrameworkCore.Metadata.IEntityType, System.Reflection.PropertyInfo)'.
Questions:
Is there a way around this so it gets picked up as an error/warning/something, at least, during the build?
Is the solution to this to use preprocessor directives around the call to .FindProperty(...) and based on the framework make the correct method call? Isn't there a way to do this based on the version of efcore instead of the dependency?
Is there a way to unit test this properly with the different packages? Right now as it is, I expected the unit tests to fail in one of the versions since the method does not exist.
Source repository and specifically the call to FindProperty can be found here.
Sample netcore3 project that results in a MissingMethodException when calling the library can be found here.
Stack trace of the exception can be found here.
I have good news and bad news. The good news is that the problem is with your package, and everything works just how you appear to believe it should work. The bad news is I don't know how your package got incorrectly authored.
Steps to verify:
Download Panner.Order version 1.1.0 from nuget.org (you've published 1.1.1 since asking this questions, which has the same, but different, problem). If you have NuGet Package Explorer installed, open the nupkg with that, expand the lib/ folder and double click each of the .dll files. Alternatively you can extract the nupkg as a zip file then use ILSpy or ILDasm or whatever else you want to inspect the assemblies. Notice that both the netstanard2.0 and netcoreapp3.0 assemblies have the same assembly references. In particular the Microsoft.EntityFrameworkCore.dll reference is for version 2.2.6.0, even though we'd expect the netcoreapp3.0 version to use version 3.0.0.0. Therefore I conclude that your netstandard2.0 assembly was copied incorrectly into the netcoreapp3.0 folder of your package. Your 1.1.1 package has the opposite problem. Both the netstandard2.0 and netcoreapp3.0 folders contain the netcoreapp3.0 assembly, so your package doesn't work with projects that try to use the netstandard2.0 assembly.
However, I have no idea why this happens. When I clone your repo and run dotnet pack and check the generated nupkg, I can see that the netstandard2.0 and netcoreapp3.0 assemblies have different references, so I'm confident that the package I generated locally should work. You need to investigate why the packages you publish are not being generated correctly.
To quickly answer your questions:
Is there a way around this so it gets picked up as an error/warning/something, at least, during the build?
It will, as the problem was not with the project, but with the package. If you multi-target your project and call an API that does not exist in at least one of the TFMs, you will get a compile error.
Is the solution to this to use preprocessor directives around the call to .FindProperty(...) and based on the framework make the correct method call? Isn't there a way to do this based on the version of efcore instead of the dependency?
When you call APIs that are different in different TFMs, yes, you can use #if to change your code per project TFM, as described in ASP.NET Core's docs when migrating to 3.0.
I'm going to ignore the "based on the version of efcore" because I'm a detail oriented person, and I don't want to write one thousand words for something that ultimately doesn't matter. The key is that in this scenario, you don't need to. You used conditions on your package references to bring in a different version of efcore per project TFM, so each time your project gets compiled, it's using a different version of efcore, but only one version per compile target. Therefore you don't need runtime selection of different versions of efcore.
Is there a way to unit test this properly with the different packages? Right now as it is, I expected the unit tests to fail in one of the versions since the method does not exist.
You multi-target your test project, but I see you've done that already. Since you're using a project reference, the test won't detect package authoring problems like what's happening.
If you really want to test the package, rather than your code, you could use a nuget.config file to add a local folder as a package source, then your multi-targeting test project references the package, not the project. You'd probably want to also use the nuget.config file to set the globalPackagesFolder to something that's in .gitignore because NuGet considers packages to be immutable and if a debug version of your package gets into your user profile global packages folder, every project you use on that machine (that uses your user profile global packages folder) will use that debug version, making it more difficult for you to make updates. For customers who want to test packages, rather than projects, I highly recommend using SemVer2's pre-release labels and create a unique package version for every single build to reduce the risk of testing a different version than you intend.
Using package reference rather than project reference is a pain, because it's no longer as simple as writing code and then running the test. You'll need to change code, compile the project that gets generated into a package, copy the package into the package source folder if you haven't automated that, update the package version in your test project, then compile and run the test project. I think you're better off keeping the project reference. Fix the package authoring problem and then trust the tooling works.
Not to directly answer all questions above one by one, just to describe the cause of the original issue and some suggestions.
In the code I'm using the function
EntityTypeExtensions.FindProperty(...). The signature of this function
changes between 2.2.6 and 3.0.0.
According to your description, I assume you may use code like EntityTypeExtensions.FindProperty(entityType, propertyInfo); in your original project.
For Microsoft.EntityFrameworkCore 2.2:
FindProperty (this Microsoft.EntityFrameworkCore.Metadata.IEntityType entityType, System.Reflection.PropertyInfo propertyInfo); second parameter=>PropertyInfo
For Microsoft.EntityFrameworkCore 3.0:
FindProperty (this Microsoft.EntityFrameworkCore.Metadata.IEntityType entityType, System.Reflection.MemberInfo memberInfo); second parameter=>MemberInfo
However, please check PropertyInfo Class, you'll find:
Inheritance: Object->MemberInfo->PropertyInfo
And I think that's the reason why the project's code uses the signature for 2.2.6 but it compiles properly in both target frameworks. And it's the cause of other strange behaviors you met after that...
So for this issue, you could use the signature for 3.0.0(MemberInfo) in code instead of 2.2.6(PropertyInfo) to do the test. I think the build will fall as you expected. And as Heretic suggests in comment, for multi-target project, use #if is a good choice.
Hope all above makes some help and if I misunderstand anything, please feel free to correct me :)
I have a solution containing several projects. Let's say PackageA and PackageB, where PackageB depends on PackageA with a ProjectReference.
Each project is set to also output a NuGet package on build. This process itself works perfectly but I am unable to specify a package version-range for individual builds.
E.g. I'd like to restrict PackageB to only refer to PackageA version 1.0.* (patch steps).
<Project Sdk="Microsoft.NET.Sdk" ToolsVersion="15.0">
<PropertyGroup
<TargetFrameworks>netstandard2.0;netcoreapp2.0;net46</TargetFrameworks>
<RootNamespace>PackageB</RootNamespace>
<Company>MyCompany</Company>
<Authors>John Doe</Authors>
<Description>This package depends on a specific version of PackageA.</Description>
<Version>1.1.0</Version>
<Copyright>Copyright © 2018 John Doe</Copyright>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\PackageA\PackageA.csproj" />
</ItemGroup>
</Project>
MSBuild seems to ignore any Version="1.0.*" or AllowVersion="1.0.*" arguments within the ProjectReference tag.
Is there a possibility to specify a version range without breaking the ProjectReference or using PackageReference?
Short Answer
No, there is no way to limit a project reference by a version attribute of that project.
Longer Answer
If you want your dependent package to vary independently from its dependency and limit the range of changes it will depend upon, you are very much in need of using a package reference rather than a project reference (yes, even if those projects are in the same solution).
Project Reference Now
When you reference a project, you're making a declaration to your IDE that you want to include the referenced projects design time state in your dependent projects design time state so that you can use it and see changes to it in your IDE before it's built. When your dependent project is built, its dependency is built too. So, A project reference is always a latest-version reference. You cannot reference a previous version of a project, but you can reference the versioned result of a project that was built previously.
Packing a Project Reference
In line with project references being built when the dependent project is built, when you pack a project with a dependency upon another project using a project reference, dotnet pack and nuget pack assume that you're going to also be packing each of those projects as packages as well, and writes the project reference as a package dependency at the same version of the dependent project package. So, if you pack projB # v1.2.3 the package will have a dependency reference to projA # v1.2.3. If you don't pack projA # v1.2.3 or you don't publish that package (because maybe there weren't any changes to it), consumers of projB # v1.2.3 will fail the install because nuget won't find projA # v1.2.3. If you're going to insist on using project references for packages, those referenced projects should also be packages that are versioned with their host (whether they change or not).
A minor exception to the above reference rule
The exception to project references listed as package dependencies of the same version as the host is a project reference that has its assets marked as private. In those situations you either need to create a build target that will include those assets in the package or have some other convention in place to deliver the dependency to the runtime. Using the private assets route does not allow you to do what you're asking, but it is an exception to the rule of project reference becoming a LISTED dependency of your package.
Existing NuGet targets don't support this directly. A couple of issues on GitHub (1, 2) requesting this functionality have been open for years. However, with a bit of MSBuild item trickery, I was able to 'extend' ProjectReference with two attributes, PackageVersion and ExactVersion:
<!-- MyProject.csproj -->
<Project Sdk="Microsoft.NET.Sdk" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
...
<ItemGroup>
<ProjectReference Include="..\MyProject1\MyProject1.csproj" PackageVersion="[1.1.0, 2.0.0)" />
<ProjectReference Include="..\MyProject2\MyProject2.csproj" ExactVersion="true" />
<ProjectReference Include="..\MyProject3\MyProject3.csproj" />
</ItemGroup>
...
<Target Name="UseExplicitPackageVersions" BeforeTargets="GenerateNuspec">
<ItemGroup>
<_ProjectReferenceWithExplicitPackageVersion Include="#(ProjectReference->'%(FullPath)')"
Condition="'%(ProjectReference.PackageVersion)' != ''" />
<_ProjectReferenceWithExactPackageVersion Include="#(ProjectReference->'%(FullPath)')"
Condition="'%(ProjectReference.ExactVersion)' == 'true'" />
<_ProjectReferenceWithReassignedVersion Include="#(_ProjectReferencesWithVersions)"
Condition="'%(Identity)' != '' And '#(_ProjectReferencesWithVersions)' == '#(_ProjectReferenceWithExplicitPackageVersion)'">
<ProjectVersion>#(_ProjectReferenceWithExplicitPackageVersion->'%(PackageVersion)')</ProjectVersion>
</_ProjectReferenceWithReassignedVersion>
<_ProjectReferenceWithReassignedVersion Include="#(_ProjectReferencesWithVersions)"
Condition="'%(Identity)' != '' And '#(_ProjectReferencesWithVersions)' == '#(_ProjectReferenceWithExactPackageVersion)'">
<ProjectVersion>[#(_ProjectReferencesWithVersions->'%(ProjectVersion)')]</ProjectVersion>
</_ProjectReferenceWithReassignedVersion>
<_ProjectReferencesWithVersions Remove="#(_ProjectReferenceWithReassignedVersion)" />
<_ProjectReferencesWithVersions Include="#(_ProjectReferenceWithReassignedVersion)" />
</ItemGroup>
</Target>
...
</Project>
Given package versions specified in other projects like this
<!-- ..\MyProject1\MyProject1.csproj -->
<!-- ..\MyProject2\MyProject2.csproj -->
<!-- ..\MyProject3\MyProject3.csproj -->
<Project Sdk="Microsoft.NET.Sdk" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Version>1.1.3</Version>
</PropertyGroup>
...
</Project>
the generated MyProject.nuspec file will contain the following dependencies:
<?xml version="1.0" encoding="utf-8" ?>
<package>
<metadata>
<dependencies>
<group targetFramework="...">
<dependency id="MyProject1" version="[1.1.0, 2.0.0)" />
<dependency id="MyProject2" version="[1.1.3]" />
<dependency id="MyProject3" version="1.1.3" />
</group>
</dependencies>
</metadata>
</package>
This useful target can be put into Directory.Build.targets to cover all projects in your solution.
As far as i know it's not possible with ProjectReference, however there are some open issues in this topic on Github, so it might happen that they will implement it sometime.
But for now this functionality is only enabled on PackageReference. Docs.
Well let's think that through shall we? The project may have a version number embedded in in it somewhere, but it's likely to be the latest or previous version, which might not even build, and there's no guarantee that a subsequent build step won't update that value. The point at which a build system produces a versioned artifact is near the end of the build, usually the last step, which is normally the packaging or publishing step.
If your project must limit version ranges for any of its dependencies, it should take dependencies on other packages, not the projects that build them. This provides a natural asynchronous set of workflows to feed into a single product.
If you want the convenience of having dependencies built to their latest, then you must keep all the projects in sync with each other wrt compatibility. Project dependencies really only make sense for developer builds, not CI builds.
One thing you should never do, is produce two different packages with the same version numbers. Visual Studio projects are broken by design in the area of versioning, as they default to using a static version string that must be set prior to the build. If you happen to forget to bump that number, you will violate this semantic versioning rule.
Even if the Nuget/VS devs give you what you are asking for, it's not a good solution. What if the the currently checked out project is for a version outside of the specified range? Assuming the devs can figure out what code to check-out of revision control, is that really what you want to happen on your dev box? Any solution they come up with will be complex and prone to errors. Even if you've got the version checked-out, Nuget can't know you didn't make a breaking change to it.
It's better to run independent pipelines of code, review, build, package, test and publish, using only published packages as dependencies.
Are you basing your question on how NodeJS versioning works (^ and ~)? In .NET that's not possible, and not necessary.
NodeJS needs this because, you know, it's javascript. Since javascript doesn't have strict type-checking, you need some way of verifying whether packages are compatible with each other. Some properties, methods might or might not exist on certain objects. So the only way the build system (node) can verify this is through the package version selectors.
As I said, in .NET we don't need this, because it's a strict programming language. If a field, property or method doesn't exist on a class, the project simply won't build.