Alexa.Tip – Using Dependency Injection with Handlers 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 taking our Handler Registration Pattern to the next level by using dependencies in them such as EF DbContexts or really any other service you want to inject.

Check out all the raw source code for this post, and more here: https://github.com/SuavePirate/Alexa.Tips.Net

If you haven’t read up on how to use the Handler Registration Pattern, take a look at my earlier post here: Alexa.Tip – Using Handler Registration Pattern in .NET

The short version is that we use this pattern of registering IHandler implementations to handle different types of requests that our skill receives, regardless of whether we are using AWS Lambdas or ASP.NET Core APIs.

Building Handlers with Dependencies

So we’ve seen samples of simple Handlers that use static responses simply using the ResponseBuilder from the Alexa.NET Library, but now let’s take it to the next logical step and start to get some data from a database. In this case, I am using Entity Framework, and therefore need to access my DbContext to get data.

To do this, we should inject the DbContext, in this case a SampleMessageDbContext into the constructor of the SampleFactHandler, then use it locally in the HandleAsync() method to get a single message.

SampleFactHandler.cs

public class SampleFactHandler : GenericHandler, IHandler
{
    private readonly SampleMessageDbContext _context;
    public SampleFactHandler(SampleMessageDbContext context)
    {
        _context = context;
    }
    public override string IntentName => "SampleMessageIntent";

    public override Type RequestType => typeof(IntentRequest);

    public override async Task<SkillResponse> HandleAsync(SkillRequest request)
    {
        // just grab one as an example
        var message = await _context.SampleMessages.FirstOrDefaultAsync();
        return ResponseBuilder.Tell(message?.Content ?? "I don't have any messages for you.");
    }
}

Although this simple example just grabs the first row from the db for the SampleMessages table, you could imagine some more complex data logic applied here to find the proper response we want for the given request. I also added some quick null handling just to clean it up for a real sample.

Registering Handlers with Dependencies

Now that we have our SampleFactHandler, we need to register it to our ICollection, but also construct it using the SampleMessageDbContext. There are a couple ways to do this. For simple situations, you just need to register the DbContext in your Startup, then grab it from the ServiceCollection when creating the List:

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)
    {
        // add other dependencies first
        services.AddDbContext<SampleMessageDbContext>(options =>
        {
            options.UseInMemoryDatabase("InMemoryDbForTesting");
        });

        // Register your handlers here!
        services.AddScoped<ICollection<IHandler>>(s =>
        {
            // get the db context to inject in the handlers that are dependent
            var dbContext = s.GetRequiredService<SampleMessageDbContext>();
            return new List<IHandler>
            {
                new SimpleLaunchHandler(),
                new DogFactHandler(),
                new SampleFactHandler(dbContext)
            };
        });

        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();
    }
}

Alternatively, you could create a class whose responsibility is to hold onto the handlers and construct that list with all the IHandlers. Something like this:

IHandlerContainer.cs

public interface IHandlerContainer
{
    ICollection<IHandler> Handlers { get; }
}

HandlerContainer.cs

public class HandlerContainer : IHandlerContainer 
{
    public ICollection<IHandler> Handlers { get; private set; }
    public HandlerContainer(SimpleLaunchHandler launch, DogFactHandler dogFact, SampleFactHandler sampleFact)
    {
        Handlers = new List<IHandler> { launch, dogFact, sampleFact }
    }
}

With this container, we update our Startup to look something like this:

 public void ConfigureServices(IServiceCollection services)
    {
        // add other dependencies first
        services.AddDbContext<SampleMessageDbContext>(options =>
        {
            options.UseInMemoryDatabase("InMemoryDbForTesting");
        });
        services.AddScoped<SimpleLaunchHandler>();
        services.AddScoped<DogFactHandler>();
        services.AddScoped<SampleFactHandler>();
        services.AddScoped<IHandlerContainer, HandlerContainer>();

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

Then our Controller would consume the IHandlerContainer rather than a flat ICollection to look like this:

SimpleAlexaController.cs

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

I think this little pattern helps cleanup the Startup without needing to explicitly pull dependencies out of the ServiceCollection in order to build the IHandlers. Which do you prefer?

What’s next?

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
  • 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

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s