We’re offering an optional new way to template Optimizely CMS sites. It's been in open beta for a while, but we're ready for its "official" unveiling.
TLDR
You can now template a CMS project in Liquid, instead of Razor.
Liquid is a common templating language. It doesn't allow raw C#, and the templates can be detached from the Visual Studio project and be stored elsewhere -- on the file system, in the cloud, as managed content, in...Dropbox? Wherever.
Additionally, some Liquid extensions make it possible for template developers to query the repository directly, rather than depending on C# developers to write retrieval logic into backend code. This is a significant change from the standard MVC conventions.
This should allow teams to more easily distribute their CMS development, and it doesn't take much imagination to extrapolate how this might change how you develop CMS projects. In addition to the direct changes of a new language, the new option will have multiple indirect changes to project organization, staffing, DevOps, project planning, and even…philosophy?
...but first, let’s back up a bit.
Some History
If there’s been one perennial thing about developing on a Episerver/Optimizely CMS project, it's that the work revolves around Visual Studio. Perhaps it’s more accurate to say that it revolves around a C# application -- Visual Studio is just the usual window into that.
Now, to be clear, I love C#. I think it’s a great language. We've pushed our CMS all the way out on .NET Core 7 now, which uses C# 11, and there are some amazing features there.
However, C# isn’t for everyone. And one of great limitations of being so…pure, is that it can be hard to distribute the work on a CMS project. If you have front-end developers that work in HTML, CSS, and JavaScript, templating becomes an unnecessary roadblock. Back when I was a partner, I often had to “translate and coerce” HTML from a front-end developer into Razor templates.
Razor, of course, is the default templating language for ASP.NET. But it’s not even really a “language,” as much as it’s just transpiled C# -- . But not only does it transpile to C#, you can simply write raw C# inside a Razor file.
(In fact, I saw an implementation the other day where there was only one actual controller, and most of the controller-ish logic was in a C# block inside the Razor layout page. On first glance, it horrified me, but the customer made a decent case for it, and it worked for them, so… yay, flexibility?)
And Razor is just...not spectacular as a templating language? I mean, it works fine, and it was undoubtedly better than Webforms, but the syntax is not entirely pleasant. (Yes, Tag Helpers make it better. We're implementing those.) Razor is basically C# mixed with HTML, and while this is effective, I've never heard anyone say that they love it.
(Yes, I'm hedging a little here. I know a lot of C# devs probably like Razor, and that's awesome -- you can keep using it. But among non-C# devs, especially front-end devs, the feelings are likely quite different.)
What We're Trying to Improve
We saw three problems we might be able to solve:
- To template a CMS project in Razor, you usually need to work within the Visual Studio project
- Razor allows unrestricted computational access to the process
- Razor is maybe, possibly, kinda not the greatest templating language in the world
So, what’s the solution?
Clearly, one solution is to shift to client-based templating, via React or something similar. We’ve had a headless API since 2015, and we’re releasing a GraphQL API at the end of Q1. Additionally, every URL into our CMS is technically a headless API URL, since you can get JSON back by simply changing one request header.
Many of our customers have done this – implemented their templates in React or Vue or whatever. And this is awesome -- we're so excited that customers are using our product this way.
But many more customers aren’t ready for this and might never be. Client-based templating requires a skillset that some customers just don’t have and don’t want to hire for. Additionally, there’s a vast new layer of development and DevOps complexity that comes with it.
Introducing Liquid Templating
With this in mind, we’ve released a new server-side templating option: Fluid, which is an implementation of Liquid.
Liquid is a templating language invented by Shopify a decade ago. It’s since become something of a standard – there are implementations in many different languages. The C# implementation is called Fluid, and was written by Sebastien Ros, who works at Microsoft on the ASP.NET core team.
(It can sometimes get confusing. For most intents and purposes, "Fluid" and "Liquid" are the same thing. But technically, Liquid is the syntax, and Fluid is the implementation of that syntax in C#. The terms will be used interchangeably.)
ASP.NET is designed to encapsulate output generation in what are called “View Engines.” The framework can have multiple view engines registered, and can check in with them, sequentially, to find one that will generate output. Usually, there’s just the one registered by default – Razor.
Our Fluid implementation has been written as a View Engine, which can be swapped in place of Razor (or loaded in addition to Razor, but more on that later).
Enabling Liquid is a process of including the code, then adding a couple lines of configuration to your Startup file.
Here’s what Liquid allows you to do, superficially, which corresponds to the three issues from above that we're trying to solve.
- Store templates in a different repository, using a different DevOps process
- Write templates in a safe, sandboxed language that doesn’t allow “root” computational access
- Use a more pleasant templating language than Razor
There are undeniable advantages here. This will allow you to distribute workload among different types of developers, and limit full project re-deploys – a templating change should not require you to redeploy and restart the entire instance. Templates can be stored in some other repository and deployed through some other process.
Downstream Changes
Now, you might think…"Interesting, but this is a minor change...."
But it’s bigger than you think. It has the potential to change the nature of a CMS project, both in the initial development and ongoing maintenance. And those differences might change how you develop.
With Liquid, it becomes easier to encapsulate different template retrieval logic
- Template retrieval is very flexible and writing a custom template provider is quite simple -- you just have to implement IFileProvider (here's an example). For example, I wrote one in about 15 minutes that retrieved templates from my personal Dropbox account.
- In fact, there’s no reason templates can’t just be stored as managed content. A file is just an array of bytes, and that can be attached to a media object as a BLOB. It’s quite easy to enable your developers to change templates and “deploy” them by just dragging them into the UI.
- To go even further, templates are just...strings. You can easily manage them as properties on a content object. Using the new “Headless Content” gadget in CMS12, a folder tree of templates could be created, and a “TemplateBlock” content type could expose a “TemplateCode” long string property. Create a new UIHint for an online editor, and now you can actually edit your templates directly in the UI.
We could actually combine all these methods --
It’s quite simple to create a “MultiSourceTemplateProvider,” which checks multiple locations for a template. For example, it might check these three locations in order, then return the first template it finds.
- A template block as a content object
- An attached .liquid file on a media asset
- A file on the file system
Using this logic, you can decide at what “level” you want to provide a template at.
(Here -- I did it: MultiSourceTemplateProvider)
Mixing Razor and Liquid
And there’s even another place we can go – you can actually stack View Engines. ASP.NET will query multiple engines, using the first one to return a view.
This means that “underneath” Liquid, you can have traditional Razor views. So your C# developers could provide default templating in Razor, which could be overridden by more dynamic view provided by a front-end developer in Liquid.
The inverse could work as well. By checking the Razor view engine first, your C# developers could “lock” templating by providing a Razor view for it, and only allowing the engine to “fallback” to Liquid when one isn’t provided.
And if you have a Razor project you want to convert to Liquid, you don’t need to do it all at once. Razor and Liquid can co-exist quite happily. In a long-running project, you could switch template languages as you implement new features.
The Potential for Architecture Changes
Clearly, template flexibility is going to increase. This means that the way you build websites might change.
Consider the process of mapping a template -- matching up a template with the requested content object. There's a convention here: you name the template for the content type. The relationship is usually always 1-to-1.
Along with alternative template retrieval, Liquid allows you to granularly control template mapping. A view name is a string, and within your own template provider, you can be very specific about what template you want returned and from where.
- If a block can be dynamically bound to other templates, in addition to a default template, would this remove the number of one-off BlockTypes required to support unique situations? Many one-offs are just to change output, because we tend to think of templating as a one-to-one-relationship to types. Liquid will make it easier to break out of that paradigm.
- If templates can be uploaded to or edited from the UI, would this free your users to create their own page or block types from the UI, and supply their own templates? Creating types from the UI isn’t new, but with Liquid, the “last mile” of template specification and ingestion is now so much easier. Could you enable front-end developers to work in the UI and templates after a C# developer has bootstrapped and deployed the project?
- Will the community coalesce around a template naming convention, to provide for dynamic template selection? A lightweight convention exists now – name your Razor files for the content type – but could this be extended to encompass template naming to specific languages, content branches, or even specific content? How about a template called “ButtonBlock-237.liquid” for content object #237? How about a template called “Sidebar-he.liquid” for Hebrew language formatting changes? How about a template called “Layout-CampaignSite.liquid” for a content under a particular site node?
And what about per-object template specification from the UI:
- A block could have a restricted-access “Templating” tab, behind which is a code editor that would allow specific blocks to “bring their own template” for one-offs?
- Or a content reference property that can link to a TemplateBlock object, from which the template can be retrieved, meaning blocks and pages could specify a template, or link to one?
Additionally, we took a calculated risk when developing the Fluid integration to increase the power of template developers. Traditionally, the MVC convention has a backend developer do all logical data retrieval in the controller and provide that information to a view in the form of a model.
However, with Razor, that convention is a bit academic anyway, since you can write unrestricted C#. So we decided to allow and even encourage repository retrieval from the template by providing a set of repository functions. For example:
{% assign children = ContentLoader.GetChildren(page.ContentLink) %}
There are methods for retrieving:
- Specific content
- The start page
- Top-level pages
- Children
- Siblings
- Parent
- Ancestors
- The nearest ancestor of a certain type
- The nearest ancestor with a value in a specific property
Template developers can now traverse and query the content repository themselves. And expanding this list with your own repository functions is quite easy.
(Don't worry: if this stresses you out, it's easy to disable.)
Our goal that was simple sites without much need for backend integration should be "developable" from the template and UI level. Could we reduce the need for deployments by...90%? We're not sure, but by empowering the front-end developers and detaching templating from the larger project, we think we're taking a step in that direction.
Liquid/Fluid Documentation
Over the last few months, I've written about 20,000 words of documentation on the Liquid/Fluid project itself:
(The title is explained at the bottom of that page.)
To be clear, that documentation is about Liquid itself, not so much using it in Optimizely CMS (which is why it's on my personal site). But a lot of times, lack of familiarity with the base language is problematic, so I wanted to solve that.
I worked on this documentation with Sebastian, and hopefully it will show you how wildly flexible Fluid is. You can extend the language in so many ways:
Using Fluid, you can develop an incredibly customized and tailored language for your front-end developers.
Conclusion
The larger point here is that Liquid brings with it a lot of flexibility in how you develop and manage templates. And rather than this being a simple language change, it has the potential to upend a lot of development paradigms that have evolved around our platform.
What we’re talking about is “editorial self-determination,” which is something I’ve been informally researching for years. To what level should we empower our users/editors to make structural changes to a website, beyond just content? What do we do about that gray area of highly-technical users that are more than editors, but not quite full-blown developers? Can we give them more tools to use? Can we offload some lightweight development tasks to them, freeing up our more experienced backend developers for other things?
And while this certainly isn't "low code," it might be... lowER code? It's a new layer of development for non-C# devs, in an effort to integrate our system into more diverse development teams and processes.
In the end, we're not sure how this will play out over the long-term, but we're interested to find out.
We've decided to simply open-source our Liquid templating, to encourage the development community to adopt and customize it. You can find it here:
https://github.com/episerver/liquid-templating-cms
We'd love your feedback and your changes. Pull requests are welcome.
Great effort all involved.
One thing of note though - this adds another spanner in the works when spinning up a new project in terms of decision making. As well as having to decide all of the options for .Net core projects these days (such as top-level statements, razor pages or MVC architecture, Areas, View components and general solution design), you then have to choose the operating system you wish to host the app on and now you have to choose the templating engine to use...
And whilst admittely I am a C# dev, I do think the following statement is a bit naive, to be honest.
Razor is basically C# mixed with HTML, and while this is effective, I've never heard anyone say that they love it.
But I do understand the motiviation - Shopify is smashing the eCommerce space and no-doubt a response is needed, it's a great idea.
But things are getting really complex in this space... and you're giving us Optimizely developers quite the headache with all these options :D
Can I suggest that some guidance be published to help solution architects with their decision making when spinning up an Optimizely project?
Great effort all involved, it's a great idea.
Just one thing of note though - it does add another spanner in the works when spinning up a new project in terms of decision making. As well as having to decide all of the options for .Net core projects these days (such as top-level statements, razor pages or MVC architecture, Areas, View components and general solution design), we then have to choose the operating system to host the app on and now we have to choose the templating engine to use.
But I do understand the motiviation - Shopify is smashing the eCommerce space and no-doubt a response is needed, it's a great idea.
And whilst it's a nice problem to have, things are getting really complex in this space.
Can I suggest that some guidance be published to help solution architects with their decision making when spinning up an Optimizely project?
heh something seems to be broken with comments today - have wrote the same comment a couple of times and it seems to be getting lost in translation..!
Your MultiSourceTemplateProvider link is wrong, points to https://accounts.welcomesoftware.com/