The Directory.Build.props works almost like a "normal" csproj file. You can define default dependencies or even apply implicit usings. That is for example very convenient when you have a test project where you might always want to import Moq
and XUnit
namespace due to your setup.
How does it work?
It is simple. MSBuild will traverse from your current csproj path upwards to file-hierarchy and selects the first More to that later Directory.Build.props it can find and includes it. Have us a look at the following file-structure:
src/
+- Infrastructure/
¦ +- Infrastructure.csproj
+- Tools/
¦ +- Tools.csproj
+- Web/
¦ +- Web.csproj
+- Directoy.Build.props
Under our src
there is directly the Directory.Build.props and then we have some folders which contain the "real" projects.
Those "real" projects automatically import our Directory.Build.props.
Let us have a look at an example:
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<PackageReference Include="SonarAnalyzer.CSharp" Version="8.35.0.42613">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.406">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<AdditionalFiles Include="$(SolutionDir)\stylecop.json" Link="stylecop.json" />
</ItemGroup>
<PropertyGroup>
<CodeAnalysisRuleSet>$(SolutionDir)\stylecop.analyzers.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup>
<WarningLevel>5</WarningLevel>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
</Project>
This is taken directly from my blog github repository: here
So in our case Infrastructure.csproj
, Web.csproj
and Tools.csproj
will automatically include SonarAnalyzer.CSharp
and StyleCop.Analyzers
as nuget package. If I update the version in my Directory.Build.props all of them get automatically updated!
Another example:
Let's say you have the following test setup:
tests/
+- IntegrationTests/
¦ +- IntegrationTest.csproj
+- TestUtilities/
¦ +- TestUtilities.csproj
+- UnitTests/
¦ +- UnitTest.csproj
+- Directory.Build.props
As the nature of tests you will most likely use one test framework throughout all of your tests plus the same mocking framework. I personally love XUnit
for various reasons and Moq
as mocking library. Therefore I want to use them in all my test projects.
So my Directory.Build.props would look like this:
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.5.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0-preview-20220131-20" />
<PackageReference Include="Moq" Version="4.16.1" />
<PackageReference Include="xunit" Version="2.4.2-pre.12" />
</ItemGroup>
<ItemGroup Label="Implicit usings">
<Using Include="FluentAssertions" />
<Using Include="Moq" />
<Using Include="Xunit" />
</ItemGroup>
</Project>
Taken from here
Super convenient, isn't it?
Now let's say the Tools project does not need all this includes and stuff or in general: How can I exclude a project?.
That is quite easy as we are using the normal syntax we would use in a csproj: Condition
.
Taking the example from above, let us exclude the TestUtilities.csproj
:
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Condition="$(MSBuildProjectName) != 'TestUtilities'">
<PackageReference Include="FluentAssertions" Version="6.5.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0-preview-20220131-20" />
<PackageReference Include="Moq" Version="4.16.1" />
<PackageReference Include="xunit" Version="2.4.2-pre.12" />
</ItemGroup>
<ItemGroup Label="Implicit usings" Condition="$(MSBuildProjectName) != 'TestUtilities'">
<Using Include="FluentAssertions" />
<Using Include="Moq" />
<Using Include="Xunit" />
</ItemGroup>
</Project>
Also very simple. Just add the name of your project without the "csproj" file extension.
Multiple Directory.Build.props
What happens if we have multiple Directory.Build.props?
tests/
+- IntegrationTests/
¦ +- IntegrationTest.csproj
+- TestUtilities/
¦ +- TestUtilities.csproj
+- UnitTests/
¦ +- UnitTest.csproj
+- Directory.Build.props
Directory.Build.props
Now we have another Directory.Build.props one level above the "tests" folder. Well this one will not automatically get included. You have to tell MSBuild it should continue scanning. You can do this by adding this to the inner Directory.Build.props (the one under "tests").
<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../'))" />
Conclusion
Directory.Build.props gives you a handy tool the centralize common dependencies and configurations.
Further read here on the Microsoft documentation