Directory.Build.props – why and how to use it?

Introduction

In programming, effective project configuration management is key to maintaining consistency and facilitating teamwork. One tool that makes it possible to consolidate configuration across multiple projects is the Directory.Build.props file. In this article, we will take a closer look at how it works, what it is used for, and consider the advantages and disadvantages of using it.

Customizing the building process in .NET

In the .NET platform, there are several ways to customize the project construction process. One of them is the contents of the .csproj file, which can take the following form:

<Project Sdk="Microsoft.NET.Sdk">

    <PropertyGroup>
        <TargetFramework>net8.0</TargetFramework>
        <ImplicitUsings>enable</ImplicitUsings>
        <Nullable>enable</Nullable>
    </PropertyGroup>

    <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">  
        <TreatWarningsAsErrors>true</TreatWarningsAsErrors>  
    </PropertyGroup> 
 
    <PropertyGroup>  
        <NoWarn>NU1507</NoWarn>  
    </PropertyGroup>

</Project>

In the case where we have one project in the solutiom (and therefore one .csproj file) it is not a problem to make changes in it.

The problem begins to arise when more projects are created in the solution and the IDE does not allow us to click out all the changes we want to make. Then we have to edit the .csproj manually, and to keep the projects consistent we have to repeat the operation for each file.

After a while, this can cause frustration, lots of duplicated lines of code and wasted time on these operations.

Directory.Build.props as a solution

We find it helpful to use Directory.Build.props, a configuration file automatically imported by MSBuild when the project is built. This file is used to define default settings and properties common to all projects in a specific directory and its subdirectories. MSBuild searches the directory structure on its own, finding the Directory.Build.props file.

All you need to do is move all the contents of the .csproj file shown above to the Directory.Build.propsfile and you can leave the .csproj empty. 

You can have multiple Directory.Build.props files at different directory levels within a single solution.

Assuming that our solution structure looks like this:

\
  MySolution.sln
  Directory.Build.props     (1)
  \src
    Directory.Build.props   (2-src)
    \Project1
    \Project2
  \test
    Directory.Build.props   (2-test)
    \Project1Tests
    \Project2Tests

For Project1, Directory.Build.props (2-src) will be used, and for Project1Tests Directory.Build.props (2-test) .

Merging the contents of multiple Directory.Build.props

If we want to combine the contents of multiple Directory.Build.props files from different levels then we can add the following code to the .csproj file:

<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../'))" />

For example:

If we add the above code to Project1 then the contents of the following will be merged

  • Directory.Build.props (2-src)
  • Directory.Build.props (1)

Summary

Pros:

  • Consolidation of configuration: makes it easier to manage common settings for multiple projects.
  • Inheritance and consistency: properties are inherited, ensuring consistency between projects.
  • Simplicity of configuration: global settings are defined centrally, simplifying project files.

Cons:

  • Problems with the compilation process: the large number of Directory.Build.props files can make it difficult to understand the project compilation process.
  • Additional time overhead: searching the directory structure can introduce additional time overhead during compilation

Personally, I use this solution and I can say that the number of places that need to be updated having, for example, 20 projects in one solution has decreased to 2-3 Directory.Build.props files, which realistically translates into saved time and introduces simplicity in managing things like sdk version.