In this article, we are going to look into a global .NET tool called dotnet format, that helps with code styling in our .NET applications. We’ll explore the basic usage, how the role of .editorconfig is important, and how to make the best use of the dotnet format in our development process.

To download the source code for this article, you can visit our GitHub repository.

To kick things off, let’s discuss the basics of dotnet format.

What is dotnet format?

dotnet format is a tool that was added a while ago that we can install as a standalone tool. But as it now ships as part of the .NET 6 SDK, there is no longer a need to install anything. The tool is used to enforce .editorconfig rules or default rules to source code, such as whitespace rules, or analyzer/code-style rules.

Without coding guidelines, the codebase becomes inconsistent. Too much time in code reviews is spent on nitpicking coding standards, instead of focusing on important aspects like functionality. Tools like dotnet format help automate this process so we can set things up once based on the agreed standards, and then have more consistency in the future.

Before we get into the basic usage of dotnet format, in the next section we’ll discuss the role of .editorconfig, as that provides a nice history into why dotnet format was created, and how .editorconfig plays an integral role.

More About the .editorconfig File

.editorconfig is a file that helps provide consistency across multiple developers’ machines. Nowadays, there is a multitude of operating systems and IDEs, each with its own set of rules. The .editorconfig file helps standardize these rules across all machines.

Let’s inspect what a very basic .editorconfig file might look like:

[*]
indent_style = space

This rule ensures that all indents are treated as spaces. No, we’re not trying to spark a debate about tabs vs spaces 🙂

The problem with .editorconfig is that to apply these changes, we need to run a manual step in our IDE of choice. For example in Visual Studio, we need to do a “Code Cleanup” (Ctrl+K, Ctrl+E), or “Format Document” (Ctrl+K, Ctrl+D).

This creates several problems:

  • Every IDE/editor will have a different way of applying the changes
  • The developer needs to “remember” to run the command

dotnet format solves both these problems:

  • There is a standard way to apply the changes (running dotnet format)
  • We can run dotnet format as part of the build process, so a developer doesn’t need to remember to run the command

In the next section, we are going to set up a basic .editorconfig file, then show how to manually clean up code in Visual Studio.

dotnet format Setup

In order to set up dotnet format, we first need our .editorconfig file. So let’s go ahead and add it.

Adding the .editorconfig File

The easiest way to get up and running with .editorconfig is to do it within Visual Studio. Let’s create a new class library project, and then at the solution level choose “Add > New Item > .editorconfig file (.NET)”.

This will add an .editorconfig file at the solution level, with some default rules. This means that any projects added to the solution will adhere to this .editorconfig file. Of course,  we can add it to a single project but then the rules from the file will apply only to that project. In other words, .editorconfig files work based on the location of the file in the file system hierarchy, much like a .gitignore file.

If we double-click the .editorconfig file in Visual Studio, we will find a nice UI. Visual Studio now provides a visual editor for the .editorconfig file, to make it a bit easier to manage the configuration. If we prefer, we can right-click on the file and use the standard “Open With” dialog to see the raw file, or of course open the file from the file system.

We see that there are a number of defaults already set up for us, based on decisions from the Microsoft team. It’s a good starting point to help build out our standards in our teams.

Now that we have the .editorconfig file, let’s look at running dotnet format.

Running dotnet format Manually

Before we format any code, of course, we need some code to format 🙂 So, let’s add a new file to our existing project:

namespace DotnetFormatExample
{
    public class Calculator
    {
        public int AddNumbers(int one, int two)
        {
            int finalNumber = 0;

            finalNumber = one + two;

            return finalNumber;
        }
    }
}

Nothing too exciting. Let’s now open a command line at the project root, and run the following command:

> dotnet format

The command completes, and we notice that 1 change has occurred in our code. The int finalNumber = 0 statement was changed to var finalNumber = 0.

This is because of the var preferences rules in .editorconfig:

var preferences for dotnet format in editorconfig file

When we run the dotnet format command, the tool first searches for the .editorconfig file. Then it looks at the rules and our code and finally applies any changes. In this case, converts the int explicit type to var

There are a few arguments we can supply to the dotnet format command to control its usage. A useful one is the --verify-no-changes argument:

> dotnet format --verify-no-changes

After we run this command, we can inspect the output:

C:\Projects\CodeMazeGuides\dotnet-cli\DotnetFormat\DotnetFormatExample\Calculator.cs(7,13): 
warning IDE0007: use 'var' instead of explicit type 
[C:\Projects\CodeMazeGuides\dotnet-cli\DotnetFormat\DotnetFormatExample\DotnetFormatExample.csproj]

Our file in this case was also unchanged. This argument is useful when we want to understand when code breaks standards, but not automatically clean it up. For now though, let’s clean it up, and run dotnet format again as is.

In the next section, we’ll look at integrating dotnet format into the build process to automate this process for us.

Integrating dotnet format Into Build Process

Since dotnet format can be run from the command line, this opens up a number of possibilities, such as integrating into scripts and build pipelines or using a pre-commit git hook.

For our example, let’s integrate this into a GitHub Action build process.

Once we push our code up to a GitHub repository, we can head over to the “Actions” tab, choose the default “.NET” build action but change the dotnet-version to 6.0.x to match our class library. We should end up with the YAML file:

name: .NET

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/[email protected]
    - name: Setup .NET
      uses: actions/[email protected]
      with:
        dotnet-version: 6.0.x
    - name: Restore dependencies
      run: dotnet restore
    - name: Build
      run: dotnet build --no-restore
    - name: Test
      run: dotnet test --no-build --verbosity normal

Once we commit that change, our GitHub action should run and succeed.

Now, let’s modify the GitHub action to run dotnet format:

name: .NET

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/[email protected]
    
    - name: Setup .NET
      uses: actions/[email protected]
      with:
        dotnet-version: 6.0.x
        
    - name: Run dotnet format
      run: dotnet format --verify-no-changes
        
    - name: Restore dependencies
      run: dotnet restore
    
    - name: Build
      run: dotnet build --no-restore
    
    - name: Test
      run: dotnet test --no-build --verbosity normal

Notice here we introduce a special argument to the command: --verify-no-changes. This will terminate with a non-zero exit code if there are any rule violations. In other words, if someone submits a PR with some code that doesn’t conform to our standards, it’ll fail the build. 

Let’s try that out by opening a PR and changing that var x back to int x. The GitHub action will indeed fail, and we should notice that in the logs:

Run dotnet format --verify-no-changes
Warning: /home/runner/work/dotnet-format/dotnet-format/DotnetFormatExample/Calculator.cs(7,13):
warning IDE0007: use 'var' instead of explicit type 
[/home/runner/work/dotnet-format/dotnet-format/DotnetFormatExample/DotnetFormatExample.csproj]
Error: Process completed with exit code 2.

This is a more verbose way of using dotnet format than how we used it previously, as the onus of the change is on the developer, instead of silently doing a change the developer isn’t aware of. It also helps knowledge and skills, because it raises awareness of coding standards.

However, if we want to be fully automated and fix the code as part of the build pipeline, we could do that. But, that is outside the scope of this article as there is a bit of complexity to set up GitHub actions to do a commit based on the changes during the build. 

Conclusion

In this article we showed how dotnet format can automate the process of code styling, giving us a consistent experience for all developers regardless of code editor or operating system. As codebases grow and teams scale, consistency is crucial for the maintenance and efficiency of PR reviews, so this tool is a happy inclusion in our toolbelt.