Heartcore Christmas Bingo

Heads Up!

This article is several years old now, and much has happened since then, so please keep that in mind while reading it.

Bingo - This is probably a term very familiar to every Umbracian who has attended a CodeGarden in person! Anything can happen at the Bingo! From being electrocuted on the stage to getting a brick wall as a gift (yes, that was a gift!). I am not going to do that here because Umbraco Bingo is on a totally different level. But since its Christmas time, why not build a Christmas Bingo word generator using Umbraco Heartcore, Blazor and Azure Static Web Apps!

Blazor WebAssembly (WASM)

Blazor is a UI framework from Microsoft. With two hosting models - Blazor Server and Blazor WASM, Blazor lets you create rich, interactive UI experiences. Applications developed using Blazor has the feeling of an app, hence they are called Blazor Apps.

Blazor WASM is regarded as the primary hosting model for Blazor. With Blazor WASM, C# code runs on the browser, in the JavaScript security sandbox, using WebAssembly. This is done by using a .NET Intermediate Language interpreter implemented in WebAssembly. All the dependencies and the .NET runtime are downloaded to your client and runs from the client thereater. This means that Blazor WASM apps can be hosted on a static webserver or any service where .NET is not used to serve the app.

Umbraco Heartcore

Umbraco Heartcore is the headless offering of our favourite CMS! Its termed "headless" in the sense that there is no presentation aspect to the CMS i.e. there is no templates or views which you would expect with an Umbraco install. Instead, the focus is on content storage and delivery. And developers can use a technology of their choice to come up with anything ranging from websites to IOT devices.

Umbraco Heartcore ships with a REST API for content management and delivery for all plans. It also has a GraphQL API for content delivery from the "Starter Plan" onwards. The GraphQL API can be used to read published as well as draft content.

Azure Static Web Apps (SWA)

This is the final piece of the puzzle for the day. Azure Static Web Apps is an offering from Microsoft to host fullstack apps. Announced at Microsoft Build back in 2020, it went into general availability this May. It is a turnkey service for hosting apps with pre-built and pre-rendered static front-ends.

An SWA app has two parts - the static app and an optional API powered by Azure Functions. Azure SWA supports a wide range of front-end frameworks and libraries. Popular JavaScript libraries, static site generators, Blazor WASM - any framework which does not need server-side rendering are perfect candidates for Azure SWA. Any server-side functionality is and must be fulfilled only using the API part of the Azure SWA.

Azure SWA has some fantastic benefits as well which makes it a great service to host static front-ends. Here are some of the advantages

  • Globally distributed static content, which means the content is always served from a point closer to the users
  • Free SSL certificates
  • Custom domain support
  • Auto-generated staging environments to preview your changes
  • Integrated API support provided by Azure functions
  • First-class GitHub and Azure DevOps integration where repository changes triggers builds

Azure SWA has two hosting plans - Free and Standard plans. The Free plan can be used for personal and hobby projects where as production apps must be hosted using the Standard plan. The most important difference between the two plans is around the API support (see API support) and the number of staging environments available (10 v/s 3 for the Free plan). With the Free plan, you have the Managed APIs where the API is the part of the same SWA service. But with the Standard plan, you have the choice of either the Managed API or you can bring in your own Azure Function as the API. You can read more about the differences here. In my demo, I am using a Free plan which means I only have the Managed API support.

API Support in Azure SWA

With Azure SWA it is not mandatory to have an API. It is totally optional. There are two API configurations available with Azure SWA - Managed functions and the Bring Your Own functions. With Managed functions, the API forms a part of the SWA service and is completely managed and deployed by the SWA. With Bring Your Own functions you can specify an existing Azure Function app as the API of your SWA. Managing and deploying the function app can then be handled separately. In both cases, the function is available at the route /api. This is a fixed route that is made available to the web app securely with a reverse proxy thereby getting rid of any CORS issues. This route is fixed and cannot be changed. Both Managed and Bring Your Own functions have access to application settings which can be used to hold any configuration like database connection strings. You can read more about the API Support for SWA here.

Integrated Developer Workflow

Azure SWA provides first-class integration with GitHub and Azure DevOps. It is pretty opinionated but there is a workflow that is very close to a developer's daily workflow. The idea is that developers focus on the development of the app while the DevOps part is handled by the platform for you.

 

 

When an Azure SWA is created, the resource interacts with your repo in GitHub or Azure DevOps and monitors a branch that you specify. Every time a change is pushed or a pull request is merged into the branch that is watched by the Azure resource, a build is automatically run and the app and the API (for managed functions) are deployed. When a new pull request is created against the branch that is being monitored, it build the branch into a staging environment. Upon merging the pull request it deploys to the production environment and also cleans up the staging environment.Multiple staging environments can co-exist for the same app. The Free plan comes with a maximum of 3 staging environments where as with the Standard plan, you can have up to 10 staging environments at the same time, all in addition to the production environment. The workflow for PRs and staging environments is only supported by GitHub Actions and not available in Azure DevOps.

Creating Azure SWA from GitHub Starter Templates

To help us get started with Aure SWA there are GitHub Starter Templates available. Starter Templates are available for a lot of front end frameworks. I am using the Blazor Starter Template to create a GitHub repo of my own.

The solution contains few projects so I have removed what I don't need and ended up with the structure as below

  • Client - This is the Blazor WASM app
  • API - The C# Azure Functions API
  • SharedLibrary - A class library project

Let us now deploy this into an Azure SWA resource.

Search for "Static Web App" in Azure Portal and hit Create

Select/Create a resource group, a name and the plan. In the Deployment Details section I have chosen GitHub as my Source. The portal will now ask to Sign in with GitHub so that I can choose my repo. I choose my repo and the branch. This branch will be monitored by the resource for any changes or open and merged pull requests.

Next in the Build Details section I get asked for the Build Preset. The option selected here controls the App Location, API Location and the Output location. The App Location and the API location must be the name of the folders in which your app and the api resides. The Output location is the folder containing the build output which is wwwroot for Blazor. I am leaving all of the options at the defaults as it matches my directory names. This also creates a GitHub actions workflow file in the repo which ties up the resource to the repo and manages deployments.

Hit review and create and that creates my Azure SWA resource and deploys the code to the resource as well. I can monitor the progress of the deploy in my GitHub repo in the Actions tab.

Once finished, I can go to my resource and it should give me a url. I can also see the source (repo and branch) for my SWA resource in the portal.

The app is a basic Blazor app that communicates to the Managed function to get some weather data. I did not have to change anything in my code in terms of CORS headers. That is all managed by the platform for you.

The Christmas Bingo Word Generator

Let us now start looking at some code to implement a Christmas Bingo using Umbraco Heartcore and Azure Static Web Apps. I am using the same GitHub Starter Template for my demo.

The GraphQL endpoint for Umbraco Heartcore is available at https://graphql.api.io. The endpoint also needs a header called umb-project-alias to read the content. The value of this header is the project alias of my Heartcore project which can be obtained from the Cloud Portal. Blazor WASM runs completely on the client after the initial download of the dependencies, so secrets like the project alias must never be stored as a part of the project. It is recommended that a API call handles all of this for you. So in my demo, instead of the app reaching out directly to the Heartcore GraphQL endpoint, the Managed function will do that for me. The Managed function has access to app settings locally and on Azure. I can use the .NET Secret Manager or the local.settings.json file for local development but when it gets to the Azure Resource I can store the project alias in the Application Settings thereby hiding the secret away securely.

Strawberry Shake - Strongly Typed GraphQL client

To communicate with the GraphQL endpoint, I am using a client called Strawberry Shake. The tooling from Strawberry Shake helps you generate a strongly typed GraphQL client for your GraphQL endpoint. I have already blogged about this in the past which you can read here. In addition, the tooling also comes with a CLI tool that can help you achieve the same.

Once the set up is done I can write my graphql queries. And for every GraphQL query that I have, the tooling generates an operation service an ExecuteAsync() method which reaches out to my endpoint, fetches the data and deserializes it for me. I am using the tooling at a very basic level here.

Umbraco Heartcore Set Up

My Umbraco set up consists of a single document type called Christmas Bingo. All the words are entered into a content node of this document type.

 

Now I can start thinking about my GraphQL query to retrieve the words.

query GetChristmasWords($url:String!){
  christmasBingo(url:$url){
    bingoTerms
  }
}

To explain my query, I start off with the query keyword and give my operation an arbitrary name GetChristmasWords. I now query my Christmas Bingo node using the christmasBingo field passing it the url of my node. This query field corresponds to my document type of the same alias. $url denotes a GraphQL variable. The String! denotes that it is a non-nullable String value. I now assign this variable to the argument url for the christmasBingo field. I can then start querying for the property in my document type which is bingoTerms which returns a list of String. The information about this query field and what it returns can be found in the auto-generated docs which is handy. You can also read about GraphQL Schema Generation in Umbraco Heartcore here.

I have Strawberry Shake tooling set up in the class library project in my solution. The tooling generates a strongly typed C# client called HeartcoreClient based on the set up I have specified and my GraphQL query generates an operation service of the same name GetChristmasWords.

The API

The API consists of an Azure Function. As I said before my API will be making the calls to the Heartcore GraphQL endpoint. To begin with I need the GraphQL client registered with my DI container. You can read more about Dependency injection in Azure Functions here. Once the correct Nuget packages are in place I can create a Startup.cs class for my API as shown below.

using Microsoft.Azure.Functions.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
using System;

[assembly: FunctionsStartup(typeof(BlazorApp.Api.Startup))]

namespace BlazorApp.Api
{
    public class Startup : FunctionsStartup
    {
       
        public override void Configure(IFunctionsHostBuilder builder)
        {
            //get the endpoint value from local.settings.json
            var endpoint = Environment.GetEnvironmentVariable("HeartcoreEndpoint");

            //get the project alias from local.settings.json
            var projectAlias = Environment.GetEnvironmentVariable("HeartcoreProjectAlias");

            builder.Services
            .AddHeartcoreClient() //register the GraphQL client with my DI container
           .ConfigureHttpClient(client => { client.BaseAddress = new Uri(endpoint); client.DefaultRequestHeaders.TryAddWithoutValidation("umb-project-alias", projectAlias); });


        }
    }
}

I am storing the GraphQL endpoint details and my project alias as app settings in Azure. While developing locally I can store them in the local.settings.json file. In both local and Azure environment, I can read the app settings from environment variables. The local.settings.json file is used for local development only and not tracked by GitHub so it does not get source controlled. I can add these as Application Settings in the portal.

And below is the code for my Azure Function.

using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using HeartcoreClient;
using System.Linq;


namespace BlazorApp.Api
{
    public class HeartcoreFunction
    {
        private readonly IHeartcoreClient _heartcoreClient;

        public HeartcoreFunction(IHeartcoreClient heartcoreClient)
        {
            _heartcoreClient = heartcoreClient;
        }

        [FunctionName("HeartcoreFunction")]
        public async Task<IActionResult> Run([HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)] HttpRequest req, ILogger log)
        {
            log.LogInformation("C# HTTP trigger function processed a request.");

            var randomWord = "Boo,No cake for you!!";

            Random r = new();

            //use the operation service passing in the url of the node
            var response = await _heartcoreClient.GetChristmasWords.ExecuteAsync("/christmas-bingo");

            //get the deserialized data
            var words = response.Data.ChristmasBingo.BingoTerms;

            if (words.Any())           
            {              
                //choose a random word
                randomWord = words.Any() ? words[r.Next(0, words.Count)] : "Boo,No cake for you!!";               
            }

            return new OkObjectResult(randomWord);
        }
    }
}

That is my Managed Function set up. The api can be invoked at /api/HeartcoreFunction.

The App

Let us wire it all up now. In my Blazor WASM app, in the Index.razor file I replace the existing code with the below. I have used a theme to give my app a Christmassy-feel, hence the extra markup!

@page "/"
@inject HttpClient Http

<div id="wish" class="about-box" style="padding-bottom: 0;">
    <div class="about-a1">
        <div class="container">

            <div class="row">
                <div class="col-lg-12 col-md-12 col-sm-12">
                    <div class="row align-items-center about-main-info">

                        <div class="col-lg-6 col-md-6 col-sm-12 text_align_center">
                            <div class="full">
                                <img class="img-responsive" src="images/w1.png" alt="#" />
                            </div>
                        </div>

                        <div class="col-lg-6 col-md-6 col-sm-12">
                            <h2><img style="width: 60px;" src="images/head_s.png" alt="Picture of Santa Claus" /> Merry Christmas</h2>

                            @if (!string.IsNullOrWhiteSpace(randomWord))
                            {
                                <p>@randomWord</p>
                            }
                            <button @onclick="GetRandomWord" class="btn btn-common" type="submit">Generate Christmas Bingo!</button>
                        </div>

                    </div>
                </div>
            </div>
        </div>
    </div>

</div>

@code {
    private string randomWord = "Let's Christmas Bingo";

    private async Task GetRandomWord()
    {
        try
        {
            //the api is available at the route /api/HeartcoreFunction
            var response = await Http.PostAsync("/api/HeartcoreFunction", null);

            randomWord = await response.Content.ReadAsStringAsync();
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.ToString());
        }
    }
}

I can now run my solution with multiple start-up projects and should be able to verify my setup locally. To deploy this all I need to do is commit the code into a branch, raise a pull request against the main and merge it in. The GitHub Actions workflow kicks in and will take care of the deployment for me.

I have deployed my app to https://witty-mushroom-05cec4703.azurestaticapps.net/ , so go ahead and have a look at the app.

There you go! That is my Christmas Bingo word generator using a Blazor WASM app deployed on Azure Static Web Apps.

Poornima Nayar

Poornima is on Twitter as