How do I get the Microsoft.Data.SqlClient.SNI native binaries copied to the bin directory of a web application that targets .NET Framework 4.7.2? - msbuild

How can I <Import> a .targets file that is included in <PackageReference>d NuGet package in a non-SDK-style web application project that targets .NET 4.7.2?
Background
The web application project is not an SDK-style project. It references a class library project in the same solution. This class library project is an SDK-style project. It has a package reference to Microsoft.Data.SqlClient version 4.1.0, which depends on Microsoft.Data.SqlClient.SNI.runtime 4.0.0. The latter contains native binaries which are automatically copied to the bin directory of the project.
The native binaries are not copied to the bin directory of the web application project. This is probably because of this:
SNI is the native C++ library that SqlClient depends on for various network operations when running on Windows. In .NET Framework applications that are built with the MSBuild Project SDK, native DLLs aren't managed with restore commands. So a ".targets" file is included in the "Microsoft.Data.SqlClient.SNI" NuGet package that defines the necessary "Copy" operations.
The included ".targets" file is auto-referenced when a direct dependency is made to the "Microsoft.Data.SqlClient" library. In scenarios where a transitive (indirect) reference is made, this ".targets" file should be manually referenced to ensure "Copy" operations can execute when necessary.
Recommended Solution: Make sure the ".targets" file is referenced in the application's ".csproj" file to ensure "Copy" operations are executed.
Source
Not sure what "built with the MSBuild Project SDK" means exactly, but I think my scenario qualifies.
Now I'm trying to implement the recommended solution, but I cannot get it to work.
What I did
Step 1: add package reference to Microsoft.Data.SqlClient.SNI version 4.0.0:
<PackageReference Include="Microsoft.Data.SqlClient.SNI">
<GeneratePathProperty>true</GeneratePathProperty>
<Version>4.0.0</Version>
</PackageReference>
I added <GeneratePathProperty> because of step 2 below. According to the documentation this should make a property available that can be used to refer to files in the package:
Sometimes it is desirable to reference files in a package from an MSBuild target. In packages.config based projects, the packages are installed in a folder relative to the project file. However in PackageReference, the packages are consumed from the global-packages folder, which can vary from machine to machine.
To bridge that gap, NuGet introduced a property that points to the location from which the package will be consumed.
Step 2: import the targets file:
<Import Project="$(PkgMicrosoft_Data_SqlClient_SNI)\build\net46\Microsoft.Data.SqlClient.SNI.targets" Condition="Exists('$(PkgMicrosoft_Data_SqlClient_SNI)\build\net46\Microsoft.Data.SqlClient.SNI.targets')" />
You can see that the package contains this targets file using the NuGet package explorer (link). The PkgMicrosoft_Data_SqlClient_SNI property is named according to the documentation (linked above) and based on their example:
MSBuild properties and package identities do not have the same restrictions so the package identity needs to be changed to an MSBuild friendly name, prefixed by the word Pkg.
Step 3: verify the tool versions on our agent:
MSBuild 16.11.2.50704 (installed on the agent C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Current\bin)
NuGet 6.1.0.106 (installed via a pipeline task)
According to the documentation linked above, these fulfil the minimum requirements (MSBuild 16 and NuGet 5).
Step 4: run nuget restore on our build agent and then build the project using the Azure DevOps MSBuild#1 task with msbuildArguments: '/t:Build'.
Step 4: collect output and publish as an artifact.
Outcome:
The SNI files are not present in the output. When building locally with MSBuild 17.0.0.52104 (installed in 'C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\MSBuild\Current\bin') I do see the SNI files.

Related

Consuming nuget package containing .targets file via PackageReference

I have .NET452 project - lets call it Consumer.csproj that I want to consume nuget lets call it SharedTargets that contained some custom targets files (SharedTargets.targets) from msbuild.
I'm using PackageReference format and now (compared to what it used to be) nuget packages are being restored to shared folder (%userprofile%.nuget\packages), and I'm not sure if it is good idea to reference it via that (doesn't feel right).
Eg:
<PackageReference Include="SharedTargets">
<Version>1.0</Version>
</PackageReference>
<Import
Project="$(USERPROFILE)\.nuget\packages\SharedTargets\1.0\SharedTargets.targets"
/>
Also this works only in VS, running this from command line (msbuild) I'm getting chicken-egg problem:
Confirm that the path in the <Import> declaration is correct, and that
the file exists on disk.
Obviously since I need to restore nuget first before I can use it :)
So question:
is there some more elegant way how to resolve path to the nuget package inside project file
is there a way how to make msbuild succeed (i.e. restore packages before SharedTargets.target is imported)
You shouldn't try to manually import targets distributed via NuGet.
Put your .targets file inside a build subfolder inside the package and name it SharedTargets.targets (package id + .targets) and NuGet will automatically include the targets - for packages.config projects it will modify the project file on install and for PackageReference projects the targets will be imported by modifying an implicitly generated targets file in the obj\ directory.

.net core msbuild nuget with additional assemblies

This sample shows a .NET Core project which can be packaged into a nuget package just using dotnet pack, and when restored in another project, it integrates in the msbuild pipeline. One of the great things about this sample is it creates a nuget package that integrates with msbuild on linux, mac and Windows. However, the custom build code doesn't have dependencies on any other assemblies.
How can I adapt this sample to use code that uses a dependency?
Here are my failed attempts:
Attempt 1
I added a package reference to Newtonsoft.Json and changed the code to do some JSON serialisation. However, in the project that uses the build nuget, when I do a dotnet publish, I get the following error:
error MSB4018: The "Zip" task failed unexpectedly. [C:\git\MSBuild-Features-With-Nate-McMaster\Video-2\1-NuGet\Web.csproj]
error MSB4018: System.IO.FileNotFoundException: Could not load file or assembly 'Newtonsoft.Json, Version=10.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed'. The system cannot find the file specified. [C:\git\MSBuild-Features-With-Nate-McMaster\Video-2\1-NuGet\Web.csproj]
Additionally, if my project didn't already have a dependency on JSON.NET, adding the build nuget would unnecessarily add it.
Attempt 2
I used nuget.exe spec to create a .nuspec file. At the end of the file, I added:
<files>
<file src="bin\Release\**" target="build" />
<file src="build\**" target="build" />
</files>
However, both "dotnet pack" and "msbuild /t:pack" ignore the file, and nuget.exe pack fails with the error Unable to find 'bin\Release\0-WriteATask\bin\Release\'. Make sure the project has been built..
If I try nuget.exe pack Zipper.nuspec or msbuild /t:pack /p:NuspecFiles=Zipper.nuspec, they both fail with the message Value cannot be null or an empty string..
Attempt 3
I edited the nuspec to remove all of the placeholders that are normally calculated from the project (any string starting and ending with a $). Then, doing a nuget.exe pack Zipper.nuspec created a nupkg file, and the net46 folder contains Newtsonsoft.Json.dll, but the netstandard1.3 folder does not.
The way MSBuild loads a task assembly can make it tricky to load additional assemblies that you may depend on.
Typically, the easiest way to solve this is to ship a copy of your dependencies inside your NuGet package. But your dependencies alongside your task assembly file in the package. There may be some additional complications that require you to use AssemblyLoadContext or the AppDomain.AssemblyResolve event.
You can do this without a nuspec file by forcing MSBuild to copy your assemblies into the local build output, and then copying them into your package. Set CopyLocalLockFileAssemblies=true, and add the items to _PackageFiles
Here's an example of how to do that: https://github.com/madskristensen/BundlerMinifier/blob/3333b5c38289a247391966443370ee6f4a29bf26/src/BundlerMinifier/BundlerMinifier.csproj#L35-L47
Hopefully, this will be addressed in the future, https://github.com/Microsoft/msbuild/issues/1312, and the task assembly resolution will use the NuGet cache.
Try it with the 9.0.1 version of Newtonsoft.Json, it worked for me, all these dll load problems went away, and it still targets .NET Standard. Although I did copy all the dependencies next to the task dll, but with the 10.x version even that didn't help.

What is the purpose of msbuild's GenerateRuntimeConfigurationFiles?

I upgraded a netcore1.1 project to the new VS2017/csproj.
In my test projects only, it added:
<PropertyGroup>
<GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
</PropertyGroup>
I did some digging to discover that it generates these files in the bin directory:
ProjectName.Tests.runtimeconfig.json
ProjectName.Tests.runtimeconfig.dev.json
What is this setting and these files, and why do I need them?
Why were they only generated for my test projects?
These are specific to .NET Core projects and specify
Which runtime and version to use. Typically Microsoft.NETCore.App. The "host framework resolver" looks for a matching folder inside the shared folder (e.g. C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.1.2). This is important since multiple runtimes can be installed side-by-side and the host needs to know which one to use when you run dotnet myapp.dll.
Additional options for the runtime. The most prominent is probably the garbage collection setting that switches between "desktop" and "server" mode. When you set <ServerGarbageCollection>true</ServerGarbageCollection> int he csproj file, this will cause a value in the runtimeconfig.json to be set. (This property is defaulted to true for web projects)
Additional options for the host. additionalProbingPath for example is set to your local NuGet cache which contains the restored packages. You may have noticed that referencing a NuGet package does not cause its dll files to be copied to the output directory (by default). The host uses the additional probing path to look for packages / dlls referenced to in this location (actually it is a two-step lookup: deps.json tells the host which packages to use and this property tells where to look for this package). Since this is only used for development and shouldn't end up in the published output (since this would mean relying on a NuGet cache on the target), this settings is put into a runtimeconfig.dev.json.
"Classic" .NET Framework projects also had a concept of letting the application set some runtime settings. This was accomplished by having an .exe.config file (which would be built from an App.config file in a project if present). You can think of runtimeconfig.json as "the new .exe.config" but the only have a few overlapping concerns.

'runtimes' Folder after Publishing a .Net Core App to Azure Publish Via VS Online

What is the purpose of the 'runtimes' folder that gets published along with all my project files? I have a VS Online account, and have the build/deploy process configured through there. The 'runtimes' folder is certainly not a folder that exists in source control or my project folder.
'runtimes' folder contents:
example contents:
Thanks,
Drew
Like #Gregory_Ott I was facing a similar issue where my deployment was an FDD deployment and the 'runtimes' folder was still being created. All I did was, mentioned the runtime and set the self-contained property to false.
In the following example I have used dotnet to publish:
dotnet publish -c Release -o ..\publish --runtime win-x64 --self-contained false
The following link should help you with the deployment:
https://learn.microsoft.com/en-us/dotnet/core/deploying/deploy-with-cli
These exist because you are building your application as a self contained application as opposed to one that is dependent upon the framework being installed on the machine executing it. Documentation can be found at https://learn.microsoft.com/en-us/dotnet/articles/core/deploying/ describing the various options that can be used to control this output.
If portable target runtime is selected, runtime folder is created.
FWIW, I was facing this situation with a .NET 5 application. An empty "runtimes" project was there in the output directory. After wasting a few minutes I realized that the folder was a left-over from a previous build. I deleted bin/obj folders completely and published again and the "runtimes" folder is no longer there in the output. I didn't have to change anything in the project file or build options. Hope it saves someone else a few minutes too.
Could this explain the existence of a runtimes folder in an FDD deployment:
A framework-dependent deployment with third-party dependencies is only
as portable as its third-party dependencies. For example, if a
third-party library only supports macOS, the app isn't portable to
Windows systems. This happens if the third-party dependency itself
depends on native code. A good example of this is Kestrel server,
which requires a native dependency on libuv. When an FDD is created
for an application with this kind of third-party dependency, the
published output contains a folder for each Runtime Identifier (RID)
that the native dependency supports (and that exists in its NuGet
package).
Source: https://learn.microsoft.com/en-us/dotnet/core/deploying/deploy-with-vs?tabs=vs156#framework-dependent-deployment
Setting your RuntimeIdentifier might be the solution. In my case, working with an Azure Function, it cut about 500 megs and reduced my archive down to 174 megs. This is important because on Consumption plans you get very limited storage.
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<LangVersion>preview</LangVersion>
<AzureFunctionsVersion>v4</AzureFunctionsVersion>
<OutputType>Exe</OutputType>
<!--
Prevents the runtimes folder from being created in publish, which contained 500 megs of runtime files we don't need.
-->
<RuntimeIdentifier>win-x86</RuntimeIdentifier>
<PublishReadyToRun>true</PublishReadyToRun>
</PropertyGroup>
In the publish profile in Visual Studio, if Target runtime is set to 'Portable' then all possible runtimes are generated. This is the default, so the output can be reduced by a more selective choice if applicable:

Command-line Package Service Fabric Application

Our continuous delivery set-up, until recently, was delivering Service Fabric packages using the following command:
msbuild SFApp.sfproj /t:Package
This was necessary because the target Package is unavailable at the solution level. I.e. The command
msbuild SFSolution.sln /t:Package
Fails, as the target does not exist.
As our dependency mesh grows, it gets to a point in which most interfaces projects will not build without a solution file (to work around the "OutputPath does not exist" red herring). There seems to be a way to do that according to this answer. Unfortunately, while targets like Clean work…
msbuild SFSolution.sln /t:SFApplication:Clean
(…snip…)
Build succeeded.
0 Warning(s)
0 Error(s)
…the target Package won't!
msbuild SFSolution.sln /t:SFApplication:Package
(…snip…)
Build FAILED.
"SFSolution.sln" (SFApplication:Package target) (1) -> SFSolution.sln.metaproj :
error MSB4057: The target "SFApplication:Package" does not exist in the
project. [SFSolution.sln]
0 Warning(s)
1 Error(s)
(Solution/project folders/names omitted/paraphrased for clarity. I can provide the actual logs if necessary.)
So the question is: how could I, using the Command Line, build one project using the Package target and the solution file?
Or how can I otherwise package a Service Fabric application from the command line?
It's bad idea to compile sfproj file(and any other project file) without sln, because it can bring wrong content to its output from referenced projects. Only solution has a knowledge about what project to compile in what configuration.
To make Package similar to "Right Click->Package" in VS:
Just add to your sfproj the following target
<Target Name="ForcePackageTarget" AfterTargets="Build" Condition="'$(ForcePackageTarget)' =='true'">
<CallTarget Targets="Package"/>
</Target>
And then running normal build on solution you may trigger the package step by /p:ForcePackageTarget=true :
msbuild yoursolution.sln /t:Build /p:ForcePackageTarget=true /p:Configuration=Release /p:Platform=x64
Actually it performs two-in-one steps, build and package, with respect to Solution Configurations on all referenced projects
MSBuild only supports a small set of target names that can be specified at the solution level. As you've discovered, Package is not one of them. You'll need to execute two separate calls to MSBuild: one which builds the solution and one which calls the Package target on the sfproj. The Package target of an sfproj has a dependency on the Build target so it will ensure that the sfproj and its project dependencies are built.
I had the same problem and fixed it by changing the Platform in the failing projects to explicitly build for x64.
Click Build > Configuration Manager and make sure that the assemblies are compiled for the x64 platform, that should also set the Output Paths in the corresponding .csproj files.
The actual command line action that is being executed is this:
"C:\Program Files (x86)\MSBuild\14.0\bin\amd64\msbuild.exe" "C:\agent\_work\1\s\Project\SFProject.sfproj" /t:Package /p:platform="x64" /p:configuration="release" /p:VisualStudioVersion="14.0"
Use the below script.
C:\Program Files (x86)\Microsoft Visual Studio 14.0> msbuild "Fabric.sfproj" /t:Package /p:Configuration=Release
Service fabric requires Target to be set in x64 platform,
So change all you reference projects target to x64 platform.
you can do this by using configuration properties of your solution. If x64 is not listed in 'Configuration Properties' click configuration manager in the same window and under platform column for the required project add new project platform as x64.
Hope this works for you.
We have had the exact same problem as you had and I have been looking around for a solution all over the web and did some experiments. Those are the steps that worked for us:
Don't manually add a target anywhere as suggested by other answers on StackOverflow. Not necessary. Especially in a CI environment, you want to build the projects separately anyways.
Prepare the projects in the Solution: Change the target platform for all projects to x64
Build the application
msbuild.exe SFAplication.xproj /p:Configuration=Release /target:rebuild
Package the App
msbuild.exe SFAplication.sfproj /p:Configuration=Release /target:Package