This is going to be a quick but hopefully very useful tutorial on how to create a more complex template for the
dotnet new command line tool.
If you have ever build a Giraffe web application then you've probably started off by installing the giraffe-template into your .NET CLI and then created a new boilerplate application by running the
dotnet new command:
dotnet new giraffe
(There is currently a bug with the .NET CLI which forces you to specify the
Previously this would have created a new Giraffe web application which would have had the
Giraffe.Razor NuGet package included and a default project structure with MVC's famous Razor view engine.
Since today (after you've updated the giraffe-template to the latest version) you can choose between three different options:
giraffe - Giraffe's default view engine
razor - MVC Razor views
dotliquid - DotLiquid template engine
dotnet new giraffe command optionally supports a new parameter called
--ViewEngine or short
dotnet new giraffe -ViewEngine razor
If you are unsure which options are available you can always request help by running:
dotnet new giraffe --help
The output of the command line will display all available options and supported values as well:
Giraffe Web App (F#)
Author: Dustin Moris Gorski, David Sinclair and contributors
giraffe - Default GiraffeViewEngine
razor - MVC Razor views
dotliquid - DotLiquid template engine
If you do not specify a view engine then the
dotnet new giraffe command will automatically create a new Giraffe web application with the default
Creating multiple project templates as part of one dotnet new template
There are many ways of how I could have programmed these options into the Giraffe template, but none of them are very obviously documented in one place. The documentation of the dotnet templating engine1 is fairly scattered across multiple resources and hard to understand if you have never worked with it before. Part of today's blog post I thought I'll quickly sum up one of the options which I believed was the cleanest and most straight forward one.
Each view engine has a significant impact on the entire project structure, such as NuGet package dependencies, folder structure, code organisation and files which need to be included. I didn't want to hack around with
#else switches and introduce complex add-, modify- or delete rules and consequently decided that the easiest and least error prone way would be to create a complete independent template for each individual view engine first:
| +-- template.json
| +-- Views
| +-- WebRoot
| +-- Program.fs
| +-- AppNamePlaceholder.fsproj
| +-- Views
| +-- WebRoot
| +-- Program.fs
| +-- AppNamePlaceholder.fsproj
I split the content of the Giraffe template into three distinctive sub templates:
As you can see from the diagram there's still only one
.template.config\template.json file at the root of the
content folder and only one
The benefit of this structure is very simple. There is a clear separation of each template and each template is completely independent of the other templates which makes maintenance very straight forward. I can work on each template as if they were small projects with full Intellisense and IDE support and being able to build, run and test each application.
The next step was to create the
--ViewEngine parameter inside the
"description": "Default GiraffeViewEngine"
"description": "MVC Razor views"
"description": "DotLiquid template engine"
All I had to do was to define a new symbol called
ViewEngine of type
parameter and data type
choice. Then I specified all supported options via the
choice array and set the
giraffe option as the default value.
Now that the
ViewEngine parameter has been created I was able to use it from elsewhere in the specification. The
sources section of a
template.json file denotes what source code should be installed during the
dotnet new command. In Giraffe's case this was very easy. If the
giraffe option has been selected, then the source code shall come from the
Giraffe.Template folder and the destination/target folder should be the root folder of where the
dotnet new command is being executed from. The same logic applies to all the other options as well:
"condition": "(ViewEngine == \"giraffe\")"
"condition": "(ViewEngine == \"razor\")"
"condition": "(ViewEngine == \"dotliquid\")"
With this in place I was able to create a new
giraffe-template NuGet package and deploy everything to the official NuGet server again.
This is literally how easy it is to support distinct project templates from a single dotnet new template.
Different templates with same groupIdentifier
Another very similar, but in my opinion less elegant way would have been to create three different
template.json files and use the
groupIdentifier setting in connection with the
tags array to support three different templates as part of one. Unfortunately this option doesn't seem to be very well supported from the .NET CLI. Even though it works, the .NET CLI doesn't display any useful error message when a user makes a mistake or when someone types
dotnet new giraffe --help into the terminal. It also doesn't allow a default value to be set which made it less attractive overall. I would only recommend to go with this option if you need to provide different templates based on the selected .NET language, in which case it works really well again.
If you have any further questions or you would like to know more about the details of the Giraffe template then you can visit the giraffe-template GitHub repository for further reference.
This blog post is part of the F# Advent Calendar in English 2017 blog series which has been kindly organised by Sergey Tihon. Hope you all enjoyed this short tutorial and wish you a very Merry Christmas!
1) Templating engine documentation
Various documentation for the
dotnet new templating engine can be found across the following resources:
Over the last couple of months I have been pretty absent from my every day life, such as keeping up with Twitter, reading and responding to emails, writing blog posts, working on my business and maintaining the Giraffe web framework. It was not exactly what I had planned for, but my wife and I decided to take a small break and wonder through South America for a bit before coming back for Christmas again. It was a truly amazing experience, but as much as travelling was fun, it was not great for my open source project Giraffe which was just about to pick up momentum after the huge exposure via Scott Hanselman's and the Microsoft F# team's blog posts (huge thanks btw, feeling super stoked and grateful for it!).
Initially I did not plan to slow down on Giraffe, but it turns out that trying to get a decent internet connection and a few hours of quality work in between of adventure seeking, 16 hour bus journeys, one flight every 3 days on average, hostels, hotels, hurricanes and multi day treks in deserts and mountains is not that easy after all :) (who thought ey?).
As a result issues and pull requests started to pile up quicker than I was able to deal with them and things got a bit stale. Luckily there were a few OSS contributors who did a fantastic job in picking up issues, replying to questions, fixing bugs and sending PRs for new feature requests when I was simply not able to do so - which meant that luckily things were moving at least at some capacity while I was away (a very special thanks to Gerard who has helped with the entire project beyond imagination and has been a huge contributor to Giraffe altogether; I think it's fair to say that Giraffe wouldn't be the same without his endless efforts).
However, even though the community stepped up during my absence I was still the only person who was able to approve PRs and get urgent bug fixes merged before pushing a new release to NuGet, and that understandably caused frustrations not only for users, but also for the very people who were kind enough to help maintaining it.
As the owner of the project and someone who really believes in the future of Giraffe it became very apparent that this was not acceptable going forward and things had to change if I really wanted Giraffe to further grow and succeed in the wider .NET eco system. So when the community asked me to add additional maintainers to the project I did not even hesitate for a second and decided that it was time to evolve the project from a one man repository to a proper OSS organisation which would allow more people having a bigger impact and control of the future of Giraffe.
An OSS project is only as good as its community
giraffe-fsharp on GitHub and moved the Giraffe repository from my personal account to its new home. Furthermore I have initially added three more contributors to the organisation who now all have the required permissions to maintain Giraffe without my every day involvement. This doesn't mean that I don't want to work on Giraffe any longer or that I want to work less on it, but it means that I just wanted to remove myself as the single point of failure, which is very important for a number of reasons.
First there is the obvious point that if I'd disappear out of the blue for whatever reason it would have a huge impact on anyone who has trusted in the project and its longevity. If I'd be a large company where changing tech can be very expensive then I'd personally not be able to justify the usage of a project which could literally drop dead over night. It is a real concern which I understand and therefore try to address this with the transition to a proper OSS organisation. It's a first step to mitigate this very real risk and a commitment from my side to do whatever I can to keep Giraffe well and alive.
Secondly I can simply not imagine that I as a single person could possibly grow the project better or faster than a larger collective of highly talented developers who are motivated to help me. I would be stupid to refuse this offer and it's in my personal interest to help them to help me.
Thirdly and most importantly I don't want to lose the fun and joy of working on Giraffe. I strongly believe that .NET Core is an excellent platform and a future proof technology. I also believe that functional programming is yet to have its big moment in the .NET world and with F# being the only functional first language I think the potential for Giraffe is probably bigger than I can think of today. I think it's only a matter of time before its usage will outgrow my own physical capability of maintaining it and the last thing I want to happen is to burn out, have an OSS fatigue or completely lose motivation for it. The only way to avoid this from happening is by accepting the help of others and delegating more responsibilities to other people who share the same vision and have an interest in the project's success.
Therefore it makes me proud to have met these people and being able to expand Giraffe into a more structured organisation which I believe is the right way of addressing all these issues effectively.
More people need more structure
Now that there's more people using Giraffe and more people helping to maintain it I think the next important step is to further expand on a work flow which aims at providing at a minimum the same quality at which I would have maintained the project myself.
As such I have set up the following process to maximise quality, reduce risk and give orgaisations of all size the confidence of trusting into Giraffe:
- All development work has to happen on individual branches (enforced via GitHub)
develop branch must be at a releasable state at all times
- Only a finished bug fix or feature enhancement can be pushed via a pull request into
develop (enforced via GitHub)
- Each PR against
develop must be formally reviewed by at least one other core maintainer (enforced via GitHub)
- If enough PRs has flown into
develop and the team wants to schedule a new release then a pull request can be made against
- Only an owner (currently me) has permissions to approve a PR on
master and hence triggering a new automated release to NuGet (enforced via GitHub)
I know this might seem like a lot of process for a fairly new project, but I think it is very important to establish a good working structure early on to keep the quality high no matter how big the project will grow in the future. This process guarantees that at least 3 separate pair of eyes (two core maintainers and one owner) will review every single line of code before it makes into an official release. At the same time this process should hopefully allow a frictionless collaboration between core maintainers up until the point of an official release without the necessity of my involvement. Things like a vacation, illness or other forms of temporary unavailability should not be an issue any longer.
Never stop growing
All of this is only a first step of what I hope will be a very long future for Giraffe. Nothing is set in stone and if we discover better or more efficient ways of working together then things might change in the future and I will most certainly blog about it in a follow up post again.
One other thing which is perhaps worth noting is that I also plan to join the .NET Foundation or the F# Foundation in the near future. I'll talk more about this in a follow up blog post as well!
Can I join giraffe-fsharp?
Short answer is yes, but you have to have contributed to the project as an outside collaborator first and shown an interest and familiarity with the project before getting an invite to become a member of the organisation. I certainly don't see an upper limit of motivated people who want to help and any form of contribution is more than welcome. If you like Giraffe and want to participate in it then feel free to go and check out the code, discuss your ideas via GitHub issues and send PRs for approved feature requests which will definitely see your code (after reviews) merged into the repository.
Please also be aware that any member of giraffe-fsharp must have two factor authentication enabled on their account. I am big on security and I don't see why an OSS project should be treated any less serious than a project which would be run by a private company.
I hope this was a helpful insight into where I see the project going, what has happend lately and which steps I am taking to get Giraffe out of infancy and make it a serious contender among all other web frameworks in the wild (and not just .NET ;))!
Thanks to everyone who has helped, blogged, used or simply talked about Giraffe! There's no better feeling than knowing that people use and like something you've built and I now it's time that Giraffe becomes not only mine but a community driven project which we can grow together.
P.S.: If you ever have a chance to pack your stuff and travel the world then I'd highly recommend you to do so! It's one of the most amazing things one can do in life and probably for many people a one in a lifetime experience. Go and explore different places, meet people, learn about new cultures and experience life from a different perspective. It's eye opening and very educational on so many levels! Follow me on Instagram for inspiration ;)