LoginSignup
1
1

More than 5 years have passed since last update.

C#でAlexaスキル公式チュートリアル第4回を実装する。

Last updated at Posted at 2018-11-20

はじめに

同一セッション内でデータを保持する方法を学んだ公式チュートリアル第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ではセッションアトリビュートまたは永続アトリビュートを取得しています。

Function.cs
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
    }
}

試してみます

公式チュートリアル第4回と同じ手順で試してみましょう。
image.png

同じ結果が返ってきています。
セッションが一度終了した後、直接ラッキーカラーを訊くと、前回DynamoDBに格納した星座情報を取得して使ってくれています。
ちゃんと動いているようですね。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1