~/codewithstu

How to make your csproj settings available via NuGet

Transcript

In this video I'm going to show you how to distribute your csproj settings via a NuGet package. Distributing settings via a NuGet package can be incredibly handy for things like setting InternalsVisibleTo and global usings amongst other things. The project I'm going to show you is available to my GitHub sponsors along with all past and future code as well as other tidbits as I work on them. See the link in the description or the one on screen now for more information.

In this solution we have two different projects. One that contains the settings we're going to distribute and the other imports our package as an example. Let's take a quick look at our settings project first. In the csproj you'll see a few settings that you might not be familiar with. The first is IncludeBuildOutput. I've set this to false because we don't want any of our build output to be shown in our destination package, although if you do have a DLL that you want to be included you need to omit this property. Setting DevelopmentDependency to true means that when this package is referenced it will not have its contents added to the reference project as part of its output. We'll see how this is done a little bit later on.

Next, inside of our item group we add our settings to two different folders. One of these is called build and the other one is called build multi-targeting. The build folder is imported when the project uses a single target framework, and build multi-targeting when you target multiple frameworks such as when you target .NET 5 and .NET 6.

Typically it's best to copy your files into both places. As we do this we explicitly look for two different files: one called PackageId.props and the other one called PackageId.targets. These are special files that when present in one of the previously mentioned build folders will be added to the csproj either at the start or at the end. Note this happens behind the scenes for you and I'll show you how this is done later. When you use a .props file it is added at the start of the csproj, and for a .targets file it is added right at the end. For this project I have not set a package ID, so the package ID will just be the name of my project: CodeWithStu.Build. This means that our targets file will need to be called CodeWithStu.Build.props or CodeWithStu.Build.targets. In this example I've used the PackageId.targets version because we want to apply our standard settings after everything else has been done by the user. This means we can do things like provide default values if they are not already filled in.

If we take a look inside of our targets file you can see that we import the properties.props file which is located inside of our build folder. When this is packaged in our NuGet package it will be in the same directory and we won't have the same folder structure as we do now. You can of course change the folder structure inside of your NuGet package as you desire.

So if we take a look inside of our props file now, you'll be able to see we set a load of different settings just to show you the power of this approach. Firstly, we set a PackageReadmeFile if it's not already been set. This is because nuget.org has recently added the ability to show a readme on the NuGet explorer pages. Next I show a couple of different ways of importing different attributes. First we have the older way of doing an AssemblyAttribute include for an InternalsVisibleTo. Then I show the new way of how to do an InternalsVisibleTo. Next we've got a couple of different ways of setting a global using statement, for example just a normal global using statement, an aliased one, or indeed a static one.

So now if we switch across to our test library project, you can see that I've included this RestoreSources element just to make it a little bit easier for me to develop locally. All this means is I don't have to set up a NuGet config with this in it, it just automatically restores based on this. Next we have the package reference. As you can see at the end we have this PrivateAssets equals All. This comes from the DevelopmentDependency attribute that we added to our other project earlier. What this means is that none of the output from this package will be added to the build, but its contents are available for the build itself, meaning we can access the properties in the targets file that we set earlier but it's not included in our build output for this project.

So to show you how this works in a little bit more detail, now I'm going to build this package. We can see a little bit better of what's happening by heading to the obj folder and then looking for our project.nuget.g.targets. Inside here we can see that we have an import statement and the project goes to the NuGet package root with our package at our version and then the path within the build folder. Because we're targeting a single framework here you can see our targets file. This file is one of the files that is looked at by the .NET toolchain when our project is built, so anything that's added into here such as the file above will be included as part of the build, unless the ExcludeRestorePackageImports property equals true.

So now let's take a look at the package itself. If I open up my folder and NuGet Package Explorer to look at the generated package, you can see that the authors element has been set for us, the readme has been set including the readme in the package contents, and you can see that we have no dependencies. This is the exact output that we wanted.

If you enjoyed this video, consider subscribing to the YouTube channel for more content like this.

// share_this