CONTENTSTART
EXCLUDESTART EXCLUDEEND

Xperience + Azure Functions

Why Azure Functions?

Azure Functions is a nifty little tool that allows you to run basic functions for extremely low costs, with the ability to scale up and down easily.  If your request stays under 100ms and 128mb of memory, it hits the lowest cost per request of $0.0000002, or roughly 5 million requests per $1.

If you are hosting your site and database on Azure, and your web application is taking a hammering because of a handful of APIs, you are faced with either scaling up/out your entire web app ($$$) or you could offload those APIs to Azure Functions instead.

It also makes possible using Xperience as a Headless CMS in a sense with a bit more structured content control, although Kentico Kontent is created for this primarily.  I personally am using it for a project where my very basic site will be run completely off Azure Functions, React, and static HTML pages on a CDN for next to nothing (becuase I'm cheap).

Limitations

Xperience on Azure Functions does have some big limitations.  It is a bit difficult to hook up Xperience to Azure Functions at the moment, they hope to make it simpler in the next version. This is largely due to Kentico Xperience 13 having to work in both .Net 4.8 and .Net Core, and running their own cross compatible IoC Container instead of using .Net Core's native IoC implementation.

Also, Azure Functions has no great way of integrating with Cache Dependencies.  You can use Caching, and it would be possible to create a function that you could trigger and pass the cache dependency hits to clear the cache, but it would be custom code.  If you do use caching, use it with caution, however so far it seems the execution times are well within reason even without caching.

You also need to generate a License for the Azure Function Domain (should be easily approved as 'free' for your license as an alias).

Setup your Azure Function Project

You can download the sample Azure Function Project from my Github to do a quick start, otherwise here is the step by step.

1: Create an Azure Function Project
Using Visual Studio, create a new Azure Function project with Http Trigger (you of course can explore using other types of azure functions, but for this project we are making a simple Xperience Driven API)
Storage Account should be Storage Emulator or None, depending on your requirements.  Authorization Level should be Anonymous if you wish the API to be publicly accessible, or Function if you wish to lock down usage.  I will be using Anonymous for our example.

2: Install the required Nuget Packages
Next, we will need to install a couple Nuget packages to get ourselves going:

Microsoft.Azure.Functions.Extensions: Needed to setup a Startup method to configure Kentico Xperience and Dependency Injection.  I used v1.1.0

Kentico.Xperience.Libraries: Kentico's core library pack.  Pick the version that matches the hotfix version of your Xperience Instance.  Should be at least Hotfix 13.0.5 since this is needed for .net Core Support.

3: Copy Boilerplate Startup.cs
To get Xperience hooked up properly, you need to use a very specific startup configuration.  Please copy the Startup.cs file into your project.  The only part you will want to adjust is RegisterCustomAssemblies method to include any additional .Net Standard or .Net Core libraries.  This should include any of your Page Type Classes, Custom Table Classes, Custom Module Classes, any interfaces and implementations, etc.  Let me break down the Startup.cs for you:

RegisterCmsAssemblies/RegisterCustomAssemblies: For Azure Functions, all the binaries must be located in the root of the project, or it will not function.  These functions copy the compiled binaries into the proper location for Azure Functions to work, including all of Kentico Xperience's libraries and any you may have as well.

CMSApplication.PreInit: This starts the registration of the Interfaces that Kentico Xperience has.

Service.MergeDescriptors(builder.Services): This merges all of Xperience's Interfaces and services with the Azure App's IServiceCollection

var Config = builder.GetContext().Config: Used primarily in Debugging, this config will grant you access to the local.settings.json properties

String EnvironmentName = builder.GetContext().EnvironmentName: This gets the current environment name, which is “Development” by default on local dev.  We use this as a toggle for where we get our Application Settings for the Connection String

CMSApplication.WaitForDatabaseAvailable.Value = true: This tells Xperience that if the database is not awake, to wait till it is.  This is important if your database can go 'sleep' due to inactivity with auto-pause

ConnectionString = Config.GetSection("ConnectionStrings").GetSection("CMSConnectionString").Value: If in the Development Environment, gets the Connection string from the { “ConnectionStrings: { “CMSConnectionString”: “theconnectionstringhere” } } setting

ConnectionString = Environment.GetEnvironmentVariable("CMSConnectionString", EnvironmentVariableTarget.Process): If in production, this will get the value from the Azure Function's Application Setting (managed in Azure Portal -> Azure Function -> Configuration -> Application Setting)

ConnectionHelper.ConnectionString = ConnectionString: Set the Connection String for Xperience to use.

CMSApplication.Init(): Initiailze the connection and Xperience.
With these items in place, your Azure Function should be ready to execute functions.

4: Add your Connection Strings
In your local.settings.json, add your ConnectionStrings and CMSConnectionString fields, this is what mine looked like:

Failed to load widget object.
The file '/CMSWebParts/Custom/HighlightJS/HighlightJS.ascx' does not exist.

5: Create your Function
Next, we will add our function.  You can delete the default one and add a new Azure Function and it will create a Boilerplate one.  However, there are some issues that I want to point out that caused me a lot of problems.

Issue 1 – req.Content Empty: When an azure function goes to sleep, and gets a request and wakes up, there is currently some bug that sometimes the request's body content disappears by the time it gets to your function.  A common way to send data to your method is to post JSON in the request body, and thus because of this bug you may end up with a very big error.
Solution – Use POST and a strongly typed Class: Instead, you should use a serializable Request Class and have it as the first parameter in your method.  Azure Functions seems to properly parse the body into the class using normal json deserialization, and it seems immune from these startup issues thankfully.
Solution – Use Query Strings: The other option is to pass your parameters as a query string, as it seems these are also not lost during this cold-start bug.

Issue 2 – Retries: It seems also that an azure function may 'fail' periodically that you need to account for.
Solution – Retry Policy Azure Functions provides some Retry Policy Attributes (FixedDelayRetry and ExponentialBackoffRetry) that will trigger the function to automatically retry running if it fails.  This can be done at a function by function level, or on all through the host.json
Solution – Handle in Code Another option is to handle these at the client level and put in a loop on the APIs.  I prefer the Retry Policy personally over this though.

In my sample function, I created a Strongly typed “GetNavigationItemsRequest” model and used that to grab the first X Navigation Items and returned the Fields (simple model generated by Kentico).  I ran the project locally

6: Publish
The last step is to publish your Azure Function to Azure.  I would use the Publish Profile Azure provides and use Visual Studio to publish if it works, mine didn't so I had to just use the normal Publish wizard.

Setup Azure Function in Azure

In your Azure Portal, add a new Resource -> Function App.  Set the Runtime stack to .NET and version 3.1, and for the Host Operating System you can do Linux since this is all .net Core.  For the plan type, Consumption (serverless) is the cheapest and you can literally get millions of requests for a single dollar.

Once it is built, on the Overview click Get Publish Profile and use this to publish your app from Visual Studio to AF.  If this fails (did for me), then you can use the normal Publish wizard to publish to Azure -> Azure Function App (Linux) -> Select your Subscription and find your Azure Function and publish. 

The last step is to add our CMSConnectionString to the App settings, this is done through the Azure Portal -> your Azure Function -> Configuration -> New Application Settings, set the name to “CMSConnectionString” and the value to your Azure SQL connection string.

Domain and License Key

Do not forget that Kentico Xperience license still runs off the domain, so once you have your azure App going, you may want to also configure a custom Domain / SSL Certificate and add a license for that domain.

Database Considerations and Firewall Rules

Your Database needs to be accessible to your Azure Function, this means your database should be hosted in Azure SQL.   Since there are many tutorials on deploying your database to azure, I will not go into detail on getting your database in Azure.  However, there IS something specific to Azure Function usage that you need to be aware of, and that's Firewall rules.

When you deploy the normal Azure Functions (non-premium), you do not have a Static IP address for your functions, which makes setting up an open connection from Azure Functions to your Database a bit tricky.  There are ways to make Virtual networks and such, however, to keep things simple, any Azure Function you set up does have a list of possible IP addresses it can send from.
To Find your Azure Function Possible Ips, go to…

https://resources.azure.com -> Subscriptions ->  [Expand your Subscription] -> Providers -> Microsoft.Web -> Sites

Then here find your Azure Function Site, and search for outboundIpAddresses and possibleoutboundIpAddresses, these will contain a list of IP addresses.  Add all of them to your SQL server's firewall and you should be good to go!

API Only Xperience Projects

If you are using the Xperience Database only to drive the API, you may want your Database to be Server-less, and with this you can have your Database go to sleep when not active (minimum 1 hour time).  If your site does have times where the API is not hit, you can use this option, just be aware that you must have the CMSApplication.WaitForDatabaseAvailable.Value = true in your startup so Xperience knows to wait will the database comes online.

Heartbeat/Keep Alive

Azure Functions are set to go to sleep if not hit, and there is a couple second wake up time as Xperience configures itself.  If you are going for performance, you may want to consider adding a heartbeat function to keep the azure function and the database connection alive, I would hit it every 5 minutes to be safe.

The End Result

With this you now have an API that you can infinitely scale for fraction of pennies.  For me, I will eventually have a site run on Xperience 'headless' with great performance and near 0$ hosting cost.  I hope this helps you out there to try this new way of doing things.  Big shout out to Kentico Support who helped me work through this and get things going!  Happy Coding!
 
Comments
Blog post currently doesn't have any comments.
= eight + six
CONTENTEND