DEV Community

Cover image for MSBuild and Project Files
Randa Zraik
Randa Zraik

Posted on

MSBuild and Project Files

What is MSBuild?

MSBuild (Microsoft Build Engine) is a build system and platform for building applications, primarily in the .NET ecosystem. It orchestrates how code is compiled, tested, packaged, and deployed by processing XML project files like .csproj, .fsproj and .vbproj. Visual Studio uses MSBuild, but you can use MSBuild without Visual Studio to build .NET applications.


Key Features of MSBuild

  1. Project Building:
    • Compiles your source code into Intermediate Language (IL) and packages it into binaries (.dll, .exe).
    • Resolves project dependencies (e.g., NuGet packages) and includes them in the build process.
    • Executes additional build tasks (e.g., running tests, creating packages, deployment tasks).
  2. XML-Based Configuration:
    • Uses XML-based project files to define build instructions in a clear and extensible format.
    • Project files are used to define build steps, configurations, dependencies, and more.
  3. Highly Customizable:
    • You can write custom targets and tasks to extend its functionality.
  4. Integrated with .NET CLI:
    • Commands from .NET CLI like dotnet build, dotnet restore, and dotnet publish use MSBuild under the hood to build projects.
    • Visual Studio has a built-in support for MSBuild. For editors that are not integrated with MSBuild like VS Code and Zed, the .NET CLI is commonly used to manage builds.
  5. Build Automation and CI/CD Integration:
    • Integrates with CI/CD systems like GitHub Actions, Azure Pipelines, and Jenkins to automate builds, tests, and deployments.
    • Defines build and deployment pipelines entirely in MSBuild scripts.
  6. Cross-Platform:
    • Initially Windows-only, MSBuild became cross-platform starting with .NET Core, allowing builds on Linux and macOS.
    • Ensures the same build logic works across operating systems, making it ideal for CI/CD pipelines.

MSBuild Project File

MSBuild processes an XML-based project file format which is configured to describe build items, configurations, and reusable build rules for consistency across projects.
There are different types of project files such as .csproj for C# projects, .fsproj for F# projects and .vbproj for visual basic projects.


MSBuild Project File Structure

The following is an overview of the key elements that make up an MSBuild project file, explaining how each contributes to the build process and project configuration.

File Root

  • The project file begins with the <Project> root element which acts as the container for all other elements.

    <Project>
    </Project>
    
  • Modern .NET projects use SDK-style projects, where the SDK specifies a predefined set of build logic, properties, and imports.

  • Some available SDKs:

    • Microsoft.NET.Sdk: For console apps or libraries.
    • Microsoft.NET.Sdk.Web: For web projects lika Web APIs or MVC apps.
    • Microsoft.NET.Sdk.Worker: For worker services and background jobs.
    • Aspire.AppHost.Sdk: For Aspire app host.
    • MSTest.Sdk: For MSTest apps.
  • Ways of declaring SDK:

    • Inline SDK declaration:

      <Project Sdk="Microsoft.NET.Sdk">
      </Project>
      
    • Using the <Sdk> element:

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

Properties

  • Key-value pairs used to configure builds and global settings like target framework, build configuration, and output paths.
  • They are defined within a <PropertyGroup>. Multiple <PropertyGroup> sections can be added.

    <PropertyGroup>
      <TargetFramework>net9.0</TargetFramework>
    </PropertyGroup>
    
  • Conditions can be specified to dynamically enable properties:

    <PropertyGroup Condition="'$(Configuration)' == 'Release'">
      <Optimize>true</Optimize>
    </PropertyGroup>
    

    Examples on properties:

  • Target Framework

    • <TargetFramework> used to specify a single .NET version and <TargetFrameworks> for multiple versions:

      <TargetFramework>net9.0</TargetFramework>
      <TargetFrameworks>net9.0;net40;net45</TargetFrameworks>
      
    • Common TFMs: net9.0, net8.0, netstandard2.1, netcoreapp3.1, net481. OS-specific TFMs (e.g., net5.0-windows, net6.0-ios) include platform-specific bindings.

    • You can add to source code preprocessor directives for conditional compilation by framework:

      #if NET40
      Console.WriteLine("Target framework: .NET Framework 4.0");
      #endif
      
  • Implicit using Directives

    • Starting with .NET 6, C# projects automatically include commonly used namespaces via implicit global using directives, reducing the need to manually add them.
    • Enabled by default for SDKs like Microsoft.NET.Sdk, Microsoft.NET.Sdk.Web, Microsoft.NET.Sdk.Worker, and Microsoft.NET.Sdk.WindowsDesktop.
    • <ImplicitUsings> is used to enable/disable the feature:

      <PropertyGroup>
        <ImplicitUsings>enable</ImplicitUsings>
      </PropertyGroup>
      
    • Additional implicit global using directives can be specified by adding Using items to project file:

      <ItemGroup>
        <Using Include="System.IO.Pipes" />
      </ItemGroup>
      
  • Compiler and Code Analyzer Warnings

    • <TreatWarningsAsErrors>: Converts all compiler warnings into errors.
    • <WarningsAsErrors>: Converts specific compiler warnings into errors.
    • <CodeAnalysisTreatWarningsAsErrors>: Converts code analysis warnings into errors.
    • <NoWarn>: Suppresses specific warnings and doesn't show them in build outputs.

      <PropertyGroup>
        <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
        <WarningsAsErrors>CS0168</WarningsAsErrors>
        <CodeAnalysisTreatWarningsAsErrors>true</CodeAnalysisTreatWarningsAsErrors>
        <NoWarn>CS2002</NoWarn>
      </PropertyGroup>
      

Items

  • Specify inputs to the build process, such as source files, packages, dependencies, and resources.
  • They are defined within a <ItemGroup>. Multiple <ItemGroup> sections can be added.
  • For simplicity, use dotnet add package to add a package instead of manually adding it to the project file.

    <ItemGroup>
      <PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
      <ProjectReference Include="..\OtherProject\OtherProject.csproj" />
      <Compile Include="Program.cs" />
    </ItemGroup>
    

Tasks

  • Individual steps within targets to perform certain actions.
  • MSBuild includes built-in tasks (e.g., Copy, Exec, MakeDir, Csc) and supports custom ones (by implementing ITask or deriving from the helper class Task)

    <Target Name="CustomTarget">
      <Exec Command="dotnet restore" />
      <Copy SourceFiles="README.md" DestinationFolder="bin\docs\" />
      <Csc Sources="@(Compile)" OutputAssembly="bin\MyApp.dll" />
    </Target>
    

Targets

  • Group tasks and define sections of the project file as entry points for the build process.
  • BeforeTargets, AfterTargets and DependsOnTargets attributes can be used to order targets.

    <Target Name="PreBuild" BeforeTargets="PreBuildEvent">
      <Exec Command="echo pre build" />
    </Target>
    
    <Target Name="PostBuild" AfterTargets="PostBuildEvent">
      <Exec Command="echo post build" />
    </Target>
    
    <Target Name="PostPostBuild" DependsOnTargets="PostBuild">
      <Exec Command="echo post post build" />
    </Target>
    

C# Web API Project Example

Lets create a dummy C# Web API project from scratch, without using Visual Studio nor dotnet new command to generate a template. Instead, we’ll manually create the required files, use the dotnet CLI to build and run the project, and test it using a simple HTTP request. Feel free to use your favorite editor to edit the files.

  1. Create the project directory:

    mkdir DemoApp
    cd DemoApp
    
  2. Create the .csproj file:

    vim DemoApp.csproj
    
  3. Add the following content to the .csproj file - <TargetFramework> is required:

    <Project Sdk="Microsoft.NET.Sdk.Web">
    
      <PropertyGroup>
        <TargetFramework>net9.0</TargetFramework>
        <ImplicitUsings>enable</ImplicitUsings>
      </PropertyGroup>
    
    </Project>
    
  4. Create the Program.cs File:

    vim Program.cs
    
  5. Add the following content to Program.cs to define a simple /welcome API endpoint.

    var builder = WebApplication.CreateBuilder(args);
    var app = builder.Build();
    
    app.MapGet("/welcome", () => "Hello, you!");
    app.Run();
    
  6. Build the project - The output binaries will be placed in the bin/Debug/net9.0 directory by default.

    dotnet build
    
  7. Run the project - The output will indicate that the app is listening on: http://localhost:5000.

    dotnet run
    
  8. Test the project - The output should be: Hello, you!.

    curl http://localhost:5000/welcome
    
  9. Final output via Zed editor:

Web API Project via Zed


And that's all! You can play around .csproj configurations and explore other properties.


Learning Resources

Refer to the following resources if you would like to learn more about MSBuild and project files:

Top comments (0)