Matthias Shapiro

I need fewer hobbies

Tutorial: Alexa Skills in C# – The Code

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.

This entry was posted in Alexa, c#. Bookmark the permalink.

6 Responses to Tutorial: Alexa Skills in C# – The Code

  1. Bob Sholtes

    Hi, thanks for the great article, but I too am getting the Null reference exception. Latest pull is your Beta-5 branch.

    • matthiasshapiro

      Ah, I see the problem. The github code was fixed, but I didn’t update the code in the blog post

      Use the following:

      SkillResponse response = new SkillResponse();
      response.Response = new ResponseBody();
      response.Response.ShouldEndSession = false;
      IOutputSpeech innerResponse = null;

  2. Noor-ul-ain Ali

    Hi,
    Your tutorial looks great but when I am trying to deploy and test it it throws Null reference exception(see below). Can you please explain, i have changed nothing in your code, just download and try to test it.

    {
    “errorType”: “NullReferenceException”,
    “errorMessage”: “Object reference not set to an instance of an object.”,
    “stackTrace”: [
    “at SpaceGeek.Function.FunctionHandler(SkillRequest input, ILambdaContext context)”,
    “at lambda_method(Closure , Stream , Stream , ContextInfo )”
    ]
    }

    • matthiasshapiro

      Do another pull… there seemed to be a problem with the response body trying to set a property before it was initialized. It should be fixed.

      • Noor-ul-ain Ali

        Hi,

        I made it work using Slight.Alexa.Core. Thanks. Also can you tell me where can I find documentation to use session attributes, I want to make a conversational skill.

        Thanks.