Skip to content

Commit 571699d

Browse files
authored
Merge pull request #76 from aws-samples/lambda-alexa
Lambda and Alexa Sample
2 parents b530495 + 5b5d252 commit 571699d

File tree

18 files changed

+695
-0
lines changed

18 files changed

+695
-0
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Building an Alexa Skill with AWS Lambda
2+
3+
Alexa Skill developers have the ability to create skills that execute an AWS Lambda Function on the backend to complete a task needed for the skill. Since AWS Lamdba functions can be written in .NET, it is exciting to see what great things we can build for smart home assistants like Amazon Echo.
4+
5+
This code repository accompanies a series of blog posts titled [Building an Alexa Skill with AWS Lambda and Amazon DynamoDB](ADD LINK LATER)
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
using Amazon.Lambda.Core;
2+
using Microsoft.Extensions.DependencyInjection;
3+
4+
// Assembly attribute to enable the Lambda function's JSON input to be converted into a .NET class.
5+
[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))]
6+
7+
namespace TMDBAlexa.SeedData;
8+
9+
public class Function
10+
{
11+
private readonly LambdaEntryPoint _entryPoint;
12+
13+
public Function()
14+
{
15+
var startup = new Startup();
16+
IServiceProvider provider = startup.Setup();
17+
18+
_entryPoint = provider.GetRequiredService<LambdaEntryPoint>();
19+
}
20+
21+
public async Task<string> FunctionHandler(ILambdaContext context)
22+
{
23+
return await _entryPoint.Handler();
24+
}
25+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
using Amazon.DynamoDBv2.Model;
2+
using Amazon.DynamoDBv2;
3+
using Microsoft.Extensions.Logging;
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Linq;
7+
using System.Text;
8+
using System.Threading.Tasks;
9+
using TMDbLib.Client;
10+
using Microsoft.Extensions.Configuration;
11+
using TMDBAlexa.Shared;
12+
13+
namespace TMDBAlexa.SeedData
14+
{
15+
public class LambdaEntryPoint
16+
{
17+
private readonly ILogger<LambdaEntryPoint> _logger;
18+
private readonly DynamoDBService _dbService;
19+
private readonly IConfiguration _config;
20+
public LambdaEntryPoint(ILogger<LambdaEntryPoint> logger, DynamoDBService dbService, IConfiguration config)
21+
{
22+
_logger = logger;
23+
_dbService = dbService;
24+
_config = config;
25+
}
26+
27+
public async Task<string> Handler()
28+
{
29+
_logger.LogInformation("Handler invoked");
30+
31+
TMDbClient client = new TMDbClient(_config["TMDBApiKey"]);
32+
33+
await _dbService.CreateTable();
34+
35+
for (int i = 1; i < 100; i++)
36+
{
37+
var results = await client.GetMoviePopularListAsync("en-US", i);
38+
39+
await _dbService.WriteToTable(results);
40+
}
41+
42+
return "Done";
43+
}
44+
}
45+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"profiles": {
3+
"Mock Lambda Test Tool": {
4+
"commandName": "Executable",
5+
"commandLineArgs": "--port 5050",
6+
"workingDirectory": ".\\bin\\$(Configuration)\\net6.0",
7+
"executablePath": "%USERPROFILE%\\.dotnet\\tools\\dotnet-lambda-test-tool-6.0.exe"
8+
}
9+
}
10+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
using Amazon.DynamoDBv2;
2+
using Amazon.Extensions.NETCore.Setup;
3+
using Microsoft.Extensions.Configuration;
4+
using Microsoft.Extensions.DependencyInjection;
5+
using Microsoft.Extensions.Logging;
6+
using System;
7+
using System.Collections.Generic;
8+
using System.Linq;
9+
using System.Text;
10+
using System.Threading.Tasks;
11+
using TMDBAlexa.Shared;
12+
13+
namespace TMDBAlexa.SeedData
14+
{
15+
public class Startup
16+
{
17+
public IServiceProvider Setup()
18+
{
19+
var configuration = new ConfigurationBuilder()
20+
.SetBasePath(Directory.GetCurrentDirectory())
21+
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
22+
.AddEnvironmentVariables()
23+
.Build();
24+
25+
var services = new ServiceCollection();
26+
27+
services.AddSingleton<IConfiguration>(configuration);
28+
29+
services.AddLogging(loggingBuilder =>
30+
{
31+
loggingBuilder.ClearProviders();
32+
});
33+
34+
35+
ConfigureServices(configuration, services);
36+
37+
IServiceProvider provider = services.BuildServiceProvider();
38+
39+
return provider;
40+
}
41+
42+
private void ConfigureServices(IConfiguration configuration, ServiceCollection services)
43+
{
44+
AWSOptions awsOptions = configuration.GetAWSOptions();
45+
services.AddDefaultAWSOptions(awsOptions);
46+
services.AddAWSService<IAmazonDynamoDB>();
47+
48+
services.AddSingleton<LambdaEntryPoint, LambdaEntryPoint>();
49+
services.AddSingleton<DynamoDBService, DynamoDBService>();
50+
}
51+
}
52+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<TargetFramework>net6.0</TargetFramework>
4+
<ImplicitUsings>enable</ImplicitUsings>
5+
<Nullable>enable</Nullable>
6+
<GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
7+
<AWSProjectType>Lambda</AWSProjectType>
8+
<!-- This property makes the build directory similar to a publish directory and helps the AWS .NET Lambda Mock Test Tool find project dependencies. -->
9+
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
10+
<!-- Generate ready to run images during publishing to improve cold start time. -->
11+
<PublishReadyToRun>true</PublishReadyToRun>
12+
</PropertyGroup>
13+
<ItemGroup>
14+
<PackageReference Include="Amazon.Lambda.Core" Version="2.1.0" />
15+
<PackageReference Include="Amazon.Lambda.Serialization.SystemTextJson" Version="2.3.0" />
16+
<PackageReference Include="AWSSDK.DynamoDBv2" Version="3.7.3.66" />
17+
<PackageReference Include="AWSSDK.Extensions.NETCore.Setup" Version="3.7.2" />
18+
<PackageReference Include="System.Configuration.ConfigurationManager" Version="6.0.0" />
19+
20+
<PackageReference Include="TMDbLib" Version="1.9.2" />
21+
22+
<!-- Packages required for Configuration -->
23+
<PackageReference Include="Microsoft.Extensions.Configuration" Version="6.0.1" />
24+
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="6.0.1" />
25+
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="6.0.0" />
26+
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="6.0.0" />
27+
<!---->
28+
29+
<!-- Packages required for Logging -->
30+
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
31+
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="6.0.0" />
32+
</ItemGroup>
33+
<ItemGroup>
34+
<ProjectReference Include="..\TMDBAlexa.Shared\TMDBAlexa.Shared.csproj" />
35+
</ItemGroup>
36+
<ItemGroup>
37+
<None Update="appsettings.json">
38+
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
39+
</None>
40+
</ItemGroup>
41+
</Project>
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"TMDBApiKey": ""
3+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
2+
{
3+
"Information" : [
4+
"This file provides default values for the deployment wizard inside Visual Studio and the AWS Lambda commands added to the .NET Core CLI.",
5+
"To learn more about the Lambda commands with the .NET Core CLI execute the following command at the command line in the project root directory.",
6+
"dotnet lambda help",
7+
"All the command line options for the Lambda command can be specified in this file."
8+
],
9+
"profile" : "default",
10+
"region" : "us-west-2",
11+
"configuration" : "Release",
12+
"function-runtime" : "dotnet6",
13+
"function-memory-size" : 256,
14+
"function-timeout" : 30,
15+
"function-handler" : "TMDBAlexa.SeedData::TMDBAlexa.SeedData.Function::FunctionHandler",
16+
"framework" : "net6.0",
17+
"function-name" : "TMDBAlexaSeedData",
18+
"package-type" : "Zip",
19+
"function-role" : "arn:aws:iam::563011967245:role/lambda_exec_TMDBAlexaSeedData",
20+
"function-architecture" : "x86_64",
21+
"function-subnets" : "",
22+
"function-security-groups" : "",
23+
"tracing-mode" : "PassThrough",
24+
"environment-variables" : "",
25+
"image-tag" : "",
26+
"function-description" : ""
27+
}
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
using Amazon.DynamoDBv2;
2+
using Amazon.DynamoDBv2.DataModel;
3+
using Amazon.DynamoDBv2.Model;
4+
using Microsoft.Extensions.Logging;
5+
using TMDbLib.Objects.General;
6+
using TMDbLib.Objects.Search;
7+
8+
namespace TMDBAlexa.Shared
9+
{
10+
public class DynamoDBService
11+
{
12+
private readonly ILogger<DynamoDBService> _logger;
13+
private readonly AmazonDynamoDBClient _client = new AmazonDynamoDBClient();
14+
private readonly string _tableName = "movies";
15+
16+
public DynamoDBService(ILogger<DynamoDBService> logger)
17+
{
18+
_logger = logger;
19+
}
20+
21+
public async Task<MovieModel> SearchTable(string searchText)
22+
{
23+
DynamoDBContext context = new DynamoDBContext(_client);
24+
25+
var conditions = new List<ScanCondition>()
26+
{
27+
new ScanCondition("TitleLower", Amazon.DynamoDBv2.DocumentModel.ScanOperator.Contains, searchText.ToLower())
28+
};
29+
30+
var queryResult = await context.ScanAsync<MovieModel>(conditions).GetRemainingAsync();
31+
32+
if (queryResult.Count > 0)
33+
{
34+
return queryResult.FirstOrDefault();
35+
}
36+
return null;
37+
}
38+
39+
public async Task CreateTable()
40+
{
41+
await DeleteTable();
42+
var request = new CreateTableRequest
43+
{
44+
AttributeDefinitions = new List<AttributeDefinition>()
45+
{
46+
new AttributeDefinition
47+
{
48+
AttributeName = "id",
49+
AttributeType = "N"
50+
},
51+
new AttributeDefinition
52+
{
53+
AttributeName = "popularity",
54+
AttributeType = "N"
55+
}
56+
},
57+
KeySchema = new List<KeySchemaElement>
58+
{
59+
new KeySchemaElement
60+
{
61+
AttributeName = "id",
62+
KeyType = "HASH" //Partition key
63+
},
64+
new KeySchemaElement
65+
{
66+
AttributeName = "popularity",
67+
KeyType = "RANGE" //Sort key
68+
}
69+
},
70+
ProvisionedThroughput = new ProvisionedThroughput
71+
{
72+
ReadCapacityUnits = 5,
73+
WriteCapacityUnits = 6
74+
},
75+
TableName = _tableName
76+
};
77+
78+
var response = await _client.CreateTableAsync(request);
79+
80+
var tableDescription = response.TableDescription;
81+
_logger.LogInformation("{1}: {0} \t ReadsPerSec: {2} \t WritesPerSec: {3}",
82+
tableDescription.TableStatus,
83+
tableDescription.TableName,
84+
tableDescription.ProvisionedThroughput.ReadCapacityUnits,
85+
tableDescription.ProvisionedThroughput.WriteCapacityUnits);
86+
87+
string status = tableDescription.TableStatus;
88+
_logger.LogInformation(_tableName + " - " + status);
89+
90+
await WaitUntilTableReady(_tableName);
91+
}
92+
93+
public async Task WriteToTable(SearchContainer<SearchMovie> results)
94+
{
95+
DynamoDBContext context = new DynamoDBContext(_client);
96+
97+
BatchWrite<MovieModel> model = context.CreateBatchWrite<MovieModel>();
98+
99+
foreach (var movie in results.Results)
100+
{
101+
model.AddPutItem(new MovieModel
102+
{
103+
Id = movie.Id,
104+
Overview = movie.Overview,
105+
Popularity = movie.Popularity,
106+
ReleaseDate = movie.ReleaseDate.ToString(),
107+
Title = movie.Title,
108+
TitleLower = movie.Title.ToLower(),
109+
VoteAverage = movie.VoteAverage,
110+
VoteCount = movie.VoteCount
111+
});
112+
}
113+
114+
await model.ExecuteAsync();
115+
}
116+
117+
private async Task DeleteTable()
118+
{
119+
try
120+
{
121+
await _client.DescribeTableAsync(new DescribeTableRequest
122+
{
123+
TableName = _tableName
124+
});
125+
126+
DeleteTableRequest deleteRequest = new DeleteTableRequest
127+
{
128+
TableName = _tableName
129+
};
130+
131+
await _client.DeleteTableAsync(deleteRequest);
132+
133+
Thread.Sleep(5000);
134+
}
135+
catch (ResourceNotFoundException)
136+
{ }
137+
}
138+
139+
private async Task WaitUntilTableReady(string tableName)
140+
{
141+
string status = null;
142+
// Let us wait until table is created. Call DescribeTable.
143+
do
144+
{
145+
System.Threading.Thread.Sleep(5000); // Wait 5 seconds.
146+
try
147+
{
148+
var res = await _client.DescribeTableAsync(new DescribeTableRequest
149+
{
150+
TableName = tableName
151+
});
152+
153+
_logger.LogInformation("Table name: {0}, status: {1}",
154+
res.Table.TableName,
155+
res.Table.TableStatus);
156+
status = res.Table.TableStatus;
157+
}
158+
catch (ResourceNotFoundException)
159+
{
160+
// DescribeTable is eventually consistent. So you might
161+
// get resource not found. So we handle the potential exception.
162+
}
163+
} while (status != "ACTIVE");
164+
}
165+
}
166+
}

0 commit comments

Comments
 (0)