In the last piece, I walked through setting up an AWS account and putting together the policies needed to deploy your code to Amazon’s Lambda service.
Now we’re going to write our code.
Complete project code on github
This will be a C# port of the “SpaceGeek” Alexa Skill sample (which was actually pulled down a couple days before this post was published. Grrr)
Also a huge debt is owed here to Tim Heuer and his piece on Alexa in C#.
First we need to get the Lambda C# template, which is in the AWS Toolkit for Visual Studio. Download and install that toolkit. Then open up Visual Studio & pick the AWS Lambda Project (.NET Core) from the AWS Lambda templates.
Name it “Space Geek” and create your new project. Select Empty Function for your blueprint & click Finish
You might get a Package restore failed error. Just right-click on the project & select Restore Packages and that should be fixed.
Now let’s add some helpful nuget packages for the project. Open Manage NuGet Packages and go to Browse. Type json.net and install the Json.NET package.
Now do the same for Alexa.NET, but make sure you check the Include prerelease checkbox. (Also keep an eye on the Alexa.NET github)
OK. We’re ready to go.
We’re porting the Alexa science facts skill, which supports multiple (human) languages. We won’t go that far, but we will build our skill with the ability to extend later on.
Adding our Facts
Open up Function.cs. First we’ll define an object to hold our facts as well as some other valuable properties (like our help and stop messages).
public class FactResource { public FactResource(string language) { this.Language = language; this.Facts = new List(); } public string Language { get; set; } public string SkillName { get; set; } public List<string> Facts { get; set; } public string GetFactMessage { get; set; } public string HelpMessage { get; set; } public string HelpReprompt { get; set; } public string StopMessage { get; set; } }
Inside the Fuction class, we’ll set a method to populate the resources
public List<FactResource> GetResources() { List<FactResource> resources = new List<FactResource>(); FactResource enUSResource = new FactResource("en-US"); enUSResource.SkillName = "American Space Facts"; enUSResource.GetFactMessage = "Here's your fact: "; enUSResource.HelpMessage = "You can say tell me a space fact, or, you can say exit... What can I help you with?"; enUSResource.HelpReprompt = "What can I help you with?"; enUSResource.StopMessage = "Goodbye!"; enUSResource.Facts.Add("A year on Mercury is just 88 days long."); enUSResource.Facts.Add("Despite being farther from the Sun, Venus experiences higher temperatures than Mercury."); enUSResource.Facts.Add("Venus rotates counter-clockwise, possibly because of a collision in the past with an asteroid."); enUSResource.Facts.Add("On Mars, the Sun appears about half the size as it does on Earth."); enUSResource.Facts.Add("Earth is the only planet not named after a god."); enUSResource.Facts.Add("Jupiter has the shortest day of all the planets."); enUSResource.Facts.Add("The Milky Way galaxy will collide with the Andromeda Galaxy in about 5 billion years."); enUSResource.Facts.Add("The Sun contains 99.86% of the mass in the Solar System."); enUSResource.Facts.Add("The Sun is an almost perfect sphere."); enUSResource.Facts.Add("A total solar eclipse can happen once every 1 to 2 years. This makes them a rare event."); enUSResource.Facts.Add("Saturn radiates two and a half times more energy into space than it receives from the sun."); enUSResource.Facts.Add("The temperature inside the Sun can reach 15 million degrees Celsius."); enUSResource.Facts.Add("The Moon is moving approximately 3.8 cm away from our planet every year."); resources.Add(enUSResource); return resources; }
and we’ll add a method to take in a resource and output a random fact string along with our FactMessage preface
public string emitNewFact(FactResource resource, bool withPreface) { Random r = new Random(); if(withPreface) return resource.GetFactMessage + resource.Facts[r.Next(resource.Facts.Count)]; return resource.Facts[r.Next(resource.Facts.Count)]; }
Writing our Skill
In the Function class, we’ll look at the FunctionHandler and change the method handler to:
At the beginning of our function, we’ll create a Response to return and set up our logger and resources.
SkillResponse response = new SkillResponse(); response.Response.ShouldEndSession = false; IOutputSpeech innerResponse = null; var log = context.Logger; var allResources = GetResources(); var resource = allResources.FirstOrDefault();
We’ll be handling 2 kinds of requests, a launch request and an intent request. Often we’ll want to give some special introduction with our launch request. In this case, we just want to kick out a random fact.
if (input.GetRequestType() == typeof(LaunchRequest)){ log.LogLine($"Default LaunchRequest made: 'Alexa, open Science Facts"); innerResponse = new PlainTextOutputSpeech(); (innerResponse as PlainTextOutputSpeech).Text = emitNewFact(resource, true); }
If our request is an intent, there are several kinds of built-in intents and we’re not going to handle them all, but we will handle the AMAZON.CancelIntent, AMAZON.StopIntent, and AMAZON.HelpIntent intents, along with our own custom GetFactIntent and GetNewFactIntent.
else if (input.GetRequestType() == typeof(IntentRequest)) { var intentRequest = (IntentRequest)input.Request; switch (intentRequest.Intent.Name) { case "AMAZON.CancelIntent": log.LogLine($"AMAZON.CancelIntent: send StopMessage"); innerResponse = new PlainTextOutputSpeech(); (innerResponse as PlainTextOutputSpeech).Text = resource.StopMessage; response.Response.ShouldEndSession = true; break; case "AMAZON.StopIntent": log.LogLine($"AMAZON.StopIntent: send StopMessage"); innerResponse = new PlainTextOutputSpeech(); (innerResponse as PlainTextOutputSpeech).Text = resource.StopMessage; response.Response.ShouldEndSession = true; break; case "AMAZON.HelpIntent": log.LogLine($"AMAZON.HelpIntent: send HelpMessage"); innerResponse = new PlainTextOutputSpeech(); (innerResponse as PlainTextOutputSpeech).Text = resource.HelpMessage; break; case "GetFactIntent": log.LogLine($"GetFactIntent sent: send new fact"); innerResponse = new PlainTextOutputSpeech(); (innerResponse as PlainTextOutputSpeech).Text = emitNewFact(resource, false); break; case "GetNewFactIntent": log.LogLine($"GetFactIntent sent: send new fact"); innerResponse = new PlainTextOutputSpeech(); (innerResponse as PlainTextOutputSpeech).Text = emitNewFact(resource, false); break; default: log.LogLine($"Unknown intent: " + intentRequest.Intent.Name); innerResponse = new PlainTextOutputSpeech(); (innerResponse as PlainTextOutputSpeech).Text = resource.HelpReprompt; break; } }
Those are all the intents we need to cover, so now we just need to attach that innerResponse to our response object and attach that to the SkillResponse.
response.Response.OutputSpeech = innerResponse; response.Version = "1.0"; return response;
That’s it for the code (which you can find in totality at my github).
The next step is to deploy our project to Amazon’s Lambda service, which we’ll cover in the next tutorial.