Alexa.Tip – Using Handler Registration Pattern in .NET

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, design patterns, and implementations that developers might not be aware of, 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 using the Registration Pattern for our Request Handlers. This post builds off of some of the concepts found in a previous post: Alexa.Tip – Build Intent Handlers in .NET but uses a newer abstraction tactic I like that can be found in my Alexa .NET Samples Repo here: https://github.com/SuavePirate/Alexa.Tips.Net

If you followed the previous post, you saw the benefits of abstracting our Alexa specific business logic into Handlers. That post talked specifically about IntentHandlers, but I’ve gone about implementing a new pattern for handling all request types that follows some of the same patterns of the official Alexa Skills Kit SDKs in JavaScript and Java but with all the C# goodness of Dependency Injection, Abstractions, async, and more.

Build Handlers

The primary concept is to create Handlers that register with information about what types of requests they can handle, then register those handlers for Dependency Injection (but in a slightly different way than normal), then finding the proper Handler for the given request and passing the rest of the transaction off to it to run its logic.

Here’s a look at our foundation: the IHandler:

IHandler.cs

public interface IHandler
{
    Type RequestType { get; }
    string IntentName { get; }
    bool CanHandle(SkillRequest request);
    Task<SkillResponse> HandleAsync(SkillRequest request);
}

So with this, we can use RequestType to validate what type of request the handler is for such as LaunchRequest or IntentRequest. You can take a peep at all the request types of Alexa Skills here: https://developer.amazon.com/docs/custom-skills/request-types-reference.html

We can also use the IntentName to easily assign a specific intent to a handler if the RequestType is IntentRequest. Then CanHandle() is used for applying custom validation logic to tell the registration what type of requests it is for. An implementation of this might validate the RequestType and IntentName while still allowing a specific Handler to apply its own logic to whether it can handle the request or not such as checking sessionAttributes or Context.

To handle this abstraction, I implemented an abstract class to implement the default CanHandle:

GenericHandler.cs

public abstract class GenericHandler : IHandler
{
    public abstract string IntentName { get; }
    public abstract Type RequestType { get; }
    public abstract Task<SkillResponse> HandleAsync(SkillRequest request);
    public virtual bool CanHandle(SkillRequest request)
    {
        if (request.GetRequestType() != RequestType)
            return false;
        if(request.GetRequestType() == typeof(IntentRequest))
        {
            if ((request?.Request as IntentRequest)?.Intent?.Name != IntentName)
                return false;
        }
        return true;
    }
}

If you are using C# 8 (I’m not in this case because its in preview at time of writing this), you can just use the Default Interface Implementation feature instead of using this extra abstract class at all. So your IHandler would look like this:

IHandler.cs

public interface IHandler
{
    Type RequestType { get; }
    string IntentName { get; }
    Task<SkillResponse> HandleAsync(SkillRequest request);
    bool CanHandle(SkillRequest request)
    {
        if (request.GetRequestType() != RequestType)
            return false;
        if(request.GetRequestType() == typeof(IntentRequest))
        {
            if ((request?.Request as IntentRequest)?.Intent?.Name != IntentName)
                return false;
        }
        return true;
    }
}

The last bit is the actual HandleAsync() which is just used to actually process the request and build a response to send back to Alexa.

So let’s look at a real implementation with the most simple example I can think of.

SimpleLaunchHandler.cs

public class SimpleLaunchHandler : GenericHandler, IHandler
{
    public override string IntentName => null;

    public override Type RequestType => typeof(LaunchRequest);

    public override Task<SkillResponse> HandleAsync(SkillRequest request)
    {
        return Task.FromResult(ResponseBuilder.Ask("Welcome to abstracted .NET Alexa Skills. How can I help?", null));
    }
}

Request type of LaunchRequest because we are handling the welcome message of the skill. We don’t override the CanHandle because we only care about the RequestType, and then the HandleAsync just returns a result task with a simple text-only response welcoming the user.

Here’s an example of a basic Intent Handler for a specific intent:

DogFactHandler.cs

public class DogFactHandler : GenericHandler, IHandler
{
    public override string IntentName => "DogFactIntent";

    public override Type RequestType => typeof(IntentRequest);

    public override Task<SkillResponse> HandleAsync(SkillRequest request)
    {
        return Task.FromResult(ResponseBuilder.Tell("Dogs do in fact have a sense of time, and even miss you when you're gone."));
    }
}

Just like the SimpleLaunchHandler, we set the RequestType but instead to IntentRequest, then supply the name of the Intent we want to handle.

Find and Execute Handlers

Now that we have some implementations of IHandlers, we can register them to use in our actual skill. The general idea is to create the collection of these handlers, then find the correct one to use given the type of request and execute it.

In it’s simplest form, we have a collection of handlers:

var handlers = new List<IHandler> { new SimpleLaunchHandler(), new DogFactHandler() };

Then find the right one to use:

var foundHandler = handlers.FirstOrDefault(h => h.CanHandle());

Then execute it:

var response = await foundHandler.HandleAsync(request);

Implement in ASP.NET Core

Let’s take a look at a real sample of how to do this using ASP.NET Core as if we were using HTTPS as our fulfillment URL of our skill rather than an AWS Lambda ARN.

First thing we should do is register our IHandlers for dependency injection in our Startup:

Startup.cs

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        // Register your handlers here!
        services.AddScoped<ICollection<IHandler>>(s =>
        {
            return new List<IHandler>
            {
                new SimpleLaunchHandler(),
                new DogFactHandler()
            };
        });

        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        if (env.IsDevelopment())
            app.UseDeveloperExceptionPage();
        else
            app.UseHsts();

        app.UseHttpsRedirection();
        app.UseMvc();
    }
}

Now we can create a Controller that consumes an ICollection in the constructor. Alternatively, you can also register the IHandlers independently and then build the collection in the constructor of the Controller instead, but I kinda like this way better.

SimpleAlexaController.cs

[Route("[controller]")]
public class SimpleAlexaController : Controller
{
    private readonly ICollection<IHandler> _handlers;
    public SimpleAlexaController(ICollection<IHandler> handlers)
    {
        _handlers = handlers;
    }
    [HttpPost]
    public async Task<SkillResponse> HandleRequest([FromBody]SkillRequest request)
    {
        var viableHandler = _handlers.FirstOrDefault(h => h.CanHandle(request));
        return await viableHandler.HandleAsync(request);
    }
}

And that’s it! Now we can send requests to our /SimpleAlexa/ endpoint and use the handlers we registered. As we add more functionality to our Skill, we just create new IHandlers and add them to the Startup registration.

Implement in Lambda

So you might already know that Lambda functions don’t really play well with dependency injection outside of building a simple service locator, and their recommended practice is to build your code all in the lambda (yucky for us C# devs) or build your depdendent classes all at the beginning of executing the function. For this scenario, I like to have a single method for building my primary dependency (in this case, it’s the list of handlers again!). So our full Lambda Function code might looke like this:

Function.cs

public class Function 
{
    public async Task FunctionHandler(SkillRequest request, ILambdaContext context)
    {
        var viableHandler = BuildHandlers().FirstOrDefault(h => h.CanHandle(request));
        return await viableHandler.HandleAsync(request);
    }

    private ICollection<IHandler> BuildHandlers()
    {
        return new List<IHandler> { new SimpleLaunchHandler(), new DogFactHandler() };
    }
}

What’s next?

This is a pretty neat design pattern I’ve been working out in some custom Alexa Skills to help scale the codebase of more complex scenarios. These samples are quite simple, but hopefully convey how easy it is to get setup with this pattern, and start to get you thinking about ways to build ontop of this pattern.

In future posts, we’ll take a look at building on these types of handlers with things like:

  • Well written Unit Tests
  • Full Integration Tests
  • Using Entity Framework
  • Using multi-level dependency injection
  • Advanced Contextual driven handlers

If there’s enough interest in this pattern and the tools I’m building around it, let me know in GitHub or Twitter and I’ll work on getting them into properly libraries and NuGet packages 🙂

Check out more Alexa Developer Tips here: https://alexdunn.org/tag/alexa/


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.


voicify_logo
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/


Advertisements