はじめに
同一セッション内でデータを保持する方法を学んだ公式チュートリアル第3回でしたが、そこから第4回に至るまでは思いの外長い道のりでした。
C#からDynamoDBにアクセスする方法を基礎から調べ、やっとできた、と思ったら低レベルAPIを使っていることがわかり、高レベルAPIでのやり方に変え、最終的にはAlexaスキルでの用途に限定したクラスを作ってそれをNuGetギャラリーに公開、と。
やっと、公式チュートリアル第4回をC#で実装する準備が整いました。
NuGetギャラリーからパッケージをインストール
Visual StudioでNuGetパッケージマネージャーを使ってAlexaPersistentAttributesManager
をインストールしてください。
ざっくり使い方
Alexaスキル特化ですが、DynamoDBにデータを追加したり、データを取得したりするのがほんの数ステップでできます。
AttributesManager
クラスをインスタンス化
//このとき、コンストラクタの中で指定したテーブルに`_tableName`で指定したテーブルが作られます。`userId`はgetオンリーなプロパティに格納され、テーブルへのデータの追加、取得の際にプライマリキーとして使われます。
var attrManager = new AttributesManager(userId, _tableName);
DynamoDB上のテーブルにデータをセットする方法
attrManagerにキーバリューペアの形でデータを追加していく。
//"starSign" as a key, "Gemini" as a value
attrManager.SetPersistentAttributes("starSign", "Gemini");
attrManager.SetPersistentAttributes("color", "blue");
//.....
SavePersistentAttributes
メソッドでデータをDynamoDB内のテーブルに追加します。
attrManager.SavePersistentAttributes();
DynamoDB上のテーブルからデータを取得する方法
GetPersistentAttributes
メソッドでテーブル上のid
カラムがuserId
であるレコードのattributes
カラムの値を取得します。
取得したデータはキーバリューペアなのでキーを指定すると、対応する値を取得することができます。
var attr = attrManager.GetPersistentAttributes();
var sign = attr["starSign"];
こちらがソースコード全体になります
HoroscopeIntentHandler
内で「星座」の情報を取得して、それをセッションアトリビュートと永続アトリビュート(DynamoDB)に格納します。
そしてLuckyColorIntentHandler
ではセッションアトリビュートまたは永続アトリビュートを取得しています。
using Alexa.NET.Request;
using Alexa.NET.Request.Type;
using Alexa.NET.Response;
using Amazon.DynamoDBv2;
using Amazon.DynamoDBv2.Model;
using Amazon.Lambda.Core;
using Amazon.Runtime.Internal;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using AlexaPersistentAttributesManager;
using Amazon;
using Amazon.Runtime;
// Assembly attribute to enable the Lambda function's JSON input to be converted into a .NET class.
[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.Json.JsonSerializer))]
//公式チュートリアル第4回(https://developer.amazon.com/ja/blogs/alexa/post/4144a8ea-7549-4c44-a4bd-e94cb93807ea/chapter4-jp)の内容です。
namespace HoroscopeSkill_CSharp
{
public class Function
{
private class FortuneScore
{
public string Score { get; }
public string Description { get; }
public FortuneScore(string score, string description)
{
this.Score = score;
this.Description = description;
}
}
private readonly List<FortuneScore> _fortunes = new List<FortuneScore>
{
new FortuneScore("good","星みっつで良いでしょう"),
new FortuneScore("normal","星ふたつで普通でしょう"),
new FortuneScore("bad","星ひとつでイマイチでしょう"),
};
private readonly string[] _luckyColors =
{
"赤",
"ピンク",
"オレンジ",
"ブルー",
"水色",
"紺色",
"紫",
"黒",
"グリーン",
"レモンイエロー",
"ホワイト",
"チャコールグレー"
};
private readonly string _tableName = "HoroscopeSkillTableCSharp";
/// <summary>
/// A simple function that takes a string and does a ToUpper
/// </summary>
/// <param name="skillRequest"></param>
/// <param name="context"></param>
/// <returns></returns>
public SkillResponse FunctionHandler(SkillRequest skillRequest, ILambdaContext context)
{
SkillResponse skillResponse = null;
try
{
//型スイッチの利用
switch (skillRequest.Request)
{
case LaunchRequest launchRequest:
skillResponse = HelpIntentHandler(skillRequest);
break;
case IntentRequest intentRequest:
switch (intentRequest.Intent.Name)
{
case "HoroscopeIntent":
skillResponse = HoroscopeIntentHandler(skillRequest);
break;
case "LuckyColorIntent":
skillResponse = LuckyColorIntentHandler(skillRequest);
break;
case "AMAZON.HelpIntent":
skillResponse = HelpIntentHandler(skillRequest);
break;
case "AMAZON.CancelIntent":
skillResponse = CancelAndStopIntentHandler(skillRequest);
break;
case "AMAZON.StopIntent":
skillResponse = CancelAndStopIntentHandler(skillRequest);
break;
default:
//skillResponse = ErrorHandler(skillRequest);
break;
}
break;
case SessionEndedRequest sessionEndedRequest:
skillResponse = SessionEndedRequestHandler(skillRequest);
break;
default:
//skillResponse = ErrorHandler(skillRequest);
break;
}
}
catch
{
skillResponse = ErrorHandler(skillRequest);
}
return skillResponse;
}
#region 各インテント、リクエストに対応する処理を担当するメソッドたち
private SkillResponse HoroscopeIntentHandler(SkillRequest skillRequest)
{
var intentRequest = skillRequest.Request as IntentRequest;
var speechText = "";
var skillResponse = new SkillResponse
{
Version = "1.0",
Response = new ResponseBody()
};
//StarSignスロットから値を取り出します。
var sign = intentRequest.Intent.Slots["StarSign"].Value;
//占い結果をランダムに取り出す
var random = new Random();
int fortuneIdx = random.Next(3);
var fortune = _fortunes[fortuneIdx];
speechText = $"今日の{sign}の運勢は{fortune.Description}。";
var repromptText = "他にラッキーカラーが占えます。ラッキーカラーを聞きますか?";
skillResponse.Response.OutputSpeech = new PlainTextOutputSpeech
{
Text = speechText + repromptText
};
skillResponse.Response.Card = new SimpleCard
{
Title = "サンプル星占い",
Content = speechText
};
skillResponse.Response.Reprompt = new Reprompt
{
OutputSpeech = new PlainTextOutputSpeech
{
Text = repromptText
}
};
#region セッションオブジェクトを利用
//セッションオブジェクトを取得
var attributes = skillResponse.SessionAttributes;
//nullだったらインスタンスを生成
if (attributes == null)
{
attributes = new Dictionary<string, object>();
}
//「sign」をキーにしてユーザーの星座を格納
attributes["sign"] = sign;
//レスポンスに格納
skillResponse.SessionAttributes = attributes;
#endregion
#region DynamoDBを利用した永続アトリビュート
//レコードのプライマリキーにuserIdを使用
var userId = skillRequest.Session.User.UserId;
var attrManager=new AttributesManager(userId,_tableName);
//ユーザーの星座情報をsignをキーにしてセット
attrManager.SetPersistentAttributes("sign", sign);
//セットした情報をDynamoDBに保存
attrManager.SavePersistentAttributes();
#endregion
return skillResponse;
}
private SkillResponse LuckyColorIntentHandler(SkillRequest skillRequest)
{
var intentRequest = skillRequest.Request as IntentRequest;
var speechText = "";
var skillResponse = new SkillResponse
{
Version = "1.0",
Response = new ResponseBody()
};
//ローカル関数だった。忘れてた。
//
SkillResponse ComposeReturnToAskFortuneResponse()
{
speechText = "そういえばまだ運勢を占っていませんでしたね。";
speechText += "今日の運勢を占います。" +
"たとえば、ふたご座の運勢を教えてと聞いてください";
skillResponse.Response.OutputSpeech = new PlainTextOutputSpeech
{
Text = speechText
};
skillResponse.Response.Reprompt = new Reprompt
{
OutputSpeech = new PlainTextOutputSpeech
{
Text = speechText
}
};
return skillResponse;
}
#region セッションアトリビュートから値を取得
var sign = skillRequest.Session.Attributes?["sign"].ToString() ?? "";
#endregion
#region DynamoDBから値を取得
if (string.IsNullOrEmpty(sign))
{
var userId = skillRequest.Session.User.UserId;
var attrManager = new AttributesManager(userId, _tableName);
var attr = attrManager.GetPersistentAttributes();
sign = attr?["sign"] ?? "";
}
#endregion
//セッションアトリビュートと永続アトリビュートのどちらにも値が入っていなければ
//ここでリターン
if (string.IsNullOrEmpty(sign))
{
return ComposeReturnToAskFortuneResponse();
}
//ラッキーカラーをランダムで。
var random = new Random();
var luckyColorIdx = random.Next(3);
var luckyColor = _luckyColors[luckyColorIdx];
speechText = $"今日の{sign}のラッキーカラーは" +
$"{luckyColor}です。素敵ないちにちを。";
skillResponse.Response.OutputSpeech = new PlainTextOutputSpeech
{
Text = speechText
};
skillResponse.Response.Card = new SimpleCard
{
Title = "サンプル星占い",
Content = speechText
};
skillResponse.Response.ShouldEndSession = true;//セッション終了を指定
return skillResponse;
}
private SkillResponse HelpIntentHandler(SkillRequest skillRequest)
{
var intentRequest = skillRequest.Request as IntentRequest;
var speechText = "今日の運勢を占います。" +
"例えば、ふたご座の運勢を教えてと聞いてください。";
var skillResponse = new SkillResponse
{
Version = "1.0",
Response = new ResponseBody()
};
skillResponse.Response.OutputSpeech = new PlainTextOutputSpeech
{
Text = speechText
};
skillResponse.Response.Reprompt = new Reprompt
{
OutputSpeech = new PlainTextOutputSpeech
{
Text = speechText
}
};
return skillResponse;
}
private SkillResponse CancelAndStopIntentHandler(SkillRequest skillRequest)
{
var intentRequest = skillRequest.Request as IntentRequest;
var speechText = "";
var skillResponse = new SkillResponse
{
Version = "1.0",
Response = new ResponseBody()
};
skillResponse.Response.OutputSpeech = new PlainTextOutputSpeech
{
Text = speechText
};
skillResponse.Response.Card = new SimpleCard
{
Title = "サンプル星占い",
Content = speechText
};
skillResponse.Response.ShouldEndSession = true;
return skillResponse;
}
private SkillResponse SessionEndedRequestHandler(SkillRequest skillRequest)
{
var sessionEndedRequest = skillRequest.Request as SessionEndedRequest;
return new SkillResponse
{
Version = "1.0",
Response = new ResponseBody()
};
}
private SkillResponse ErrorHandler(SkillRequest skillRequest)
{
var speechText = "すみません。聞き取れませんでした。";
var skillResponse = new SkillResponse
{
Version = "1.0",
Response = new ResponseBody()
};
skillResponse.Response.OutputSpeech = new PlainTextOutputSpeech
{
Text = speechText
};
skillResponse.Response.Reprompt = new Reprompt
{
OutputSpeech = new PlainTextOutputSpeech
{
Text = speechText
}
};
return skillResponse;
}
#endregion
}
}
試してみます
同じ結果が返ってきています。
セッションが一度終了した後、直接ラッキーカラーを訊くと、前回DynamoDBに格納した星座情報を取得して使ってくれています。
ちゃんと動いているようですね。