Umbraco 9 Server Variables
Heads Up!
This article is several years old now, and much has happened since then, so please keep that in mind while reading it.
- What are Server Variables?
- Why would you need Server Variables?
- How do you use Server Variables?
The primary focus of this article is Umbraco 9, but I will mention older versions where possible.
What are Server Variables?
Before I try to explain this to you in my own words, let me start with a rather technical quote from the Umbraco documentation:
In v7 once a user is authenticated a request is made to retrieve the Server Variables collection from the server which creates a global dictionary object that is accessible in JavaScript. This dictionary contains information based on the current Umbraco environment for which the JavaScript and client side needs to know about. Information such as whether the application is running in debug mode, the service API URLs, or other non-sensitive global server side variables.
This is the only piece of documentation you will find on the subject on the official Umbraco documentation website. But only if you look for the words "server variables", which you would only do if you already knew this is how Umbraco calls whatever Server Variables are meant for. To make matters worse, it is also specifically targeted at Umbraco 7, meaning you would probably dismiss it as non-relevant if you are using Umbraco 9. Even if you do decide to read the old documentation, you will most likely be stuck on how it should be done in Umbraco 9.
So, what are Server Variables, exactly? In short: Server Variables are a way to pass information from the backend (C#) to the backoffice (JavaScript).
You can probably already imagine how useful this is. But sceptics, as most developers are, might also wonder: How is this different from "just" calling an API and getting said information that way when I need it? Answer: The Umbraco backoffice does all the work for you, before you even get the chance to call your API!
Why would you need Server Variables?
Truth given, "pass information" in my explanation above is an overstatement. The use case for Server Variables is passing things like API URLs, constants and other "non-sensitive global server side variables", as per above documentation.
The Umbraco backoffice itself uses it for exactly that! It gives the JavaScript an opportunity to figure out:
- What the URLs for most API endpoints are
- Whether or not debug-mode is enabled
- Which image extensions are allowed and disallowed
- Which background and logo to show on the Umbraco login screen
- Which version of Umbraco is running
- And so on... check /umbraco/ServerVariables on a running Umbraco instance to see what else is inside.
Everything you configure in files like appsettings.json have to somehow end up on the JavaScript side of things! That is why you need Server Variables.
Coming back to the "pass information" being an overstatement... Technically you could store a complex JSON object in a single Server Variable. But you should probably ask yourself the questions: Do you really want to do that? Do you really need all that information? I'll leave that one for you to figure out.
Before moving on, let's put Server Variables into perspective by looking at an example: You made an awesome dashboard to show the backoffice user some statistics about content. In order to generate these statistics you have to call an API endpoint on a web service unrelated to Umbraco, directly from the dashboard.
During development the API endpoint is located at http://localhost:8080/api/GenerateStatistics. On your test environment it is located at https://test.mydomain.tld/api/GenerateStatistics. While on your production environment it is located at https://www.mydomain.tld/api/GenerateStatistics.
You could make the decision to proxy the request from the server running the backoffice to the API endpoint and back, but let's assume this is not a feasible solution for various reasons. You are now posed with a problem: How can the JavaScript know which API endpoint to communicate with, without having to modify the JavaScript per environment? Now you know the answer: Server Variables!
How do you use Server Variables?
With the above example in mind, how would one go about implementing that?
For Umbraco 7, you can follow the instructions of the documentation page I linked earlier.
If you need more handholding, or want to do this in Umbraco 8, you could take a look at an article written by Umbraco's own Developer Advocate Warren Buckley.
When it comes to Umbraco 9, things have changed quite a bit! So let's layout the process:
- Create a Composer
- Create a NotificationHandler
- Register a NotificationHandler
- Use the Server Variable in the backoffice
As a bonus, let's also throw in:
- Create, register and use an Options-section
Step 1. Create a Composer
Since Umbraco 8, it's possible to use Dependency Injection (DI) to manage, well... your dependencies.
TIP! For those unfamiliar, Dependency Injection is a Design Pattern and part of the SOLID principles in more than one way. Definitely worth a read! I won't get into any further details as part of this article. As a Software Engineering lecturer I can also talk for hours on end about this, so I'll spare you that.
With the introduction of DI, we said goodbye to the good ol' IApplicationEventHandler and said hello to the IUserComposer! In Umbraco 9, the IUserComposer has been simplified to IComposer.
Besides handling DI, another use-case of IComposer is telling Umbraco what it has to configure before fully starting up. You can read more about it here.
For step 1, create a StartupComposer.cs and add the following code to it:
using Umbraco.Cms.Core.Composing;
using Umbraco.Cms.Core.DependencyInjection;
public class StartupComposer : IComposer
{
public void Compose(IUmbracoBuilder builder)
{
//TODO: Add logic here
}
}
As the name of the class implies, we are going to use this composer to handle everything we want to do when Umbraco starts up.
Step 2. Create a NotificationHandler
Prior to Umbraco 9, the StartupComposer would have been the place to register an event handler for the ServerVariablesParser.Parsing-event. In Umbraco 9, the ServerVariablesParser-class still exists, but it doesn't have an event anymore. Better yet, none of the original events like ContentService.Publishing exist anymore!
Fret not, they are not actually gone! You just have to register your event handlers in a slightly different way now by using notification handlers.
To create a notification handler, you need to decide on two things:
- Which "notification" do you want to handle?
- Do you need to handle that notification synchronously or asynchronously?
To figure out which notification you need to handle, take a look at the documentation. Do keep in mind not all notifications are actually documented, so your safest bet it searching for Notification on GitHub. The one we need for handling Server Variables is ServerVariablesParsingNotification.
TIP! If you go the the in-browser Visual Studio Code of the Umbraco CMS GitHub repository, you can search through all files using CTRL+SHIFT+F. Be sure to enable "local indexing" when offered, as the GitHub index tends to not find what you are looking for.
Next you need to decide if your notification handler has to run synchronously or asynchronously:
- If you want to run code synchronously, you need to use INotificationHandler.
- If you want to run code asynchronously, you need to use INotificationAsyncHandler.
With both things decided, you can move on with step 2 and create your notification handler. For our example, we'll go with a synchronous notification handler.
Create a ServerVariablesParsingNotificationHandler.cs and add the following code to it:
using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.Notifications;
public class ServerVariablesParsingNotificationHandler : INotificationHandler<ServerVariablesParsingNotification>
{
public void Handle(ServerVariablesParsingNotification notification)
{
//TODO: Add logic here
}
}
The important bit in here is the interface implementation: INotificationHandler<ServerVariablesParsingNotification>. It's a combination of both things we decided on earlier. If we were to implement an asynchronous notification handler, this would have been INotificationAsyncHandler<ServerVariablesParsingNotification>.
Inside the Handle-method, we can now add logic to add information to the Server Variables, which get passed to the backoffice.
The easiest way to explain this, is by showing it. For our example dashboard we need an API endpoint to be passed to the backoffice, so replace the TODO with something like this:
notification.ServerVariables.Add("statisticsDashboard", new
{
apiEndpoint = "http://localhost:8080/api/GenerateStatistics"
});
In this code, notification.ServerVariables is of type Dictionary<string, object>. The key can be any name and is used to retrieve the object in the backoffice later on. The object can be anything, as long as it is serializable to JSON. In the example above I'm using an anonymous object, which may contain other objects. But it could also be any other type like string or bool.
Let's ignore the fact our API endpoint is still hardcoded and move on to the next step.
Step 3. Register a NotificationHandler
Now that we have a composer and a notification handler, it's time to combine the two!
Open up StartupComposer.cs and replace the TODO with the following code:
builder.AddNotificationHandler<ServerVariablesParsingNotification, ServerVariablesParsingNotificationHandler>();
You will also need an additional using for this to work:
using Umbraco.Cms.Core.Notifications;
If you were to use an asynchronous notification handler, instead of AddNotiticationHandler you would use AddNotificationAsyncHandler.
Compile your code, start Umbraco and go to /umbraco/ServerVariables in the browser. You should now see something like this somewhere in the response:
"statisticsDashboard": {
"apiEndpoint": "http://localhost:8080/api/GenerateStatistics"
}
Great, almost there! Let's use this on our dashboard.
Step 4. Use the Server Variable in the backoffice
Because creating a dashboard is out of scope for this article, I'm just going to give you the steps to create a simple dashboard to demonstrate the Server Variables. If you want to read more about creating dashboards, go here.
- Create an App_Plugins\StatisticsDashboard-folder.
- Inside that folder, create a package.manifest with the following contents:
{
"dashboards": [
{
"alias": "statisticsDashboard",
"sections": [
"content"
],
"view": "~/App_Plugins/StatisticsDashboard/dashboard.html",
"weight": -1
}
],
"javascript": [
"~/App_Plugins/StatisticsDashboard/dashboard.js"
]
}
- Inside that folder, create a dashboard.html with the following contents:
<div ng-controller="StatisticsDashboard.Controller as vm">
My API Endpoint is {{vm.apiEndpoint}}!
</div>
- Inside that folder, create a dashboard.js with the following contents:
angular.module("umbraco").controller("StatisticsDashboard.Controller", function() {
const vm = this;
vm.apiEndpoint = "something";
});
If you open Umbraco after restarting, you should now have a dashboard in your Content-section saying My API Endpoint is something!.
TIP! Did you know you can register dashboards with a Composer instead of a package.manifest? You can read more about it here.
With that out of the way, let's replace the something with the actual endpoint from the Server Variables!
Replace vm.apiEndpoint = "something"; with the following code:
const serverVariables = Umbraco.Sys.ServerVariables.statisticsDashboard;
vm.apiEndpoint = serverVariables.apiEndpoint;
All Server Variables are available from the global variable Umbraco.Sys.ServerVariables. You can access them with the key you decided on in step 2 by using .key like in the code above, or by using ["key"]. If you have to retrieve more than one piece of data, it is wise to store your key in a temporary variable first. This keeps your code nice and clean!
I then assign the apiEndpoint from the Server Variables to vm.apiEndpoint for it to show up on the dashboard. If you refresh Umbraco, the dashboard should now show: My API Endpoint is http://localhost:8080/api/GenerateStatistics!.
That's it, you've now successfully exposed a piece of information from the backend to the backoffice!
Before wrapping up this article, let's solve our example "problem" with multiple environments and different API endpoints.
Step 5. Create, register and use an Options-section
Now that Umbraco 9 runs on .NET 5 and beyond, we have a whole slew of cool tricks we can use! One of those things is adding a custom options section.
In .NET Framework you could add App Settings in the web.config. This was by far the easiest way to make configurable settings, compared to making your own custom configuration section.
In .NET 5, App Settings no longer exist. Luckily, adding custom option sections is now easier than ever! An added bonus on top of this bonus paragraph: we can reload changes to these custom option sections without having to restart the website.
Create a StatisticsDashboardOptions.cs and add the following code to it:
public class StatisticsDashboardOptions
{
public const string StatisticsDashboard = "StatisticsDashboard";
public string ApiEndpoint { get; set; }
}
Open your StartupComposer.cs and add the following code anywhere in the Compose-method:
builder.Services
.AddOptions<StatisticsDashboardOptions>()
.Bind(builder.Config.GetSection(StatisticsDashboardOptions.StatisticsDashboard));
You will need an additional using for this to work:
using Microsoft.Extensions.DependencyInjection;
Last but not least, open your appsettings.json and add the following JSON to it. I left the $schema as a reference, you do not need to actually add that again.
{
"$schema": "./umbraco/config/appsettings-schema.json",
// ... Other keys ...
"StatisticsDashboard": {
"ApiEndpoint": "http://localhost:8080/api/GenerateStatistics"
}
}
In the above code, AddOptions<StatisticsDashboardOptions>() registers our options class as an injectable dependency for other classes in our project. Next the Bind(builder.Config.GetSection(StatisticsDashboardOptions.StatisticsDashboard)) tells our application that the data for our StatisticsDashboardOptions-class is located in our appsettings.json under the key StatisticsDashboard.
You can read more about how all this works here.
Now that we've registered our dependency, we can inject it where we need it!
Open your ServerVariablesParsingNotificationHandler.cs and add the following code to it:
private readonly StatisticsDashboardOptions _statisticsDashboardOptions;
public ServerVariablesParsingNotificationHandler(
IOptionsMonitor<StatisticsDashboardOptions> statisticsDashboardOptions)
{
_statisticsDashboardOptions = statisticsDashboardOptions.CurrentValue;
}
The magic in this piece of code is the IOptionsMonitor<StatisticsDashboardOptions> statisticsDashboardOptions-argument in the constructor. Our application will automatically retrieve a reference to our custom options section in the appsettings.json and keep track of changes in it. Every time we request .CurrentValue, we will get the value that's in the appsettings.json at that time.
Because our ServerVariablesParsingNotificationHandler is instanced every time the backoffice requests the Server Variables, we can immediately call .CurrentValue and store its result in an instance variable (_statisticsDashboardOptions).
Inside your Handle-method, update apiEndpoint = "http://localhost:8080/api/GenerateStatistics" to apiEndpoint = _statisticsDashboardOptions.ApiEndpoint.
Compile your code, start Umbraco and take a look at your dashboard. It should still show: My API Endpoint is http://localhost:8080/api/GenerateStatistics!.
Now open your appsettings.json again, and change the "ApiEndpoint": "http://localhost:8080/api/GenerateStatistics" from earlier to "ApiEndpoint": "https://www.mydomain.tld/api/GenerateStatistics".
Refresh Umbraco and behold, your dashboard should immediately reflect this change without having to restart: My API Endpoint is https://www.mydomain.tld/api/GenerateStatistics!.
Wrapping up
In this article, you've learned about Server Variables, Composers, Notification Handlers, Options and how all these things go hand in hand!
Now there is only one thing left to do: start using this knowledge in your own projects as soon as possible!