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 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:
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/checkout@v3 - name: Setup .NET uses: actions/setup-dotnet@v2 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/checkout@v3 - name: Setup .NET uses: actions/setup-dotnet@v2 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.