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.
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