A ‘Magical’ Alexa Skill with .NET Core and AWS Elastic Beanstalk

01/29/2018

A ‘Magical’ Alexa Skill with .NET Core and AWS Elastic Beanstalk

How many times have you looked up from your code, gazing off into the distance, just waiting for an owl to fly up to the window and deliver your acceptance letter to a magical castle thousands of miles away? If your answer is greater than or equal to zero, then read on - this Alexa Skill is for you. With the ‘Wizard Adventures’ Alexa Skill, this dream becomes (close to) reality.

The Wizard Adventures skill allows you to learn magical spells in the classroom setting with Alexa. After learning spells you unlock future levels and can join Alexa on interactive adventures in which you will apply your newly learned skills.

Feel free to give this Skill a try. Just say “Alexa, open Wizard Adventures.” You can also see the skill details HERE: Wizard Adventures on the Amazon Skill store.

Wizard Adventures is my third Alexa Skill. I set out to build this skill for fun and to further my hands on experience with .NET Core, gain more experience with a variety of AWS Services, and experiment with new deployment methods (AWS Elastic Beanstalk). The below article walks through an overview of how my latest Alexa Skill was built on .NET Core, deployed using AWS Beanstalk, and integrated with a variety of AWS Services. Now let’s shed some light (Lumos!) on how this skill is built.


The Overall Design

This article is broken down to outline and describe, often with code snippets, the components and services involved in creating this Alexa Skill. They are ordered roughly in the order that an incoming request to Alexa would be processed. To help contextualize an overview of the design, please see the below diagram (which will be outlined in detail below).

Minimum Spanning Tree

AWS Domain Name Service & AWS Route 53 & AWS Certificate Manager

Alexa Skills can be built in two ways. They can be built as an AWS Lambda function or as a Web Service. There is documentation that covers the background for each type of skill HERE: Types of Alexa Skills. This Alexa Skill is built as a Web Service with an exposed endpoint for handling the requests generated by users’ Alexa devices. This allows it to also act as a traditional MVC website - which in the case of this Skill is a CRUD web interface and content management system for managing the data resources used in the skill (ie Skills, Adventures, AlexaMembers, etc).

Since this skill is built as a Web Service, it exposes an endpoint for Alexa requests to hit (in a POST request). This endpoint is set up at https://WizardAdventuresAlexa.com/api/invokealexa (note this endpoint will only accept valid post requests from an Alexa device). This configuration endpoint is added to the skill in the Alexa developer console.

An incoming request starts at the DNS level. AWS offers the Domain Name service to register a web domain, and the Route 53 Service effectively maps it to my IP address. Amazon also offers Certificate Manager which creates an SSL certificate for this domain. This certificate is used at the Elastic Beanstalk Elastic Load balancer level. The last point at the domain layer is the use of an Alias. AWS Elastic Beanstalk (outlined below) offers deployment of my .NET Core application to an IP address. Route 53 stores an alias so that requests to https://WizardAdventuresAlexa.com are sent to my Elastic Beanstalk configuration address.


AWS Elastic Beanstalk

The next piece of the puzzle is Elastic Beanstalk. Elastic Beanstalk acts as a ‘wrapper’ around several underlying resources and sets up the Elastic Load Balancer (which handles incoming traffic from Route 53) and disperses the traffic to one or more EC2 instances. Elastic Beanstalk also serves as a deployment tool. EBS provides a one click code publish interface from Visual Studio to deploy .NET Core code into a predefined environment, and it provides a consolidated view into all of the Web Server resources where the code is deployed.

AWS EC2 Auto Scaling Group and Elastic Load Balancer

Within Elastic Beanstalk, we can configure all of the underlying Elastic Load Balancer Settings and EC2 scaling. For this skill, we ensure that the Load Balancer accepts Https requests and we assign the above mentioned certificate to the load balancer. We can also specify EC2 instance size, scaling, and optional sticky sessions.


AWS CloudWatch & .NET Core Logging

Microsoft provides out of the box logging in .NET Core which is achieved through dependency injection into any controller, service, etc. Documentation and examples of logging in .NET Core are seen HERE:
https://docs.microsoft.com/en-us/aspnet/core/fundamentals/logging/?tabs=aspnetcore2x

AWS also provides a nuget package for .NET Core logging on AWS so that all logs are sent to AWS Cloudwatch. This nuget package and library can be seen HERE: https://github.com/aws/aws-logging-dotnet

In the context of this Alexa Skill, the above mentioned resources allow for .NET Core logging to the console in a local environment, and a stream of logs to AWS Cloudwatch in production. Setting up this logging in this Skill looks like the below.

// appsettings.Production.json
 "AWS.Logging": {
    "Region": "us-east-1",
    "LogGroup": "AspNetCore.WebSample",
    "LogLevel": {
      "Default": "Debug",
      "System": "Information",
      "Microsoft": "Information"
    }
  }
// Startup.cs

using Microsoft.Extensions.Logging;

public void Configure(
     IApplicationBuilder app, 
     IHostingEnvironment env, 
     ApplicationDbContext context,
     ILoggerFactory loggerFactory,
     IServiceProvider serviceProvider)
{

    // ....
    // For Production ...
     loggerFactory.AddAWSProvider(this.Configuration.GetAWSLoggingConfigSection(),
         formatter: (logLevel, message, exception) => $"[{DateTime.UtcNow}] {logLevel}: {message}");

    // ...
}

And to use the logging service (which logs to CloudWatch in Production), we can inject the Logger into any controller or Service:

public class AlexaRequestService : IAlexaRequestService
{
        private readonly ILogger _logger;

        public AlexaRequestService(
            // ...
            ILogger<AlexaRequestService> logger,
        )
        {
            // ...
            _logger = logger;
        }


        public AlexaResponsePayload ProcessAlexaRequest(AlexaRequestPayload alexaRequestPayload)
        {
            // ...
            _logger.LogInformation("Processing The Alexa Request");
        }
}

AWS ElasticCache (Memcache) & .NET Enyim Memcache Core

In the Wizard Adventures Alexa Skill, caching is used to keep certain pieces of information throughout a user’s interactions with the skill. For this skill, certain pieces of information are stored and set on certain incoming requests and used in game logic. This includes information stored in an AlexaRequestPayload object like the below attributes.

/// <summary>
/// The LastRequestType is saved with a request payload for caching between intents from a user
/// </summary>
[JsonProperty("lastRequestType")]
public string LastRequestType { get; set; }

[JsonProperty("numberCorrectInARow")]
public int? NumberCorrectInARow { get; set; }

To keep this application as distributed and not specific to one in-memory cache, the cache layer is moved to AWS ElastiCache. AWS ElastiCache provides a Memcache implementation that can be quickly integrated with .NET Core. ElastiCache gives the ability to set up a cluster of cache nodes, similar to how ElasticBeanstalk provides a ‘wrapper’ around autoscale EC2 instances.

After setting up an ElastiCache cluster, it can be integrated into the .NET Core application with the help of the EnyimMemcachedCore library: https://github.com/cnblogs/EnyimMemcachedCore

// Startup.cs

public void ConfigureServices(IServiceCollection services)
{
    // ...
    // Cache Services
    services.AddTransient<ICacheService, MemcacheService>();
    services.AddEnyimMemcached(options => Configuration.GetSection("enyimMemcached").Bind(options));

}
// appsettings.Production.json
"enyimMemcached": {
    "Servers": [
      {
        "Address": <MY_ADDRESS>.cfg.cache.amazonaws.com", // must be in VPC to access
        "Port": 11211
      }
    ]
  }

AWS Relational Database Service - SQL Server and Entity Framework Core

For this Alexa Skill, I use Microsoft Entity Framework and a code-first model approach to generating the database. The database is hosted on AWS Relational Database Service with SQL Server. Using Entity Framework in .NET Core is made possible through the EntityFrameworkCore nuget package https://docs.microsoft.com/en-us/ef/core/. This package is installed through the package manager command line in Visual Studio.

EF Core sets up an ApplicationDbContext class and the PackageManager tools in Visual Studio allow for migrations to be managed and auto generated on schema updates and saved in a Migrations directory.

public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{

    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
        : base(options)
    {
    }

    public ApplicationDbContext() /* Required for migrations */{ }

    public DbSet<AlexaMember> AlexaMembers { get; set; }
    public DbSet<AlexaRequest> AlexaRequests { get; set; }
    public DbSet<Spell> Spells { get; set; }
    public DbSet<Adventure> Adventures { get; set; }
    public DbSet<AlexaMemberCompletedSpell> AlexaMemberCompletedSpells { get; set; }
    public DbSet<AlexaMemberCompletedAdventure> AlexaMemberCompletedAdventures { get; set; }
    public DbSet<SoundFile> SoundFiles { get; set; }

    // ...
}

The ApplicationDbContext class than then be injected into any service classes to manage data access:

public class SpellService : ISpellService
{
    private readonly ApplicationDbContext _context;

    public SpellService(ApplicationDbContext context)
    {
        _context = context;
    } 
}

AWS S3 - Audio Service

For this Alexa Skill, many sound files are used. These sounds occur on different intents, such as an audio welcome on the LaunchRequest, and various sound files to be played and associated with spells, adventures, and prompts. Amazon Alexa offers a straightforward way to play these audio files through their Speech Synthesis Markup Language (SSML): https://developer.amazon.com/docs/custom-skills/speech-synthesis-markup-language-ssml-reference.html#audio :

As seen in the documentation, playing an audio sound requires an <audio src=”https-url.mp3” /> tag reference to the sound file in question. There are some restrictions on how audio files are accessed as documented.

For hosting these audio files, I followed the recommendations from the Alexa documentation and hosted the audio files in AWS S3.

For my skill, I download audio files from AudioJungle: https://audiojungle.net/
and use the FFmpeg command line tool to convert the audio files into an Alexa format. From there I upload the audio files into an S3 bucket. In my application, I have a SoundFile table to store file metadata (filename) and allow for Foreign Key relationship with Spells and Adventure resources (which play the sound files).

To play the audio files, I have a helper below, which returns a simple audio tag to use in building an AlexaResponsePayload.

public interface IAudioService
{
    string BuildAudioTagFromFileName(string filename);
}

public class AudioService : IAudioService
{
    private const string S3BucketUrlPrefix = "https://s3.amazonaws.com/alexa-wizard-adventures/";

    private readonly ILogger _logger;

    public AudioService(ILogger<AudioService> logger)
    {
        _logger = logger;
    }

    public string BuildAudioTagFromFileName(string filename)
    {
        if (string.IsNullOrEmpty(filename))
        {
            _logger.LogError("Empty FileName");
        }
        return string.Format("<audio src='{0}{1}'/>", S3BucketUrlPrefix, filename);
    }
}

Conclusion

This wraps up the overview of the Wizard Adventures Alexa Skill which was built using integrations with and a combination combination of:
* .NET Core / C# (Visual Studio & AWS Toolkit)
* AWS Certificate Manager
* AWS Route53
* AWS ElasticBeanstalk
* AWS Elastic Load Balancer
* AWS EC2
* AWS ElastiCache
* AWS Relational Database Service (SQL Server)
* AWS S3

Make sure you checkout Wizard Adventures (“Alexa, open Wizard Adventures”) to see everything in action. Wizard Adventures