Visual StudioとAzure Functionsで作るLINE BOTアプリケーション入門
以前の記事で紹介した LINE BOT開発用のAzure Functionsプロジェクトテンプレートですが、
サンプルコードを大幅に追加しました。
(詳しくはMarketplaceのRelease Notesを確認してみてください。)
この機会に是非、使ってほしい!ということで、今回はLine.Messagingライブラリを使ったAzure FunctionsのLINE BOTアプリケーション開発について少し解説してみたいと思います。
目次
Visual Studio とAzuzre Functionsで作るLINE BOTアプリケーション入門
はじめに
ここで紹介するサンプルコードは、拙作Line.Messaging
クラスライブラリを使用することを前提としています。
この記事の内容を実際に試したい方は、MarketPlaceに記載のクイックスタートガイド参考に開発環境を作っておきましょう。
(導入時の問題、質問等ありましたら、このブログのコメント欄でも、MarketplaceのQ&AでもTwitterでも何でも良いのでご連絡ください)
Webhook イベントを処理する
友だち追加やメッセージ送信などのイベントは、指定したAzure FunctionsのURLにHTTP POSTリクエストとして送信されます。 そのリクエストによって、HttpTriggerFunctionクラスのRunメソッドが実行されます。
以下は、Line.Messagingを使用したHttpTriggerFunctionの実装例です。
public static class HttpTriggerFunction { //LINE Messaging API クライアントの初期化 static LineMessagingClient lineMessagingClient; static HttpTriggerFunction() { lineMessagingClient = new LineMessagingClient(System.Configuration.ConfigurationManager.AppSettings["ChannelAccessToken"]); var sp = ServicePointManager.FindServicePoint(new Uri("https://api.line.me")); sp.ConnectionLeaseTimeout = 60 * 1000; } [FunctionName("LineMessagingApiSample")] public static async Task<HttpResponseMessage> Run([HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)]HttpRequestMessage req, TraceWriter log) { //WebhookのリクエストBodyからイベントオブジェクトを取得 IEnumerable<WebhookEvent> events; try { var channelSecret = System.Configuration.ConfigurationManager.AppSettings["ChannelSecret"]; events = await req.GetWebhookEventsAsync(channelSecret); } catch (InvalidSignatureException e) { return req.CreateResponse(HttpStatusCode.Forbidden, new { Message = e.Message }); } //イベントを処理する try { var app = new EchoBotApp(lineMessagingClient,log); await app.RunAsync(events); } catch (Exception e) { log.Error(e.ToString()); } return req.CreateResponse(HttpStatusCode.OK); } }
LINE Messaging APIクライアントを初期化する
メッセージのリプライ、プッシュ通知などのLINEサーバーへのリクエストにはLine.Messaging.LineMessagingClient
クラスを使用します。
このクラスは内部でSystem.Net.Http.HttpClient
クラスを使用しています。
これをHttpTriggerFunctionクラスのStaticフィールドに1インスタンスだけ作成し、使いまわすようにします。
※インスタンスを使いまわす理由は、以下を参照ください。
リクエストBodyからWebhook Event Objectを取得する
Webhookイベントの内容は、リクエストBodyにJSONで格納されています。
Line.Messagingライブラリでは、Run関数の引数HttpRequestMessage req
からEvent Objectを取得する拡張メソッドを用意しています。
static Task<IEnumerable<WebhookEvent>> GetWebhookEventsAsync(this HttpRequestMessage req,strin channelSecret);
このメソッドは以下の処理を行います。
- リクエストの構文検証(Signature Validation)を行い、NGの場合は例外を投げる
- リクエストBodyのJSONをパースしてWebhook Event Object(のコレクション)を返す
[FunctionName("LineMessagingApiSample")] public static async Task<HttpResponseMessage> Run([HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)]HttpRequestMessage req, TraceWriter log) { IEnumerable<WebhookEvent> events; try { var channelSecret = System.Configuration.ConfigurationManager.AppSettings["ChannelSecret"]; //Webhook Event Objectの取得 events = await req.GetWebhookEventsAsync(channelSecret); } catch (InvalidSignatureException e) { //構文検証エラー return req.CreateResponse(HttpStatusCode.Forbidden, new { Message = e.Message }); } //... }
Webhook Event Object
Webhook Event Object はイベントのタイプ別に7種類用意されています。
Line.Messagingライブラリでは、WebhookEvent
抽象クラスのサブクラスとして実装されています。
public abstract class WebhookEvent { public WebhookEventType Type { get; } public WebhookEventSource Source { get; } public long Timestamp { get; } }
MessageEvent
ユーザーからメッセージが送られた際に発行されるイベント。
メッセージには、送られてくるデータの種類によって異なる以下のタイプがあります。- text (文字列メッセージ)
- image(画像データ)
- audio(オーディオデータ)
- video(ビデオデータ)
- file(ファイル)
- location(位置情報)
- sticker(スタンプ)
FollowEvent
ユーザーがBOTアカウントをフォローした際に発行されるイベント- UnfollowEvent
ユーザーにBTOアカウントをブロックされた際に発行されるイベント - JoinEvent
BOTアカウントがグループやトークルームに参加した際に発行されるイベント - LeaveEvent
BOTアカウントがグループやトークルームから退室させられた際に発行されるイベント - PostbackEvent
テンプレートメッセージに設定したPostbackアクションが実行された際に発行されるイベント - BeaconEvent
LINE Beaconデバイスの受信圏内に入った(出た)際に発行されるイベント
※各イベントの詳細はLINE公式のAPIリファレンスを参照ください。
WebhookEventSource
WebhookEventクラスは、イベントの送信元を表すSourceプロパティを持ちます。
Line.MessagingライブラリではWebhookEventSource
クラスとして実装しています。
public class WebhookEventSource { public EventSourceType Type { get; } public string Id { get; } public string UserId { get; } }
送信元のタイプは以下の3種類です。
- User (ユーザーと1対1のトークで発生するイベントに付与されます)
- Group (トークグループで発生するイベントに付与されます)
- Room (トークルーム(複数ユーザー間でのトーク)で発生するイベントに付与されます)
また、Idプロパティには、各送信元を示すIDが格納されます(User ID、Group ID、Room ID)。
UserIdには、各送信元から発信したユーザーのIDが入ります。
イベントを処理する
あとは、リクエストBodyから取得したWebhookEventオブジェクトの内容を見てそれに応じた処理を記述すればOKです。
これをHttpTriggerFunctionクラスに直接記述しても良いのですが、Line.Messagingライブラリではイベントの種類に応じて処理を振り分けて実行するためのクラスが用意されているので、これを使います。
WebhookApplicationクラスを使用する
WebhookApplicationクラスは、RunAsync
というメソッドでWebhookEventのコレクションを受け取り、イベントの種類に応じて各イベントの処理(On~ 仮想メソッド)を呼ぶだけのクラスです。
public abstract class WebhookApplication { protected virtual Task OnMessageAsync(MessageEvent ev) => Task.CompletedTask; protected virtual Task OnJoinAsync(JoinEvent ev) => Task.CompletedTask; protected virtual Task OnLeaveAsync(LeaveEvent ev) => Task.CompletedTask; protected virtual Task OnFollowAsync(FollowEvent ev) => Task.CompletedTask; protected virtual Task OnUnfollowAsync(UnfollowEvent ev) => Task.CompletedTask; protected virtual Task OnBeaconAsync(BeaconEvent ev) => Task.CompletedTask; protected virtual Task OnPostbackAsync(PostbackEvent ev) => Task.CompletedTask; public async Task RunAsync(IEnumerable<WebhookEvent> events) { foreach (var ev in events) { switch (ev.Type) { case WebhookEventType.Message: await OnMessageAsync((MessageEvent)ev).ConfigureAwait(false); break; case WebhookEventType.Join: await OnJoinAsync((JoinEvent)ev).ConfigureAwait(false); break; case WebhookEventType.Leave: await OnLeaveAsync((LeaveEvent)ev).ConfigureAwait(false); break; case WebhookEventType.Follow: await OnFollowAsync((FollowEvent)ev).ConfigureAwait(false); break; case WebhookEventType.Unfollow: await OnUnfollowAsync((UnfollowEvent)ev).ConfigureAwait(false); break; case WebhookEventType.Postback: await OnPostbackAsync((PostbackEvent)ev).ConfigureAwait(false); break; case WebhookEventType.Beacon: await OnBeaconAsync((BeaconEvent)ev).ConfigureAwait(false); break; } } } }
このクラスを継承したクラスを作成し、必要な仮想メソッドをオーバーライドして処理を記述します。
以下に、ユーザーのメッセージをオウム返しするだけの処理の実装例を示します。
//オウム返しBOTアプリケーション public class EchoBotApp:WebhookApplication { private LineMessagingClient MessagingClient {get;} private TraceWriter Log{get;} //コンストラクタでLineMessagingClientのインスタンスを渡す public EchoBotApp(LineMessagingClient messagingClient, TraceWriter log) { MessagingClient = messagingClient; Log = log; } //メッセージEventを処理する protected override async Task OnMessageAsync(MessageEvent ev) { //Textメッセージにだけ返信する if(ev.Type != EventMessageType.Text){return;} return MessagingClient.ReplyMessageAsync(ev.ReplyToken, ((TextEventMessage)ev.Message).Text); } }
あとは、HttpTriggerFunctionのRunメソッドで以下の様に記述するだけです。
//WebhookApplicationを継承したクラスでイベントを処理する var app = new EchoBotApp(lineMessagingClient,log); await app.RunAsync(events);
まとめ
基本的な使い方は以上です。
次回からは、もう少し具体的な実装例を解説してみたいと思います。
LINE BOT 開発に使えるLINE Messaging API の.Net Standard Libraryと、Visual Studio 2017用プロジェクトテンプレートを作りました
LINEでビンゴゲームができるBOTを作ったのですが、 このBOTで実装したMessaging API のコードをクラスライブラリに切り出してみました。
Line.Messaging V0.20-alpha
NuGet Galleryで公開しています。
- ターゲットバージョンは.Net Standard 1.3 としました。
- APIは一通り実装されていますが、テストが不十分のためAlphaリリース版としています。
LINE BOT Function プロジェクトテンプレート
Azure Functionsのプロジェクトに、「Line.MessagingのNuGet参照」と「ユーザーのメッセージを受け取り、オウム返しをするコード」を追加したテンプレートです。
Visual Studio Marketplaceからダウンロードできます。
テンプレートの使い方は、以下のリンク先に記載されています。
これからLINE BOT を作ってみたいという方は、ぜひこのテンプレートをダウンロートして試してみてください。
GitHubリポジトリ
ソースコード等はこちらから github.com
LINE でビンゴゲームができるBOTを改良しました
BINGO BOT
以前に紹介した、LINE BOTを改良しました。
改良したところ
- リッチメニューに対応しました
- ビンゴカードを画像で取得できるようになりました
- ゲーム参加者同士の簡易チャットが可能になりました
リッチメニューに対応しました。
画面下部のメニューバーに追加した「BINGO Menu」をタップすると、上図の様にメニューが表示されます。 メニューの各項目をタップすると、その項目に割り当てられた定型文が送信されます。
メニュー | 定型文(コマンド) | 説明 |
---|---|---|
ゲームを開始する | 開始 | 新しいゲームを開始します。 このコマンドでゲームを開始したユーザーがゲームの進行役になります。 |
ゲームに参加する | 参加 | ゲームの進行役が作成したゲームに参加します。 |
番号を引く | ドロー | 番号を引いてゲームを進めます。 ゲームの進行役のユーザーのみ使用可能です。 |
カードを更新 | カード | ゲームの進行状況を反映した、最新のカード画像を取得します。ゲームの参加者のみ使用可能です。 |
ビンゴカードを画像で取得できるようになりました
これだけで、だいぶビンゴゲームらしくなりましたね。(デザインはいまいちですが)
ゲーム参加者同士の簡易チャットが可能になりました
これまで、ゲームの進行に必要なキーワード(開始、参加、ドロー、カード、終了)のみを受け付けて、その他のメッセージは無視していました。
今回、上記キーワード以外のメッセージは、同じゲームに参加している全ユーザーに送信するようにしました。
上図のように他のユーザーからのメッセージには、先頭に@ユーザー名を付けて送信します。
お試しユーザー募集中!
ちょっと試してみたいと思った方、こちらから友達登録してみてください!
LINE BOT Webhooks の署名検証をC#で実装する
LINE BOT 開発では、ユーザーが送信したメッセージなどのイベントは、指定したURLにHTTPSのPOSTリクエストとして送信されますが、リクエストの送信元が間違いなくLINEからのものであることを確認する必要があります。
LINE API Referenceには以下の様に書かれています。
Signature validation
リクエストの送信元がLINEであることを確認するために署名検証を行わなくてはなりません。 各リクエストには X-Line-Signature ヘッダが付与されています。 X-Line-Signature ヘッダの値と、request body と Channel secret から計算した signature が同じものであることをリクエストごとに 必ず検証してください。
検証は以下の手順で行います。
今回は、これをC#で実装してみましょう。
必要なデータは以下の3つです。
- リクエストのX-Line-Signature ヘッダに格納されている値
- リクエストBODYの値
- Bot アカウントに付与されるChannel Secret(Line DeveloppersのChannels>>Basic Informationで確認できます)
これらを引数にして検証を行う関数を作ります。
using System.Security.Cryptography; using System.Text; public bool VerifySignature(string xLineSignature, string requestBody, string channelSecret) { try { //channnel secret と request body をByte配列に変換する var key = Encoding.UTF8.GetBytes(channelSecret); var body = Encoding.UTF8.GetBytes(requestBody); //channel secretをキーにしてHMAC-SHA256アルゴリズムでrequest bodyのダイジェスト値を得る using (HMACSHA256 hmac = new HMACSHA256(key)) { var hash = hmac.ComputeHash(body, 0, body.Length); //ダイジェスト値をBASE64に変換 var hash64 = Convert.ToBase64String(hash); //X-LINE-Signatureヘッダの値と一致すればOK!! return xLineSignature == hash64; } } catch { return false; } }
これを、Azure Functions(HttpTrigger)で使用する例を以下に示します。
using System.Configuration; using System.Linq; using System.Net; using System.Net.Http; using System.Threading.Tasks; public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log) { //Channel Secretの取得。Channel SecretはAzure ポータルでアプリケーション設定に登録しておきます。 var channelSecret = ConfigurationManager.AppSettings["ChannelSecret"]); //Request Body の取得 var contentJson = await req.Content.ReadAsStringAsync(); //X-Line-Signatureヘッダ値の取得 var xLineSignature = req.Headers.GetValues("X-Line-Signature").FirstOrDefault() if (string.IsNullOrEmpty(xLineSignature) || !VerifySignature(xLineSignature, contentJson, channelSecret)) { //検証に失敗 return req.CreateResponse(HttpStatusCode.Forbidden, new { Message = "Signature validation faild." }); } //BOTでやりたい処理... }
LINEでビンゴゲームができるBOTを作りました
ビンゴゲームの基本機能をASP.NETのWeb APIで、LINEとの連携部分(BOT)をAzure Functionsで作りました。
例によってソースコードをGitHubにて公開していますので、覗いてみてください。
BINGO Web API
ビンゴゲームの基本的な機能は、Azure App ServiceでホストしたASP.NET CoreのWeb APIで提供します。
次のような、ごく基本的な機能のみをREST APIで提供しています。
- ゲームを作成する
- ゲームにカードを追加する
- 番号を引いてゲームを進める
- 現在のゲームの状態を取得する
- 現在のカードの状態を取得する
詳しくは、Swaggerさんが生成してくれたAPIリファレンスをご確認ください。 http://bingowebapi.azurewebsites.net/swagger/
LINE BOT Function
LINE BOT 関連の機能は、Azure Functionsで処理しています。
主に次のような処理を行っています
- LINEユーザーからのメッセージを受け取る
- メッセージの内容に応じてゲームの開始、参加、進行などを行う。
- 状況に応じたBINGO Web APIを呼び出す。
- ゲームの進行状況、その他メッセージを LINEユーザーに送信する
開発環境、Frameworkとか
BINGO WebAPI
Web APIの開発にはVisual Studio 2017を使用しています。
「ASP.NET Core Webアプリケーション」のプロジェクトテンプレートを使用して作成しました。
DB関連はAzure SQL Server + EntityFramework Coreを使用しています。
LINE BOT Function
Azure Function Tools for Visual Studio 2017
LINE BOTの開発には,「Azure Function Tools for Visual Studio 2017」というツールを使用しています。
このツールを使用するとAzure Functions の開発がとてもに楽になります。
- Azure Functions のプロジェクトテンプレートが使用できる
- 開発~(ローカル)デバッグ~デプロイをVisual Studioだけで簡単に行うことができる
- C#スクリプト(.csx)ではなくビルドしたアセンブリ(.dll)を配置する
※ このツールは(2017年7月現在では) プレビュー版のVisual Studio (Visual Studio 2017 Preview(2))でないとインストールできないようです。 お試しする場合は、ご注意を。
- Visual Studio 2017 Tools for Azure Functions - Visual Studio Marketplace
- プレリリース版 Visual Studio 2017 | Visual Studio プレビュー
Azure Table Strage
ゲームに参加しているLINEのユーザー情報と、BINGO APIの発行するゲームID、カードIDとの紐づけなどの情報はAzure Table Strageに保存しています。
遊び方
LINE BOTを友達登録します
BINGO BOTというアカウントを友達登録するとお試しで遊ぶことができます。
BINGO BOT
※ ただし、開発中のお試し版であることをご承知の上でご利用ください。 また、予告なくサービスが停止したり、仕様が変更されたりする場合がありますので予めご了承ください。
ゲームオーナーとプレーヤー
BINGOゲームを始めるには、ゲームを作成・進行するゲームオーナー1人と、ゲームに参加するプレーヤー(複数人)が必要です。
ゲームオーナーの操作
ゲームを作成する
まずは、ゲームオーナーがゲームを作成します。
やり方は簡単で、BINGO BOT に「0」または「開始」と送るだけです。
合言葉を決める
するとLINE BOTから、合言葉の設定を促すメッセージが帰ってきます。
合言葉は、プレーヤーがゲームに参加するために必要なキーワードです。
20文字まで好きな言葉を返信してください。 合言葉を設定しないこともできます。(その場合は「なし」と返します)
ゲームIDと合言葉をプレーヤーに伝える
合言葉を送信すると、ゲームが開始されLINE BOT からゲームIDが返信されます。
このゲームIDと、指定した合言葉を参加するプレーヤーに伝えて、入力してもらいます。
番号を引く
あとは、適当な文字をBINGO BOT に送信するだけです。 1回送信するたびに1つ番号を引きます。
ゲームを終了する
ゲーム進行中に「終了」と入力するとゲームを終了できます。(終了するまで次のゲームを遊ぶことができませんので注意)
プレーヤーの操作
ゲームに参加する
ゲームに参加するには、BINGO BOTに「1」または「参加」と送ります。
ゲームIDと合言葉を入力する
するとLINE BOTからゲームIDと合言葉を入力するよう促すメッセージが返ってくるので、ゲームオーナーから伝えられたそれらを入力します。
カードを取得する
ゲームへの参加に成功すると、BINGOカードが返信されます。
あとは、ゲームオーナーが番号を引くたびに、BINGO BOTから引いた番号が通知されます。
ゲーム進行中、何かメッセージを送ると、ヒットした番号を反映した現在のカードが取得できます。
まとめ
まだまだ作り込みが足りていないので、もうしばらくはこいつの開発で楽しめそうです。
とりあえず、BINGOカードは画像にしたいですね。
あと、今後は開発中にハマったこととか、調べたことなどTIPS的な小ネタを記事にできればいいな。
Azure App Service の継続的配信(プレビュー)を試してみました
Azure App Service の継続的配信(プレビュー)
最近あまり触れていなかったASP.NET系の技術にそろそろじっくり取り組んでみようかな、と思う今日この頃。
ASP.NET Core & Azure App Service で何か作ろうとAzureポータルを触っていると、App Serviceのメニューに「継続的配信(プレビュー)」なる項目を見つけました。
今回はこれを試してみたいと思います。
どんな機能か
Visual Studio Team Service (VSTS)を利用して、Azure App Serviceへの継続的配信(Continuous Delivery)を行うためのワークフローを構成してくれる機能です。
これを使用することで、GitのリポジトリにコードをPushするだけでVSTSのビルドタスクが走り、コードの取得→ビルド→Unitテスト→デプロイ といった一連の流れを自動化できるようになります。
配信元のソースはVSTSのGit リポジトリ以外にもGitHubのリポジトリが指定できます。(その他、GitやSVNのプライベートなリポジトリを指定することもできるようです。)
事前準備
では、早速試してみましょう。今回はGitHubのリポジトリと連携させてみます。
サンプルアプリケーション
今回サンプルとして使用するアプリケーションは、Visual Studio 2017のASP.NET Coreのプロジェクトテンプレートを使用して作成したものを使用します。
- [ファイル]-[新規作成]-[プロジェクト]で「ASP.NET Core Webアプリケーション(.NET Core)」を選択します。
- ターゲットフレームワークに「ASP.NET Core 1.1」、テンプレートは「Web API」としました。
- 「Dockerサポートを有効にする」なるチェックボックスがありますが、今回はOFFで作成します。
こうして作成したWebAPIのアプリケーションを、そのままGitHubに上げておきます。
VSTSでプロジェクトを作成
Visual Studio Team Service(VSTS)で、このアプリケーション用のプロジェクトを予め作成しておく必要があります。
VSTSのアカウントがない場合は、以下で作っておきましょう。
アカウントの準備ができたら、プロジェクトを作成しておきます。ここではとりあえずプロジェクト名を決めて作成するだけでOKです。
Azure App Serviceを作成
Azureポータルで、アプリケーションを配置するApp Serviceを作成しておきます。
ここで注意が必要なのは、今回の継続的配信機能を使用するためには、Standerd(S1)以上のサービスプランを選択する必要があるという点です。
Freeプラン(F1)では使用できないので気を付けてください。
継続的配信(プレビュー)の設定
これで下準備は整いました。早速Azureポータルで「継続的配信(プレビュー)」を使ってみましょう。
詳しい手順は、以下のページ(英語)に掲載されていますので、こちらを参考に進めて頂ければ問題ないと思います。
動かしてみよう
これで必要な設定はすべて完了し、GitHubにPushするだけで最新のアプリケーションがApp Serviceに配置されるはずです。
はずなのですが・・・
ビルドに失敗します
GitHubへのPushをトリガーにVSTSのビルドタスクが動くところまでは問題なかったのですが、そのままではビルド自体が失敗してしまっていました。
VSTSのサイトで、今回のプロジェクトを開いてビルドタスクの設定を確認してみます。
ビルドタスクの設定は「Build&Release」タブで確認できます。
- ビルド設定名部分のリンクをクリックして
- Editをクリックすると、以下のようなビルドタスクの設定画面が表示されます。
ビルドタスクの設定を見直す
既定では、ASP.NET Core(PREVIEW)というテンプレートが使用されるようなのですが、そのままではうまくいきませんでした。
更に、各タスクの設定等を変えていろいろ試してもみたのですが、解決しませんでした。
そこで、ASP.NET Core(PREVIEW)のテンプレートを使うのは止めて、以下のページに掲載されている内容で試してみたところビルドが通るようになりました。
「Get Sources」を除く既定のタスクを全て削除し、「+Add Task」で新しいタスクに置き換えていきます。
以下に設定内容のスクリーンキャプチャを載せておきます。
- Build Solution
Visual Studio Buildタスクを実行します。
- Test Assemblies
対象のリポジトリにテストプロジェクトが含まれる場合にテストを実行します。
- Archive Files
ビルド成果物をZIPに圧縮します。
- Publish Build Artifacts
ビルド成果物をサーバーに公開します。
Build Agentを変更する
もう1つ。ビルド設定画面で「Options」のタブを選択して、右側の「Default agent queue」を”Hosted”から”Hosted VS2017”に変更しておかないとうまくいきませんでした。
ここまで終えたら、ビルド設定画面の右上にある「Save & Queue」をクリックして、設定したタスクを実行してみます。
これで、ビルド成果物を作成するところまでは何とか動かすことが確認できたのですが、今度は次のフェーズで失敗してしまいます。
今度はデプロイに失敗する
ビルド成果物の作成が完了したら、Azure App Serviceへ実際に配置を行うReleaseのフェーズに移行するのですが、ここで次のようなエラーが発生して失敗してしまいます。
error: Web Deploy cannot modify the file - ERROR_FILE_IN_USE
どうやら、配置先のDLLがロックされていて上書きできないことが原因のエラーのようです。
※この問題は、GitHubのIssuesでも議論されていました。
VSTSで「Release」の設定を見てみると、以下の様になっていました。
- 「Deploy Azure App Service to Slot」でStaging用スロットにデプロイ
- それが成功したら「Manage Azure App Service - Slot Swap」で本番用のスロットにスワップ
という構成になっています。
今回は「Deploy Azure App Service to Slot」でファイルが上書きできずにエラーとなっていました。
これを解決するには、配置先のStagingスロットのサービスを一旦停止してからデプロイを実行する必要がありそうです。
Releaseタスクの設定を変更する
そこで、以下の様に「Deploy Azure App Service to Slot」の前後にサービスの停止、再開を挟むようにしてみたところ無事デプロイされるようになりました。
以下に設定内容を貼っておきます。
- Manage Azure App Service - Stop App Service
- Deploy Azure App Service to Slot
- Manage Azure App Service - Start App Service
Manage Azure App Service - Slot Swap
まとめ
非常に長くなってしまいましたが、ようやくこれでGitHubリポジトリからAzure App Serviceへの継続的配信の設定はひとまず完了です。
今回試したのはあくまでも(プレビュー)なので、(プレビュー)が取れた暁にはAzureポータルからポチポチと数クリックするだけで済むようになっているかもしれません。(涙目)
CSVファイルの読み書き設定をC#スクリプトで記述するWPFアプリをDesktop App Converterで変換してストアに公開しました
デスクトップアプリをUWPに変換してWindowsストアに公開可能な状態にするDesktop App Converterを試してみたい!
ということで、ブログのネタで作成していたWPFアプリ(CsvEditSharp)をDesktop App Converterに掛けてストアに公開するまでをチャレンジしてみました。
ひとまず、公開まで漕ぎつけることができたので、アプリの宣伝をしておきます。
CsvEditSharp
CsvEditSharpは、CSVファイルの読み書き設定をC#スクリプトで記述するCSVエディタです。
スクリプトでは、C#でCSVファイルを扱うためのクラスライブラリCsvHelperのAPIを利用して各種設定を記述します。
Windows Storeから無料でダウンロードできます。(Windows10 Anniversary Update 以降のデスクトップPCのみで利用可能)
基本操作
設定スクリプトのひな型を生成してCSVファイルを読み込む
初めて扱うCSVファイル等、設定スクリプトが存在していない場合に、CSVファイルの読み込みと同時に設定スクリプトのひな型を生成することができます。
- ツールバーの「Configuration Script」コンボボックスで "(Auto Genarate)" を選択します
「Open」ボタンをクリックして読み込むCSVファイルを選択します
以下のダイアログで、自動生成される設定スクリプトの名前、CSVファイルのエンコーディングおよびヘッダレコードの有無を入力して[OK]をクリックします
次のように、選択したCSVファイルのヘッダ情報を基に設定スクリプトが自動生成されます。
レコード格納クラスがFieldData
という名前で生成されます。
- クラス内の各プロパティが1つのカラムを表します
- ヘッダレコードに定義されているカラム名が、そのままプロパティ名となります
- プロパティのデータ型は全て文字列(string)型となります
- カラム名にプロパティ名として使用できない文字が含まれる場合、またはヘッダレコードが存在しない場合のプロパティ名には
column_
+カラム番号
が割り当てられます
設定スクリプトのひな型をカスタマイズする
設定スクリプトの編集
設定スクリプトを、読み込んだCSVファイルの内容に応じて書き換えます。
Encoding = Encoding.GetEncoding("utf-8"); //Genderの選択肢を定義するenum enum Gender { Male, Female } //レコード格納クラスの名前を変更 class Person { //プロパティの型をデータの種類に応じて変更 public string Name { get; set; } public DateTime Birthday { get; set; } public Gender Gender { get; set; }public bool Married { get; set; } public double PocketMoney { get; set; } } //クラスマッピングの設定も、扱うデータの種類に応じたものに変更 RegisterClassMap<Person>(classMap => { classMap.Map(m => m.Name).Name("Name"); classMap.Map(m => m.Birthday).Name("Birthday") .TypeConverterOption("M/d/yyyy"); classMap.Map(m => m.Gender).Name("Gender"); classMap.Map(m => m.Married).Name("Married") .TypeConverterOption(true,"Y") .TypeConverterOption(false,"N"); var culture = System.Globalization.CultureInfo.GetCultureInfo("en-us"); classMap.Map(m => m.PocketMoney).Name("PocketMoney") .TypeConverterOption("C") .TypeConverterOption(NumberStyles.Currency) .TypeConverterOption(culture); });
編集が完了したら[Run]ボタンをクリックして、結果を確認します。
編集したコードに問題がなければ、編集後の設定でCSVファイルが再読み込みされ、内容がCSVエディタに表示されます
設定スクリプトの保存
編集した設定スクリプトは[Save] (上書き保存)ボタン、[SaveAs...] (名前を付けて保存)ボタンで保存できます。
[SaveAs...]ボタンをクリックすると、以下のダイアログが表示されます。
「Save as a new file」を選択
- 現在の設定スクリプトを入力した名前で保存します
「Save into the current directory as "Default.config.csx"」を選択
設定スクリプトを指定してCSVファイルを読み込む
対応する設定スクリプトが既に存在する場合、「Configuration Script」で対応するスクリプトの名前を選択後、CSVファイルを読み込みます。
設定スクリプトの管理
[Settings...]ボタンで表示される以下のダイアログで、作成済みの設定スクリプトの名前変更や削除が可能です。
値の編集
CSVエディタ(読み込んだCSVファイル名のタブ)内のセルを直接編集することができます。
クラスマッピングでマップしたプロパティのデータ型に応じて入力方法や入力可能な値が変わります。
- データ型がenumの場合、enumのメンバーを選択肢としたコンボボックスで値を選択します
- データ型がboolの場合、チェックボックスのON/OFFでTrue/Falseを切り替えます
- データ型が数値型やDateTime型の場合、セルに入力した文字列が目的の型に変換できない場合にエラーメッセージを表示します。
※ AddValidation()
メソッドを使用してより詳細な入力検証を設定することも可能です。
編集したCSVデータの保存
ツールバーの[SaveAs]ボタンをクリックして、編集後のCSVデータを別のCSVファイルとして保存することができます。 CSVエディタに表示されている状態がそのまま保存されます。(Queryメソッドでフィルタ・ソートを行った場合も、表示されている内容がそのまま保存されます。)
設定スクリプトAPI
Encoding プロパティ
Encoding Encoding { get; set; }
対象CSVファイルのエンコーディングを指定します。
省略した場合は Encoding.Default
が割り当てられます。
Encoding = Encoding.GetEncoding("utf-8");
RegisterClassMap メソッド
void RegisterClassMap<T>(); void RegisterClassMap<T>(Action<CsvClassMap<T>> propertyMapSetter); void RegisterClassMap<T>(Action<CsvClassMap<T>> propertyMapSetter, RegisterClassMapTarget target);
CsvHelperを利用したクラスマッピングの設定を記述します。
- 登録するクラスマッピングオブジェクトを引数に取るデリゲート
propertyMapSetter
内に設定内容を記述します。 - 第2引数で、登録したクラスマッピングを使用するタイミングを指定できます。
- RegisterClassMapTarget.Reader : 読み込み時のみ
- RegisterClassMapTarget.Writer : 書き込み時のみ
- RegisterClassMapTarget.Both : 読み書き両用
クラスマッピング設定の記述方法については、CsvHelper 公式ドキュメントのMappingのセクションを参照ください。
//既定のクラスマッピング設定を使用 RegisterClassMap<Person>(); //読み書き両用 RegisterClassMap<Person>(classMap => { classMap.Map(m => m.Name); classMap.Map(m => m.Birthday); classMap.Map(m => m.Gender); classMap.Map(m => m.Married) .TypeConverterOption(true,"Y") .TypeConverterOption(false,"N"); classMap.Map(m => m.PocketMoney) .TypeConverterOption("C") .TypeConverterOption(NumberStyles.Currency); }); //読み込み時のみ RegisterClassMap<Person>(classMap => { classMap.Map(m => m.Name); classMap.Map(m => m.Birthday); classMap.Map(m => m.Gender); classMap.Map(m => m.Married) .TypeConverterOption(true,"Y") .TypeConverterOption(false,"N"); classMap.Map(m => m.PocketMoney) .TypeConverterOption("C") .TypeConverterOption(NumberStyles.Currency); }, RegisterClassMapTarget.Reader);
SetConfiguration メソッド
void SetConfiguration(Action<CsvConfiguration> configurationSetter);
引数configurationSetter
デリゲートに渡されるCsvConfiguration
オブジェクトの値を書き換えることで、CSVファイル読み書き時の詳細な設定を記述することができます。
ここで設定可能な項目の詳細は CsvHelper 公式ドキュメントのConfigurationのセクションを参照ください。
SetConfiguration(config => { config.HasHeaderRecord = false; config.AllowComments = true; config.Comment = '#'; config.Delimiter = ';'; //etc... });
AddValidation メソッド
void AddValidation<TType, TMember>(Expression<Func<TType, TMember>> memberSelector, Func<TMember, bool> validation, string errorMessage);
値変更時の入力検証機能を追加することができます。
memberSelector
デリゲート(式木)で対象となるカラムのプロパティを指定します。validation
デリゲートで検証を通過する条件を指定します。errorMessage
に検証NG時に表示する文字列を指定します。
AddValidation<Person,DateTime>( m => m.Birthday , dt => dt <= DateTime.Now.Date, "Cannot enter a future date."); AddValidation<Person, double>( m => m.PocketMoney , n => (n > 0) && (n < 10000.0), "PocketMoney must be in the range $0 to $10000.");
Query メソッド
void Query<T>(Func<IEnumerable<T>, IEnumerable<T>> query); void Query<T>(Action<IEnumerable<T>> query);
データのフィルタ、ソート
LINQの拡張メソッドを利用して、表示するデータのフィルタ、ソートを行うことができます。
Query<Person>(source => source .Where(m => m.Gender == Gender.Female ) .Where(m => m.Married ) .OrderBy(m => m.PocketMoney) );
データの一括更新
ForEach()
拡張メソッドを使用して、データを書き換えることも可能です。
Query<Person>( record => record .Where( m => m.Gender == Gender.Male ) .Where( m => m.Married ) .ForEach( m => { m.Name += " *"; m.PocketMoney = 0; }) );
(Queryメソッドは、設定スクリプトのタブではなく、CSVデータ表示タブの下部にあるテキストエディタ内に記述します。下記のようなコードを記述後、[Execute]ボタンをクリックすることで、コードが実行され、表示に反映されます。)