all Technical posts

Explaining the Magic of the Arcus .NET Project Templates

The Arcus templates library contains several 'starting point' project templates you can use at the beginning of a project to eliminate boilerplate code and introduce best practices at the very first line of code.
Let's have a look how these project templates are set up so that the 'magic' factor disappears and you can help us improve it!

Introduction

The Arcus templates library already has several .NET templates to kickstart your project: web API, Azure Service Bus… and in the near future this will also include some Azure Functions project templates. This post will give you a quick overview of how these templates are created and how they’re tested so the next time you want to contribute, you’ll be more comfortable doing so.

Let me first of all share this .NET templating GitHub library too. It contains some basic introduction to .NET templates in general with some simple examples.

Project structure

The project structure of a .NET project template doesn’t differ much from a regular .NET project. Every file you want included in the end result, can be included in the project. It can be built and tested like any other project, but you always have to keep in mind that the project is only a ‘blueprint’ of the actual project. Consumers can always include options, names, etc to alter the end result of the project.

The thing that makes a .NET project a .NET template project is a single file called template.json which should be placed in a separate folder called .template.config/. This file controls the metadata of the .NET template like the name, description, and alias, but also the configurable options that are available. We’ll go deeper into this file in a later paragraph.

A simple .NET project template could look like this:

MyProjectTemplate/
├─ .template.config/
│ ├─ template.json
├─ appsettings.json
├─ MyProjectTemplate.csproj
├─ Program.cs

That’s all it takes. If you want to test this, you can run this command to install it on your system:
C\> dotnet new -i /MyProjectTemplate
And this command to create a new project from the template:
C\> dotnet new my-shortName

Note that the my-shortName is an alias you can set in the template.json. The end result project will include all the files you’ve added to the template, in this case including the appsettings.json and the Program.cs.

The template.json file

The most important and most complex part of the .NET project templates, is probably the template.json file. Looking at the reference for this file, you can see that there’s a lot of options and possibilities.

Let’s give you some idea of how this file is structured in the most basic sense:

Excluding binaries, debug symbols… from your end result project, that seems appropriate. This example doesn’t contain any additional options or modifiers but they’re all explained in the reference.
The list of possibilities is very large. For example, you can include or exclude files based on a consumer configurable option, include or exclude code from your code files, or add an additional NuGet package in a post-action when the end result project is build.

So, when there’s a change required in the metadata of the .NET project template, a new consumer option should be included, a file excluded from the end result project… the template.json is the file to go to.

Project options

When running the dotnet new [shortName] --help on a .NET project template you’ve selected, you’ll discover the available project options. These options can be added when you create a project from a template and this will in turn affect the end result project. It’s useful when you want to add additional features to your end result project but want to leave the consumer the choice to add it or not. Like whether or not the end result project should include a test HTTP endpoint or which kind of authentication system you want to use.

The options can be configured in the template.json file within the "symbols" tag. Why ‘symbols’? Well, each project option is transformed to a build symbol. In combination with the #if [condition] preprocessor directive, you can toggle features in or out based on the options the consumer pass along. Since they are symbols, you can also use them in the .csproj file and opt-in or opt-out of certain NuGet packages.

Let’s have a look at the following project option in the snippet below. It’s extracted from our web API project template to let the consumer decide whether or not the end result project should include the appsettings.json file or not.

If you look closely, you’ll see that the snippet actually contains two entries in the "symbols" object. The include-appsettings is the actual option you’ll use in the .NET command (as in dotnet new [shortName] --include-appsettings). The AppSettings is for internal use ('computed' means here that it’s being generated into a symbol not not available as a consumer option) so we can in our code make decisions on whether or not we can rely on the presence of a appsettings.json file.

We’ll use this build symbol AppSettings to determine if we should include the appsettings.json file to the IConfiguration registration, as shown in the following snippet. As you can guess, this section between the #if AppSettings will only be written to the end result project if the user initially specified the --include-appsettings project option. If not, then this section of code will not be present in the end result project.

Easter Eggs

While developing our templates, we’ve come across some ‘problems’ that we have solved by ‘misusing’ how the project options are working and how pieces of code can be added or removed in the end result project.

The first problem was that we wanted to add meta data information to the project template (like Arcus in the .csproj file) but that these tags should of course not be present in the end result project. We solved this by introducing an additional internal 'computed' symbol which is always false. If we then place every piece of meta data information within a condition based on the AuthoringMode symbol, all the meta data will be removed in the end result project.

The second problem was that we sometimes would place a certain piece of code within the #if DEBUG build configuration, so it would only build while developing. The problem here is that how the .NET project template options replacement works, is that this piece of code would be removed as it considers it as a project option. So we require something that would make sure that the #if DEBUG condition would be treated differently then other conditions (like #if AppSettings). This was also solved by ‘misusing’ the project options. The following snippet shows this.

You’ll see that we replace the //[#if DEBUG] and //[#endif] with #if DEBUG and #endif. This allows us to comment-out the DEBUG conditions in the project template and replace it when the end result project is created.

Template testing

Now that we’ve got some basic understanding of how these .NET project templates come together; we should take a look at how these templates can be tested. As you may have guessed unit testing in this environment is not appropriate as you only interact with the template from afar. Also, if we really want to test the .NET project templating from our Arcus project templates, we should do this during our test, and create a new temporary project from our template and see if the possible options we send to the template result in changes in the end result project.

So that’s what we did. We’ve considered our project template as a first class citizen in our code, while options translate to different arguments we send to that citizen. The internal workings of how this works, though, requires a whole separate post.

Let’s have a look at some integration tests of the web API project template:

This integration test verifies that if we add the --exclude-correlation project option, there’s no correlation headers in the HTTP response if we call an endpoint on our end result project. There’s a lot happening behind the scenes, but that’s all abstracted away so we can only focus on the things that matter.

The new WebApiProjectOptions().WithExcludeCorrelation() is a way to configure command line options. It literally translates to --exclude-correlation behind the scenes.
The WebApiProject.StartNewAsync() method is a way to generalize how we create a temporary project from a .NET project template. Behind the scenes, it will create a project for us based on our Arcus project template and send along our possible project options during the creation of the project. You’ll see that the object returned is an instance that allows us to interact with this temporary project. In this case, it calls the Health property which translates to the actual health API controller that’s by default available on a end result project when it’s created from our web API project template.

The test sends out a HTTP GET requests and expects the HTTP response not to have any of the correlation headers, like we’ve previously configured. This way of working is not only very efficient, it’s also very clear for the tester, the developer and the possible new contributor on the project. Maybe that contributor can be you.

Conclusion

We’ve looked at many things in this blog post. How our .NET project templates are structured, what’s the heart of every .NET project template, how project options can be configured and how this whole templating system can be tested in a reliable way. There’s many things that haven’t been discussed here, but it would lead us a bit too far if we would go over every single thing. But hopefully things that seemed “magic” or obscure to you before, are much clearer after reading this.

We hope that we’ll be seeing you soon on one of our Arcus libraries with ideas or questions. We’re always happy to see you!

Let me share the GitHub library of our Arcus .NET project templates so you can look at the code yourself and maybe inspire you to contribute: https://github.com/arcus-azure/arcus.templates

Thanks for reading!
Stijn

Subscribe to our RSS feed

Thanks, we've sent the link to your inbox

Invalid email address

Submit

Your download should start shortly!

Stay in Touch - Subscribe to Our Newsletter

Keep up to date with industry trends, events and the latest customer stories

Invalid email address

Submit

Great you’re on the list!