The New MSBuild

MSBuild is the build engine for .NET and Visual Studio. It is written in such a way to be composable, allowing you to combine project files. If you have ever used Visual Studio, then you have indirectly used MSBuild. When you open Visual Studio, it is using MSBuild to restore, build, and manage your projects.

Recently, a new version of MSBuild has hit the streets. This came out along with Visual Studio 2017, and has changed design. Project files have been simplified, and developers are encouraged to edit them directly.

For .NET Core projects, Visual Studio now allows the direct editing of project files, without having to unload the project first. This is a big step for Visual Studio advocates.

A lot of systems can have dozens of projects in one solution. Synchronising package references, target frameworks, and metadata across all these projects can be hard. However, MSBuild is composable, and so we can make our lives easier.

Declaring A Project File

Let us define a very simple .csproj file:

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

  <PropertyGroup>
    <TargetFramework>netcoreapp2.0</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />
  </ItemGroup>

</Project>

Here we are targeting the .NET Core 2.0 framework, and have a package reference to ASP.NET Core 2.0.

This is the new MSBuild format used alongside .NET Core, and is only compatible with Visual Studio 2017 version. Luckily for us, Microsoft have provided CLI tooling alongside the new MSBuild system. To restore our packages, just run:

$ dotnet restore
  Restoring packages for msbuildexample.csproj...
  Generating MSBuild file .\obj\msbuildexample.csproj.nuget.g.props.
  Generating MSBuild file .\obj\msbuildexample.csproj.nuget.g.targets.
  Restore completed in 3.21 sec for .\msbuildexample.csproj.

We now have a fully functioning MSBuild project file. But what if we have multiple project files? how can we keep all ASP.NET Core versions synchronised between them?

Declaring a Property

Let’s modify our file, and add a property to it:

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

  <PropertyGroup>
    <TargetFramework>netcoreapp2.0</TargetFramework>
    <AspNetCoreVersion>2.0.0</AspNetCoreVersion>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.All" Version="$(AspNetCoreVersion)" />
  </ItemGroup>
  
</Project>

Two areas have changed. Firstly, we have defined a new property “AspNetCoreVersion”. We are using the syntax $(AspNetCoreVersion) to use this value. This results in exactly the same as what we achieved previously.

Using Multiple Files

When the complexity of a software system begins to rise, we start working with multiple projects. Two or more ASP.NET Core applications that make up a software system can share MSBuild properties by sharing a common.props file:

<!-- common.props file -->
<Project>
  <PropertyGroup>
    <AspNetCoreVersion>2.0.0</AspNetCoreVersion>
  </PropertyGroup>
</Project>

<!-- project1.csproj file -->
<Project Sdk="Microsoft.NET.Sdk.Web">

  <Import Project=".\common.props" />
  
  ...
  
</Project>

<!-- project2.csproj file -->
<Project Sdk="Microsoft.NET.Sdk.Web">

  <Import Project=".\common.props" />
  
  ...
  
</Project>

In the example, there is now a .props file. Both project1 and project2 share this file by importing it as a project reference with this line:

  <Import Project=".\common.props" />

Now we can change the version of the ASP.NET Core framework in all of our projects at once. Now all we have to do is change this common.props file version, and all of our projects will update in unison.

Summary

Projects can end up in a configuration nightmare. It can be laborious to update every project file within one solution with the latest dependency versions. What we can do is create a common.props file to house all of our dependency versions, and save us from dependency hell.

Due to MSBuild’s composable nature, we do not have to stop at just properties. We can also compose project files for common targets (steps in the build process), and even move an entire dependency into the shared project file (and a lot more).

I hope this helps.