In this Alexa.Tip series, we explore some little bits of code that can make your life easier in developing Alexa Skills in many languages including C# + .NET, node.jS + TypeScript, Kotlin, etc. We look at concepts that developers might not be aware of, design patterns that and how they can be applied to voice application development, best practices, and more!
In this post, we explore some more best practices in developing Alexa Skills in C# whether you are using an ASP.NET Core API or an AWS Lambda. This time, we talk about abstracting our business logic out of either our Function
or Controller
and into separate Handler
classes.
If you’ve done skill development in node.js or in .NET, you’ve probably noticed that a lot of docs, example apps, and real world apps are written with one BIG class or file. That’s pretty gross.
You may some C# examples look like this:
UglyFunction.cs
public class Function { public async Task<SkillResponse> HandleRequest(SkillRequest input) { if(input.GetRequestType() == typeof(LaunchRequest)) { return ResponseBuilder.Ask("Welcome to my skill written in one big function class with all my business logic in one place and no real testability! Ask me anything!", null); } if(input.GetRequestType() == typeof(IntentRequest)) { var intentRequest = input.Request as IntentRequest; switch(intentRequest.Intent.Name) { case "MyIntent1": // get slot values // search database // validate data // build response // add card if it supports a screen // return break; case "Intent2": // get slot values // search third party api // validate data // build response // add card if it supports a screen // return break; case "Intent3": // get slot values // search database // validate data // build response // add card if it supports a screen // return break; case "Intent4": // get slot values // search third party api // validate data // build response // add card if it supports a screen // return break; case "Intent5": // get slot values // search database // validate data // build response // add card if it supports a screen // return break; // I think you get my point here... } } return ResponseBuilder.Tell("Something went wrong. Please try again later"); } }
So that’s pretty gross right? It’s untestable, difficult to update, and hard to read! But hey, we’re C# developers. So let’s build Alexa Skills like C# developers.
The proposal here is to abstract your intent logic into Handler
classes. Then you can inject those either into your Function
or your Controller
if you’re using a RESTful API.
Here’s something that I hope can get you started:
– A generic IIntentHandler
– An inherited specific IIntentWhateverHandler
– An implementation of the IIntentWhateverHandler
– A testable and injectable version to be used in the function.
Let’s start.
IIntentHandler.cs
public interface IIntentHandler { Task<SkillResponse> HandleIntent(IntentRequest input); }
Now let’s get specific. In this simple case, this interface is empty but exists for registration – although you can add specific methods here as needed.
IMyIntentHandler.cs
public interface IMyIntentHandler : IIntentHandler { }
Now let’s create an example implementation of an intent handler that houses the business logic of the request.
MyIntentHandler.cs
public class MyIntentHandler : IMyIntentHandler { private readonly MyDbContext _context; public MyIntentHandler(MyDbContext context) { // oh snap, we can inject db context _context = context; } public async Task<SkillResponse> HandleIntent(IntentRequest input) { var mySlot = input.Slots["MySlot"].Value; // assumes the slot is there from it being required in the interaction model var myMessage = await _context.Messages.FirstOrDefaultAsync(m => m.SomeValue == mySlot) if(myMessage == null) { return ResponseBuilder.Ask("I don't know that one. Try something else."); } return ResponseBuilder.Tell(myMessage.Content); } }
Now let’s go about adding these handlers to our Function (the same thing can apply to our Controllers
.
PrettyFunction.cs
public class Function { private readonly IMyIntentHandler _myIntentHandler; private void Setup() { _myIntentHandler = new MyIntentHandler(...); // create the others - optionally implement a ServiceCollection // to handle proper dependency injection } public async Task<SkillResponse> HandleRequest(SkillRequest input) { if(input.GetRequestType() == typeof(LaunchRequest)) { return ResponseBuilder.Ask("Welcome to my skill written in one big function class with all my business logic in one place and no real testability! Ask me anything!", null); } if(input.GetRequestType() == typeof(IntentRequest)) { var intentRequest = input.Request as IntentRequest; switch(intentRequest.Intent.Name) { case "MyIntent1": return await _myIntentHandler.HandleIntent(intentRequest); break; } } return ResponseBuilder.Tell("Something went wrong. Please try again later"); } }
So now you can separately test your Handler
classes, your Data
classes, and your Function
as a whole.
SO much better 🙂
Conclusion
We don’t have to follow how Amazon writes their node.js skills when we write them in C# – let’s use some proper OO design and testability to build some scalable and awesome skills!
In the next post, we’ll talk about taking this one step closer to avoid that gross switch
statement and simply register our Handler
implementations for certain intents or RequestTypes
which is more similar to how the actual ASK SDK
works.
Stay tuned!
If you like what you see, don’t forget to follow me on twitter @Suave_Pirate, check out my GitHub, and subscribe to my blog to learn more mobile and AI developer tips and tricks!
Interested in sponsoring developer content? Message @Suave_Pirate on twitter for details.
I’m the Director and Principal Architect over at Voicify. Learn how you can use the Voice Experience Platform to bring your brand into the world of voice on Alexa, Google Assistant, Cortana, chat bots, and more: https://voicify.com/
One thought on “Alexa.Tip – Build Intent Handlers in .NET”