For years I’ve incorporated both package references — to my packages published on nuget — and project references to support libraries I’ve written in my C# projects. Why? Because I often find myself tweaking the support libraries as I use them, and taking time out to test the tweaks, publish them to nuget and update my project’s package reference a PITA. By incorporating both project and package references to the “same” library I end up with a faster workflow.
I’m not sure where I got the advice to do this. But while it works fine for me on my desktop and laptop, it really bollixes up anyone else trying to build my libraries on their machine. Because Visual Studio will complain bitterly about not being able to find the directory those project references are pointing at. The “solution” would be for other developers to download my entire support library source code base, and arrange things so their directory structure mimics mine. Which is, of course, totally ridiculous.
It turns out there’s a better way. I’m grateful to githux over on r/csharp for pointing me towards it.
It involves adding a special file to the root of your project directory: Directory.Build.props.
The Microsoft documentation on how to structure and use this file is sparse and confusing. Among other things, the docs state you should add the file to the root of your solution directory, which will result in it applying to every project in that directory. If that was the case it wouldn’t be usable in solving the problem I laid out above, since projects generally use unique sets of supporting libraries.
Fortunately, I found this article by Gary Woodfine. It correctly points out you can put Directory.Build.props in the root of a project directory. You just need to be careful, if you also have one in the solution directory, to link the two files together. I don’t need to do that for my purposes, so I won’t explain how to do it. Consult Gary’s article for more information. I very much appreciate Gary taking the time to explain critical details which Microsoft left out of the official docs.
Here’s an example of how I use Directory.Build.props. First, the csproj file:
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>net7.0-windows10.0.19041.0</TargetFramework> <TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion> <RuntimeIdentifiers>win10-x86;win10-x64;win10-arm64</RuntimeIdentifiers> <UseWinUI>true</UseWinUI> <Nullable>enable</Nullable> <AssemblyName>J4JSoftware.J4JMapWinLibrary</AssemblyName> <RootNamespace>J4JSoftware.J4JMapWinLibrary</RootNamespace> <Authors>Mark A. Olbert</Authors> <Company>Jump for Joy Software</Company> <Product>J4JSoftware J4JMapLibrary</Product> <Description>WinApp extensions to J4JMapLibrary</Description> <Copyright>© Mark A. Olbert all rights reserved</Copyright> <RepositoryType>git</RepositoryType> <RepositoryUrl>https://github.com/markolbert/ProgrammingUtilities</RepositoryUrl> <Version>0.8.1.0</Version> <AssemblyVersion>0.8.1.0</AssemblyVersion> <AppDesignerFolder>dep-props</AppDesignerFolder> </PropertyGroup> <ItemGroup> <PackageReference Include="CommunityToolkit.WinUI" Version="7.1.2" /> <PackageReference Include="CommunityToolkit.WinUI.UI.Controls.DataGrid" Version="7.1.2" /> <PackageReference Include="CommunityToolkit.WinUI.UI.Controls.Primitives" Version="7.1.2" /> <PackageReference Include="J4JSoftware.J4JMapLibrary" Version="0.8.1" /> <PackageReference Include="J4JSoftware.VisualUtilities" Version="2.1.1" /> <PackageReference Include="J4JSoftware.WindowsUtilities" Version="1.0.1" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="7.0.0" /> <PackageReference Include="Microsoft.WindowsAppSDK" Version="1.2.230313.1" /> <PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.756" /> </ItemGroup> <ItemGroup> <Content Update="media\rose.png"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </Content> </ItemGroup> </Project>
Notice that there are no project references in the csproj file. There’s also no information about whether or not to build nuget packages, apply licenses, reference readme files, nothing. That’s because all of that was migrated over to Directory.Build.props:
<Project> <PropertyGroup> <GeneratePackageOnBuild>true</GeneratePackageOnBuild> <PackageDescription>WinApp extensions to J4JMapLibrary</PackageDescription> <PackageLicenseExpression>GPL-3.0-or-later</PackageLicenseExpression> <PackageIcon>Diego nuspec.png</PackageIcon> <PackageReleaseNotes>changed how caching works, fixed nuget dependencies</PackageReleaseNotes> <PackageReadmeFile>readme.md</PackageReadmeFile> </PropertyGroup> <ItemGroup> <ProjectReference Include="..\..\ProgrammingUtilities\VisualUtilities\VisualUtilities.csproj" /> <ProjectReference Include="..\..\ProgrammingUtilities\WindowsUtilities\WindowsUtilities.csproj" /> <ProjectReference Include="..\J4JMapLibrary\J4JMapLibrary.csproj" /> </ItemGroup> <ItemGroup> <None Include="..\..\Media\JumpForJoy\Diego nuspec.png"> <Pack>True</Pack> <PackagePath></PackagePath> </None> <None Include="..\..\ProgrammingUtilities\ConsoleUtilities\GPLv3-2022.licenseheader" Link="GPLv3-2022.licenseheader" /> <None Include="docs/readme.md" Pack="true" PackagePath="/" /> <None Include="../LICENSE.md" /> </ItemGroup> </Project>
Notice the project references: they point to the same code base that I published to nuget. But by including the project references and the package references I guarantee I’m always working with the latest version of my support libraries.
Directory.Build.props also contains the instructions for how to create nuget packages, as well as references to various common elements (e.g., license files, icon files, general readmes) I include in my projects.
But up to this point you’re still going to cause problems for other developers trying to compile your code…unless you choose not to publish Directory.Build.props to your public git repository.
Which is exactly what I do, by configuring a solution’s .gitignore file to ignore any Directory.Build.props file. That keeps the contents of that file local to my development environment, allowing other developers to compile my code. Here’s the relevant section of my .gitignore file:
## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. ## ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore # User-specific files *.rsuser *.suo *.user *.userosscache *.sln.docstates # local development build options Directory.Build.props
By the way, in writing this post I realized I should move lines associated with user secrets over to Directory.Build.props, too, since they are unique to my local development environment. A developer’s work is never done… :)