Quantcast
Channel: かずきのBlog@hatena
Viewing all 1388 articles
Browse latest View live

Bot Framework のサンプルの QnA Maker の Active Learning をデプロイする方法

$
0
0

最近追加された Active Learning ですが簡単に言うと利用状況を見て、この QA にはこんな質問追加したらいいんじゃないの?っていうのをポータルに出してくれるみたいですね。

docs.microsoft.com

暗黙的フィードバックと明示的フィードバックがあるみたいで明示的フィードバックとかを送るには Train API 叩く必要があるのかな。

ここらへんを叩くためのサンプルが以下になります。

github.com

曖昧な回答が複数件帰ってきたらユーザーに選択肢を出してくれて、ユーザーが質問を選択すると、その内容が Train API に送られます。

こいつをクローンしてデプロイするには以下の手順を踏めば動きました。

  • QnA Maker を適当に準備して発行しておく。EndpointKey とかはメモっておく
  • qnamaker-activelearning.bot の qna の定義の各種設定を自分の QnA Maker のものに書き換える
  • Azure にボットチャンネル登録とWeb Appを準備
  • production の endpoint を追加して上記で作成したものを設定する
  • Startup.cs が production のエンドポイントの定義を探すようになってないので以下のように追加する
privatereadonlybool _isProduction;
public Startup(IHostingEnvironment env)
{
    _isProduction = env.IsProduction();
    // 省略
}

publicvoid ConfigureServices(IServiceCollection services)
{
    // 省略

    services.AddBot<QnAMakerActiveLearningBot>(options =>
    {
        // 省略// Retrieve current endpoint. ここに production のときの考慮のコードも追加
       var service = botConfig.Services.Where(s => s.Type == "endpoint"&& s.Name == (_isProduction ? "production" : "development")).FirstOrDefault();
        // 省略
    });
    // 省略
}

あとはデプロイしてお好きなチャンネルから動作確認で OK


Azure Bot Services に LINE のチャンネルが追加されてる?

$
0
0

今見てて気づいたのですが LINE がしれっと増えてますね。

f:id:okazuki:20190221151846j:plain

これで LINE のボット開発も捗るってもんですね!

Azure Bot Services の LINE のチャンネルを試してみたけど成功してない記録

$
0
0

はじめに

注意:現時点で動かせてません。

本文

昨日みつけた LINE のチャンネルを試してみました。ドキュメントもあるのでそっち見てもOK

docs.microsoft.com

LINE の Developers Console で適当にプロバイダーを作ります(すでにあるならそれを使ってもいい) プロバイダーがないとボットとかも作れないので。プロバイダーが同じボットや Clova のスキルはユーザーを識別するための ID が同じになるので、ユーザーデータを共有する仕組みが作りやすい点が強いです。

さて、作ったら Messaging API を作りましょう。必須入力項目は全部なので全部入れましょう。 プランはフリーでも大丈夫です。(Push Messages を使用しないなら)

作成した Messaging API を選択してチャンネル基本情報から以下の値をゲットします。

  • Channel Secret
  • アクセストークン(再発行ボタンを押して作成してください。とりあえず 0 時間で)

Azure ポータルに移動して LINE 対応させたいボットのボット チャンネル登録を選択してチャンネルを開きます。そして LINE のアイコンをクリックします。

先ほど取得した Channel Secret とアクセストークンを入れる場所があるので入れます。

f:id:okazuki:20190222110123p:plain

入力したら、その下にある Webhook URL をコピーしておいて保存しておきます。

LINE の Developers Console に移動します。

そして、先ほど作成した Messaging API のチャンネル基本情報に Webhook URL という項目があるので編集ボタンを選択して Azure ポータルでコピーした URL を貼ります。

f:id:okazuki:20190222110434p:plain

接続確認ボタンを押して成功すれば OK

そして、デフォルトでは Webhook 送信が有効化されてないので有効化します。

f:id:okazuki:20190222110951p:plain

ボットをちゃんと作ってると LINE の自動応答メッセージは邪魔になることもあるので、その場合はLINE@機能の利用から自動応答メッセージをオフにします。

f:id:okazuki:20190222111357p:plain

同じページに LINE アプリへの QR コードという項目があるので LINE アプリから登録してみましょう。

現時点で成功してない

話しかけても応答がない。一応このボットは Microsoft Teams と WebChat で動いてるけど LINE で話しかけたときだけ以下のようなエラーが Application Insights に出てきてる。

Message
POST to ボットの名前 failed: POST to the bot's endpoint failed with HTTP status 500 POST to the bot's endpoint failed with HTTP status 500 

Exception type
System.Exception

Failed method
Microsoft.Bot.ChannelConnector.BotAPI+<PostActivityToBotAsync>d__31.MoveNext

コールスタックはこんな感じ。

Microsoft.Bot.Schema.BotException:
   at Microsoft.Bot.ChannelConnector.BotAPI+<PostActivityToBotAsync>d__31.MoveNext (Microsoft.Bot.ChannelConnector, Version=3.2.2.5, Culture=neutral, PublicKeyToken=null)
Microsoft.Bot.ChannelConnector.BotAPI+<PostActivityToBotAsync>d__31.MoveNext で処理された内部例外 System.Exception:
   at Microsoft.Bot.ChannelConnector.BotAPI+<PostActivityToBotAsync>d__31.MoveNext (Microsoft.Bot.ChannelConnector, Version=3.2.2.5, Culture=neutral, PublicKeyToken=null)

ん~、なんだろう時間が解決する系??

Azure Bot Services でボットを公開するための最小のコードと手順

$
0
0

Azure Bot Services にボットを公開する最小のコードと手順をやってみようと思います。

というのも、必要最低限を把握してないとその他の応用の部分がどのコードなのかっていうのが把握しにくいと思うので自分は最低限のコードを組むのは一回流行ってる気がします。 いらないと思うものを省いてスリムにね。

ということで、ASP.NET Core の空のプロジェクトからスタートして最終的に Azure にデプロイして動かしてみたいと思います。

プロジェクトの作成

Visual Studio 2017 で ASP.NET Core Web アプリケーションを作成します。空のプロジェクトをさくっとね。HTTPS も特に今はいいのでオフにしてます。

f:id:okazuki:20190223163025p:plain

Bot Framework SDK の ASP.NET Core との統合をしてくれるパッケージを NuGet で入れます。 パッケージ名は Microsoft.Bot.Builder.Integration.AspNet.Coreです。

最小ボットのコードの追加

では、ボットのクラスを作ります。EchoBot とかいう名前で IBot インターフェースを実装したコードを追加します。ボットの対話処理のエントリーポイントになるクラスですね。OnTurnAsync の中にボットでやりたいことを書きます。

using Microsoft.Bot.Builder;
using Microsoft.Bot.Schema;
using System.Threading;
using System.Threading.Tasks;

namespace MinimumBot
{
    publicclass EchoBot : IBot
    {
        public async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken))
        {
            if (turnContext.Activity.Type == ActivityTypes.Message)
            {
                await turnContext.SendActivityAsync(turnContext.Activity.Text);
            }
        }
    }
}

ボットのクラスが出来たので、Bot Framework SDK にこれが俺のボットだよ!っていうのを教えてあげます。また、ASP.NET Core で Bot Framework SDK の初期化処理を書きます。

Startup.csを開いて Configureメソッドを以下のようにして Bot Framework を使うというのを書きます。

// using Microsoft.Bot.Builder.Integration.AspNet.Core;publicvoid Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    app.UseBotFramework();
}

そして、ConfigureServicesメソッドに先ほど作ったボットのクラスの登録処理を書きます。

publicvoid ConfigureServices(IServiceCollection services)
{
    services.AddBot<EchoBot>();
}

ローカルで動作確認

デバッグ実行して URL を確認します。そして、Bot Framework Emulator (V4) を起動してファイルメニューから New Bot Configuration を選択して先ほどの URL に /api/messagesを追加したものを指定します。あとは適当に名前を入れて Save and connectします。

f:id:okazuki:20190223164218p:plain

Azure に公開

では、Azure で Web App を一つ作ります。そして、作成した Web App の URL を控えておきます。

続けて Azure に Bot Channels Registration を作成します。日本語だとボット チャンネル登録ですね。作成時のメッセージング エンドポイントに先ほどゲットした Web App の URL に /api/messagesを追加したものを設定します。

ボット チャンネル登録をデプロイしたリソースグループのデプロイを開くとボットチャンネル登録をデプロイしたときのログが見れるので、それの入力から APPIDAPPSECRETをコピーして控えておきます。

そして、ボットのアプリの Startup.csConfigureServicesメソッドに、その値を設定する処理を追加します。

publicvoid ConfigureServices(IServiceCollection services)
{
    services.AddBot<EchoBot>(options =>
    {
        options.CredentialProvider = new SimpleCredentialProvider("<ここに APPID>", "<ここに APPSECRET>");
    });
}

追加したら先ほど作成した Web App にデプロイします。

動作確認

ボットチャンネル登録の Web チャットでテストを選択するとチャット画面が出るので適当に文字を入力して動くことを確認します。

f:id:okazuki:20190223171000p:plain

まとめ

これが多分最低限だと思います。

あとは Bot Framework SDK で提供されている便利な設定ファイルの読み込みクラスや、対話の状態を管理してくれるダイアログ関連のクラスや、状態管理のクラスや、その他サービスとの連携クラスなどを組み込んでいく感じになります。 Bot Framework SDK 使う人は、ボットとして必要最低限必要なコードがこれで、残りの追加のコードでは何をしてるのだろうというのを把握したうえで書いたり読んだりすると、理解が捗ると思います。

ということで、LINE も対応したみたいだし流行るといいな。

Azure Bot Services の LINE のチャンネルを試してみた

$
0
0

はじめに

とりあえず LINE に繋ぐ手順を試してみました。

本文

昨日みつけた LINE のチャンネルを試してみました。ドキュメントもあるのでそっち見てもOK

docs.microsoft.com

LINE の Developers Console で適当にプロバイダーを作ります(すでにあるならそれを使ってもいい) プロバイダーがないとボットとかも作れないので。プロバイダーが同じボットや Clova のスキルはユーザーを識別するための ID が同じになるので、ユーザーデータを共有する仕組みが作りやすい点が強いです。

さて、作ったら Messaging API を作りましょう。必須入力項目は全部なので全部入れましょう。 プランは Developer-trial を使いましょう。2019/02/25 時点ではドキュメントに記載はありませんが、Freeだと動きませんでした。

作成した Messaging API を選択してチャンネル基本情報から以下の値をゲットします。

  • Channel Secret
  • アクセストークン(再発行ボタンを押して作成してください。とりあえず 0 時間で)

Azure ポータルに移動して LINE 対応させたいボットのボット チャンネル登録を選択してチャンネルを開きます。そして LINE のアイコンをクリックします。

先ほど取得した Channel Secret とアクセストークンを入れる場所があるので入れます。

f:id:okazuki:20190222110123p:plain

入力したら、その下にある Webhook URL をコピーしておいて保存しておきます。

LINE の Developers Console に移動します。

そして、先ほど作成した Messaging API のチャンネル基本情報に Webhook URL という項目があるので編集ボタンを選択して Azure ポータルでコピーした URL を貼ります。

f:id:okazuki:20190222110434p:plain

接続確認ボタンを押して成功すれば OK

そして、デフォルトでは Webhook 送信が有効化されてないので有効化します。

f:id:okazuki:20190222110951p:plain

ボットをちゃんと作ってると LINE の自動応答メッセージは邪魔になることもあるので、その場合はLINE@機能の利用から自動応答メッセージをオフにします。

f:id:okazuki:20190222111357p:plain

同じページに LINE アプリへの QR コードという項目があるので LINE アプリから登録してみましょう。

動いた!!

f:id:okazuki:20190225121540j:plain

ということで

Azure Bot Service を使うと Bot Framework SDK を使って作ったボットを LINE に繋ぐことができます。 Messaging API を使って Webhook 使って自分であれこれするのに比べて以下のメリットがあるように感じます。

  • ユーザ単位での情報保存機能がある
  • 会話単位での情報保存機能がある
  • 今対話のどのフェーズなのかを管理してくれる機能がある(ダイアログ)
  • Azure の自然言語解析との連携機能がある(LUIS っていうサービス)
  • Azure の QnA Maker という QA に回答してくれる API を公開するサービスとの連携機能がある
  • LINE 以外にもつながる

過去の情報:現時点で成功してない

これは最初に試してうまくいかなかったときの情報です。
プランを Free で作るとこうなりました。

話しかけても応答がない。一応このボットは Microsoft Teams と WebChat で動いてるけど LINE で話しかけたときだけ以下のようなエラーが Application Insights に出てきてる。

Message
POST to ボットの名前 failed: POST to the bot's endpoint failed with HTTP status 500 POST to the bot's endpoint failed with HTTP status 500 

Exception type
System.Exception

Failed method
Microsoft.Bot.ChannelConnector.BotAPI+<PostActivityToBotAsync>d__31.MoveNext

コールスタックはこんな感じ。

Microsoft.Bot.Schema.BotException:
   at Microsoft.Bot.ChannelConnector.BotAPI+<PostActivityToBotAsync>d__31.MoveNext (Microsoft.Bot.ChannelConnector, Version=3.2.2.5, Culture=neutral, PublicKeyToken=null)
Microsoft.Bot.ChannelConnector.BotAPI+<PostActivityToBotAsync>d__31.MoveNext で処理された内部例外 System.Exception:
   at Microsoft.Bot.ChannelConnector.BotAPI+<PostActivityToBotAsync>d__31.MoveNext (Microsoft.Bot.ChannelConnector, Version=3.2.2.5, Culture=neutral, PublicKeyToken=null)

ん~、なんだろう時間が解決する系??

Azure Functions を TypeScript でやってみた「ハローワールドから Durable Functions まで」

$
0
0

2019/02/25 くらいに出た Azure Functions Core Tools の 2.4.401 のリリースノートに

- Add TypeScript templates

の文言が。

これは試すしかないでしょう。ということでアップデートして func initしてみます。

C:\Users\kaota\Documents\Labs\TSFunc\Hello>func init
Select a worker runtime: node
Select a Language:
javascript
typescript

typescript がちゃんとある。typescript を選択すると package.json には以下のような devDependencies が追加されて

"devDependencies": {"@azure/functions": "^1.0.1-beta1",
    "typescript": "^3.3.3"
  }

さらには tsconfig.json も生成されます。

{"compilerOptions": {"module": "commonjs",
    "target": "es6",
    "outDir": "dist",
    "rootDir": ".",
    "sourceMap": true,
    "strict": false}}

npm installしておきます。

func newで HttpTrigger を選ぶとちゃんと index.ts が追加されました。

import{ AzureFunction, Context, HttpRequest }from"@azure/functions"const httpTrigger: AzureFunction =asyncfunction(context: Context, req: HttpRequest): Promise<void>{
    context.log('HTTP trigger function processed a request.');const name =(req.query.name ||(req.body && req.body.name));if(name){
        context.res ={// status: 200, /* Defaults to 200 */
            body: "Hello " + (req.query.name || req.body.name)};}else{
        context.res ={status: 400,
            body: "Please pass a name on the query string or in the request body"};}};exportdefault httpTrigger;

そして生成された HttpTrigger の function.json を見てみるとこうなってます。

{"disabled": false,
  "bindings": [{"authLevel": "function",
      "type": "httpTrigger",
      "direction": "in",
      "name": "req",
      "methods": ["get",
        "post"
      ]},
    {"authLevel": null,
      "type": "http",
      "direction": "out",
      "name": "res",
      "methods": null}],
  "scriptFile": "../dist/Hello/index.js"
}

なるほど、scriptFile で dist フォルダーの下に作られるコンパイル後の JavaScript を見るようにしてるんですね。ということで npm run buildをするか、npm run watchをしてビルドをします。

そうすると dist フォルダーに js が生成されます。あとは func host startをするとローカルで動くのでアクセスしてみましょう。

動いた!!

f:id:okazuki:20190228130209p:plain

Durable Functions も試してみる

ハローワールド動いたので次は、個人的に Azure Functions のキラーコンテンツだと思ってる Durable Functions を試してみます。これが動かないと始まらない。 とりあえず以下のページを参考に…

github.com

docs.microsoft.com

まず下準備

func extensions install -p Microsoft.Azure.WebJobs.Extensions.DurableTask -v 1.7.0
npm install durable-functions

node_modules の下の durable-functions を見てみたらちゃんと .d.tsもありますね!

f:id:okazuki:20190228130712p:plain

func newにも、ちゃんと Durable 系のテンプレートが入ってます。いいね。

f:id:okazuki:20190228130831p:plain

ということで Starter と Orchestrator と Activity を適当に作ります。

f:id:okazuki:20190228131002p:plain

インテリセンス出る。まじ神

f:id:okazuki:20190228131236p:plain

ということで、こんな感じに Starter を調整して

import * as df from"durable-functions"import{ AzureFunction, Context, HttpRequest }from"@azure/functions"const httpStart: AzureFunction =asyncfunction(context: Context, req: HttpRequest): Promise<any>{const client = df.getClient(context);const instanceId =await client.startNew('Orchestrator',undefined, req.body);

    context.log(`Started orchestration with ID = '${instanceId}'.`);return client.createCheckStatusResponse(context.bindingData.req, instanceId);};exportdefault httpStart;

Orchestrator もこんな感じに

import * as df from"durable-functions"const orchestrator = df.orchestrator(function* (context){const outputs =[];

    outputs.push(yield context.df.callActivity("Activity1","Tokyo"));
    outputs.push(yield context.df.callActivity("Activity2","Seattle"));return outputs;});exportdefault orchestrator;

Activity 関数はこんな感じ。

import{ AzureFunction, Context }from"@azure/functions"const activityFunction: AzureFunction =asyncfunction(context: Context): Promise<string>{return`Hello ${context.bindings.name} from Activity1!`;};exportdefault activityFunction;

ではビルドしたら実行しようと思ったのですが以下のエラーが。

node_modules/durable-functions/lib/src/utils.d.ts:1:23 - error TS2688: Cannot find type definition file for 'node'.
node_modules/durable-functions/lib/src/utils.d.ts:8:56 - error TS2503: Cannot find namespace 'NodeJS'.

dist に js は生成されてるからビルドは出来てるけど気持ち悪いのでとりあえず以下のコマンドをうったら消えた。

npm i -d @types/node

Azure Storage Emulator を起動して func host startします。

Azure Storage Emulator は最新にしましょう。少し古いやつだと何か問題が起きた記憶。

そうするとエラーが出ました。

[2019/02/28 4:20:44] A host error has occurred
[2019/02/28 4:20:44] Microsoft.WindowsAzure.Storage: Settings must be of the form "name=value".
Settings must be of the form "name=value".

そういえば local.settings.json に Azure Storage の接続文字列が初期状態では設定されてないんでした。エミュレーター用の接続文字列をとりあえず追加します。

こんな感じで

{"IsEncrypted": false,
  "Values": {"FUNCTIONS_WORKER_RUNTIME": "node",
    "AzureWebJobsStorage": "UseDevelopmentStorage=true"
  }}

あ、あと func host start以外にも npm run startnpm run start:hostとかでも OK。 何やってるかは package.jsonにあります。

起動したら Starter のエンドポイントを叩くとばっちり動きます。

f:id:okazuki:20190228132921p:plain

statusQueryGetUri を叩くと以下の JSON が返ってきました。

{"instanceId":"28038e30463544ab965db44f9c9ccb1e",
  "runtimeStatus":"Completed",
  "input":null,
  "customStatus":null,
  "output":["Hello Tokyo from Activity1!","Hello Seattle from Activity2!"],
  "createdTime":"2019-02-28T04:28:54Z",
  "lastUpdatedTime":"2019-02-28T04:28:56Z"
}

ばっちりですね。適当に Azure に Function App を作ってデプロイもしてみました。とりあえず試すだけなら以下のコマンドで

func azure functionapp publish <作った Function App の名前>

デプロイされました。関数もちゃんと認識されてます。

f:id:okazuki:20190228133603p:plain

叩いてみると無事動いた動いた。めでたい。

{"instanceId":"03e27dc7db234991bead2ea369173963",
  "runtimeStatus":"Completed",
  "input":null,
  "customStatus":null,
  "output":["Hello Tokyo from Activity1!","Hello Seattle from Activity2!"],
  "createdTime":"2019-02-28T04:36:16Z",
  "lastUpdatedTime":"2019-02-28T04:36:26Z"
}

まとめ

これで Azure Functions の node のほうで開発するときに手軽に TypeScript 始めることが出来るようになりました。 TypeScript 好きな人も是非 Azure Functions 使ってみてね!

Azure DevOps に CLI が来た(プレビュー)

$
0
0

まじで。

azure.microsoft.com

Azure CLI v2.0.49 以上が入ってれば以下のコマンドで入ります。

 az extension add --name azure-devops

こんなメッセージが出れば OK

The installed extension 'azure-devops' is in preview.

あとは、まだログインしてなかったら az loginでログインして、デフォルトのオーガナイゼーションを設定して

az devops configure --defaults organization=https://dev.azure.com/xxxx project=xxxx

ヘルプを見てみるとこんな感じでした。色々できそう。

>az devops -h

Group
    az devops : Manage Azure DevOps organization level operations.
        Related Groups
        az pipelines: Manage Azure Pipelines
        az boards: Manage Azure Boards
        az repos: Manage Azure Repos
        az artifacts: Manage Azure Artifacts.

Subgroups:
    admin            : Manage administration operations.
    project          : Manage team projects.
    service-endpoint : Manage service endpoints/service connections.
    team             : Manage teams.

Commands:
    configure        : Configure the Azure DevOps CLI or view your configuration.
    feedback         : Displays information on how to provide feedback to the Azure DevOps CLI team.
    login            : Set the credential (PAT) to use for a particular organization.
    logout           : Clear the credential for all or a particular organization.

リポジトリの情報見てみたかったらこんな感じ

$ az repos show -r Xxxx
{
  "defaultBranch": "refs/heads/master",
  "id": "xxxx",
  "isFork": null,
  "name": "Xxxx",
  "parentRepository": null,
  "project": {
    "abbreviation": null,
    "description": null,
    "id": "xxxx",
    "lastUpdateTime": "2019-02-15T05:10:50.317Z",
    "name": "Xxxx",
    "revision": 243,
    "state": "wellFormed",
    "url": "https://dev.azure.com/xxxx/_apis/projects/xxxx",
    "visibility": "private"
  },
  "remoteUrl": "https://xxxx",
  "size": 9537505,
  "sshUrl": "git@ssh.dev.azure.com:v3/xxxx/xxxx/xxxx",
  "url": "https://dev.azure.com/xxxx/xxxx/_apis/git/repositories/xxxx",
  "validRemoteUrls": null
}

ふむ、個人的に気になるのは Azure Boards まわり。id 指定で見たり更新したり(GUIでやったほうが便利だった)、あとは知らなかったんですが Work Item Query Language (WIQL) で問い合わせ出来るみたいです。

docs.microsoft.com

まぁ SQL ですね。使える列というかフィールドは以下にありました。

docs.microsoft.com

ということでこんなクエリーを投げると…

az boards query --wiql "SELECT [System.Id] FROM WorkItems WHERE [System.IterationPath] = @CurrentIteration('[Tasks]\Tasks Team')"

こんな JSON が返ってきました。

[{"fields": {"System.Id": 853},
    "id": 853,
    "relations": null,
    "rev": 21,
    "url": "https://dev.azure.com/xxxx/_apis/wit/workItems/853"
  },
  {"fields": {"System.Id": 915},
    "id": 915,
    "relations": null,
    "rev": 21,
    "url": "https://dev.azure.com/xxxx/_apis/wit/workItems/915"
  },
  {"fields": {"System.Id": 972},
    "id": 972,
    "relations": null,
    "rev": 14,
    "url": "https://dev.azure.com/xxxx/_apis/wit/workItems/972"
  },
  ... 続く ...
]

CLI ツールが出来たことでいろんなものとの連携が捗りそうですね!

Azure の更新情報って何処で仕入れてるの??

$
0
0

というのに対して自分が見てるところをメモっておきます。

非公式ソース

まず見やすいのがお馴染みのブチザッキ。

blog.azure.moe

Azure 関連の最新情報に加えて、各種マイクロソフトの海外イベントの翌朝には内容がまとまってる神サイトです。 凄く大変なことだと思うので素直に尊敬してます。

公式ソース

あと、公式だと以下に更新情報が垂れ流されてるので定期的に見て面白そうなものがないかな?ってチェックしてます。

azure.microsoft.com

GitHub

上記のものは全体的なものを把握するものですが、もし自分が興味のあるサービス関連の開発が GitHub 上で行われている場合は GitHub の Issues や Pull Requests やソースをチェックすると、新機能や修正の背景までわかって面白いと思います。

個人的にはこことか

github.com

ここらへん?

github.com

個人的にはランタイム的な根っこの部分のコードよりは各種 SDK のコードが公開されてれば、そこらへんから見るのが難易度的には低いと思ってますし、私もランタイムとかのコードが公開されてても調査の過程で、そこに行きつくまで中を見ることは滅多にないです。

まとめ

週 1 くらいで少し眺めるということをしておかないと、量が増えるので週 1 くらいのペースで全体の更新情報を見てみるのが個人的なお勧めです。


Mixed Reality アプリに欲しい機能

$
0
0

もうかれこれ 1 年近く Unity は追いかけてないので、今ならあるぜっていうものもある可能性もあるけど、こんなの欲しいな~っていう願望です。

3D 空間を前提としたレイアウトシステム

最近、2D アプリでレイアウトの位置計算を自前ですることってないですよね??? 大体の API やスタイルを適用する仕組みに、こんな感じでよろしくって宣言的に書いておけばプラットフォーム側が解釈してよしなにしてくれる。

自分で座標計算が必要なケースでは、そういうこともできる手段を提供してくれてて、必要なときだけ自前計算します。

そんな感じで一般的なインタラクションとかを作る場合には宣言的にマークアップするだけでよしなにしてくれるような仕組みがほしい。

本当に欲しい。

Xaml Islands for MR

MR アプリ作るときに全部 3D じゃなくて 2D のコンテンツというか普通のウィンドウ浮かしたいってことありますよね。 そのときは uGUI 使って書くと思うのですが元々が GUI アプリ開発用のプラットフォームじゃないので辛いし、OS では提供されてるようなコントロールも意外となかったりしてユーザーに機能を提供するのに、なんで一般的な GUI 部品を自分で実装してるのか?っていう気持ちになります。

ということで UWP のウィンドウもしくはコントロールをホストするような部品が欲しい。欲しい。

可能ならば一般的なウィンドウに対してする操作(移動、リサイズ、最大化、最小化、etc...)はさくっとしたい。

2D アプリで 3D 空間出したい

3D がメインなら 3D の中に 2D をオプションでって形でいいんですが、2D がメインで 3D がおまけ的立ち位置の場合には新しいウィンドウを 1 つ作るくらいのノリで 3D 空間出したい。

こんな雰囲気で

private UnityApplication _app;

private async void Launch3DWorldButton_Click(object sender, EventArgs e)
{
  if (_app == null)
  {
    return;
  }
  
  _app = new UnityApplication();
  await _app.LaunchAsync();
}

そして、こんな感じで動かせるようにしたい。

_app.HogeBehavior.Foo();

_app.HogeBehavior.SomeProperty = "xxxxx";

await _-app.RunOnUnityThreadAsync(() =>
{
});

まとめ

妄想でした。

WPF のビヘイビアーが入ってる Blend SDK for Visual Studio が Visual Studio 2019 で消えた件について

$
0
0

2017 までは Visual Studio についてきた Blend SDK for Visual Studio でしたが 2019 で消えました。2017 から必須じゃなくてオプションになってたので変だなぁって思ってたんですよ。

id:nuitsjpさんのブログにも書いてありますというか、そのブログを見て知りました。

www.nuits.jp

2017 のインストーラーから入れると従来の Blend SDK for Visual Studio が入るので従来のものを使いたい場合はそうするのがいいのですが、調べてみたら、いつの間にか OSS になってました。

github.com

名前空間が Microsoft.Xaml.Behaviorsになっているので、従来の System.Windows.Interactivityから見たら破壊的変更ですが、上記のリポジトリーに Behaviorsなどの System.Windows.Interactivityに入っていたクラスや Microsoft.Expression.Interactionsに入っていた CallMethodActionなどが入っているので 2 が統合されたのが XamlBenaviorsWpfになるっぽいです。

NuGet を確認したらパッケージも 3 か月前に上がってました。

www.nuget.org

ということで、今後はこちらに引っ越すのがよさそうです。

まとめ

素で OSS になってるの知らなかった。

Azure Functions の Durable Functions をクロスデバイスアプリケーションのバックエンドとして使おう

$
0
0

個人的に一番好きな Azure の機能の中に Azure Functions があります。 その中でも特に Durable Functions は強い。

複数の処理が組み合わさって長時間にわたって動く可能性のある処理を簡単にコードで書けるのが特徴です。

例えば以下のドキュメントからの引用ですが…

Durable Functions のパターンおよび技術的概念 - Azure | Microsoft Docs

以下のような複数の関数が、連続して動くようなケースですね。

f:id:okazuki:20190303233327p:plain

もし、これを特に Azure Functions や Durable Functions にあるような機能を使わないで書くとこんなイメージになると思います。

// F1 のコード

var result = DoSomething();

var queue = new QueueClient("F2Queue");
queue.PutMessage(result);
// F2 のコード
var inputQueue = new QueueClient("F2Queue");
var outputQueue = new QueueClient("F3Queue");
while (true)
{
  var input = inputQueue.GetMessage();
  var result = DoSomething2(input);
  outputQueue.PutMessage(result);
}

F3 と F4 も同じ感じですね。

F4 だけは処理結果を最終的に DB なり何らかのストレージに書き込むかもしれません。

// F4 のコード
var inputQueue = new QueueClient("F2Queue");
var db = new Database("接続文字列");
while (true)
{
  var input = inputQueue.GetMessage();
  var result = DoSomething2(input);
  db.Insert(result);
}

んで、この関数チェーンを読んで結果を受け取る人は…

var body = ...;
var client = new HttpClient();
awaut client.PostAsync("http://example.com/api/F1", body);

var db = new Database("接続文字列");
Result result = null;
do 
{
  result = db.Get();
  Thread.Sleep(3000);
}
while (result != null);

DoSomething(result);

非常に単純に書きましたが、こんな単純な処理でも足りてないものがあります。処理の呼び出しと結果のデータを紐づけるためのキー情報が必要ですね。あとはエラーが出たときはどうするの?とか、分岐したらどうなるの?とかループがあると…と色々考えると出来なくはないけど、かなりめんどくさいレベルになります。

Durable Functions

まぁそこらへんをこういう風に書けるようにしてくれるやつ。

var x = await context.CallActivityAsync("F1", null);
var y = await context.CallActivityAsync("F2", x);
var z = await context.CallActivityAsync("F3", y);
return await context.CallActivityAsync("F4", z);

F1, F2, F3, F4 の処理も普通に関数みたいに書けます。

// F1
[FunctionName("F1")]
publicstatic async Task<string> F1([ActivityTrigger] string arg)
{
  return DoSomething();
}

F2, F3, F4 とかも同じノリでいけます。

さて、ここで気付いてほしいのは F1, F2, F3, F4 の中には単純に各々の処理でやりたいことを書けます。そして、それを呼び出す側では処理の流れを書けます。

Durable Functions を使わないケースのコードは F1, F2, F3, F4 の中に処理の全体の流れの制御が分散していて見通しが悪いものでした。さらにフローが断片化したものとビジネスロジックが混ざってあまりいい感じではないものでした。

こんな風にフローを定義するところがオーケストレーター関数で、個別のロジックを書くところがアクティビティ関数と呼ばれたりします。

このような非常に単純なケースでも見た目がすっきりします。複雑なケースだと推して知るべし。

その他にもタイマーで一定時間待ったり、外部イベントという機能を使うと外から特定の処理が呼ばれるまでオーケストレーター関数を途中で待たせるとか色々できます。

クロスデバイスアプリの裏側として

クロスデバイスアプリケーションって複数デバイスで 1 つのタスクを最適なデバイスでやれるようにしてるものだと思ってるのですが、そういうものを作る場合には、処理がどこまで進んだのかとか何処かでみんながアクセスできるように管理してやる必要があります。

多分単純に作ると DB に進捗状況を記録しておいて、それを参照しつつ、その時にする処理を進めるとかっていった感じで実装することが出来るんじゃないかなって思います。

例えば、処理A、処理B、処理Cを順番にするけど 処理A はスマートスピーカーで起動して、次の処理はパソコンでやって、最後の処理はスマホでやるとか。

そんな作業ってどんな作業だよ!?って思うかもしれないけど、あくまでたとえってことで。 そういう場合にはこんな感じで Durable Functions のオーケストレーター関数が書けるかな?

[FunctionName("SomeTask")]
publicstatic async Task<COutput> Run([OrchestrationTrigger] DurableOrchestrationContext context)
{
  context.SetCustomStatus("処理Aの途中");
  var a = await context.CallActivityAsync("処理A", null);
  context.SetCustomStatus(new { result = a, status = "処理Aの終わり" });
  // 外から B トリガーが呼ばれるのを待つ
  var bInput = await context.WaitForExternalEvent<BInput>("BTrigger");
  var b = await context.CallActivityAsync<BOutput>("処理B", bInput);
  context.SetCustomStatus(new { result = b, status = "処理Bの終わり" });
  var cInput = await context.WiatForExternalEvent<CInput>("CTrigger");
  return await context.CallActivityAsync<COutput>("処理C", cInput);
}

Durable Functions には実行したオーケストレーター関数の処理状況を確認するための API があるので、それを使えば今自分がやらないといけない所まで処理が進んでるのか?とかがチェックできます。

もうちょっとクライアント側にやさしく作るとすると、各処理の終わりとかのタイミングでプッシュ通知のメカニズムを使って通知してあげると親切っぽいですね。

まとめ

つらつらとしたメモですが、こんな感じで処理状態を記録しつつ処理を進めていくようなワークフロー的なものを簡単に書けるのでマルチデバイスアプリケーションや、まぁ別にマルチデバイスじゃなくても 1 つのワークフローが長い処理を進めていくようなものを作るのには良さそうですね。

もちろんプログラムコードで書けるので分岐、ループ、例外処理などが普通に書けて強い。

スマートスピーカーを遊びたおす会で Durable Functions の発表してきました

$
0
0

時間のかかる処理簡単に作れるよ!しかも進捗確認も簡単にできる!!っていうくらいのノリで発表してきました。

発表資料はこちらになります。

ちょまどさんパートの中身については、ちょまどさんにお任せするとして私のパートでお伝えしたかったことはこれです。

こういう複数の処理が連携して動くワークフローの定義って、私の認識ではワークフローデザイナーみたいなので画面でぽちぽちデザインして、裏側では JSON なり XML なりで管理されて実行エンジンが、それを解釈して動くようなものが多いと思ってます。 Durable Functions はプログラムコード(現在対応している言語は C#, JavaScript(TypeScript も OK)) で書ける点が強いです。

会場で受けた質問について

要約すると以下のような内容の質問を受けました。

最初はいい感じで動いてたけど、そこそこの期間運用してるとうまく動かなくなることがあった。
Durable Functions をオーケストレーター関数のインスタンス ID にはユーザー ID を指定している。

即答できなかったのでちょっと調べてみたら、同じ ID での実行履歴がたくさん溜まってくると、それを読み取るのに時間がかかるので結果として Durable Functions がうまく動かなくなるっぽいように見えました。

docs.microsoft.com

上記ドキュメントから引用

このテーブルのパーティション キーは、オーケストレーションのインスタンス ID から派生します。 
ほとんどの場合、インスタンス ID はランダムであり、Azure Storage での内部パーティションの最適な分散が確保されます。

そうはいってもユーザー単位で単一のオーケストレーター関数っていうのは便利なのでユーザー単位でインスタンス ID が 1 つというのはやりたい。

ということなので、履歴消去の仕組みがありました。

docs.microsoft.com

PurgeInstanceHistoryAsyncで自分にとって都合のいいタイミングでいらなそうな履歴は消してしまうと精神衛生上よさそうです。

まとめ

Durable Functions はいいぞ。

デモのソースコード

github.com

TypeScript を使ってサーバーレスで WebSocket サーバーを作ってみた

$
0
0

先日 Microsoft Azure のサーバーレスプラットフォームの Azure Functions に SignalR Service bindings の一般提供開始のアナウンスがありました!

azure.microsoft.com

リポジトリはこちら。

github.com

ということで、これもまた先日 Azure Functions Core Tools でサポートされた TypeScript のプロジェクトテンプレートを使ってやってみたいと思います。

やる前に

Azure Functions の SignalR Service bindings にはクライアントに接続のための情報を返すための機能と、繋いでいるクライアントへメッセージを送信するための出力バインディングがあります。

つまり、Azure Functions 側から繋ぎに来ているクライアントにメッセージをブロードキャストすることは出来ますが、クライアントからの SignalR での通信を受けることは出来ないっぽいようです。 クライアントから Azure Functions へは HttpTrigger あたりを使った普通の REST API 呼び出しを使う感じですね。

プロジェクト作成

Azure Functions の拡張機能を入れた Visual Studio Code をベースに作業をしていきたいと思います。 ということでさくっと Create New Project...で Azure Functions のプロジェクトを作成します。

f:id:okazuki:20190309111307p:plain

言語は TypeScript を選ぼう

f:id:okazuki:20190309111249p:plain

そして以下のコマンドを実行して SignalR 用の拡張をインストールします。

func extensions install --package Microsoft.Azure.WebJobs.Extensions.SignalRService --version 1.0.0

SignalR 対応をしていこう

SignalR 接続情報入力バインドというものを作ってクライアントに Azure の SignalR Service に接続するための情報を返してあげます。とりあえず HttpTrigger の関数を作って SignalR の接続情報を返す処理を追加します。名前は negotiateじゃないとダメみたいです。

func newコマンドか Visual Studio Code の関数を作成するボタン(プロジェクトを作った時に押したボタンの横にある)で HttpTrigger の関数を作ります。

作成した関数の function.jsonsignalRConnctionInfoのバインドを追加します。

{"bindings": [{"authLevel": "anonymous",
      "type": "httpTrigger",
      "direction": "in",
      "name": "req",
      "methods": ["get",
        "post"
      ]},
    {"type": "signalRConnectionInfo",
      "name": "connectionInfo",
      "hubName": "chat",
      "connectionStringSetting": "SignalRConnection",
      "direction": "in"
    },
    {"type": "http",
      "direction": "out",
      "name": "res"
    }],
  "scriptFile": "..\\dist\\negotiate\\index.js"
}

関数の中では入力として受け取った接続情報を単純に HTTP のボディにセットして返します。

import{ AzureFunction, Context, HttpRequest }from"@azure/functions"const httpTrigger: AzureFunction =asyncfunction(context: Context, req: HttpRequest, connectionInfo: any): Promise<void>{
    context.res ={ body: connectionInfo };};exportdefault httpTrigger;

これでクライアントから繋ぐことができるようになったので、続けて繋いできたクライアントにメッセージを投げようと思います。それ用の関数として PostMessage という名前の HttpTrigger 関数を作成します。

接続してきているクライアントにメッセージを投げるには SignalR 用の出力バインディングを関数に追加してやれば良さそう。 ということで PostMessage 関数の function.json に signalr の出力バインディングの定義を追加します。

{"bindings": [{"authLevel": "anonymous","type": "httpTrigger","direction": "in","name": "req","methods": ["get","post"]},{"type": "signalR","name": "signalRMessages","hubName": "chat","connectionStringSetting": "SignalRConnection","direction": "out"},{"type": "http","direction": "out","name": "res"}],"scriptFile": "..\\dist\\PostMessage\\index.js"}

出力バインディングに渡す C# のオブジェクトを見てみると userId, groupName, target, argumentsを渡してやればよさそう。 targetarugmentsが必須での折はオプションみたいです。ということで、このようなインターフェースを TypeScript で定義しました。

exportinterface SignalRMessage {
    userId?: string
    groupName?: string
    target: stringarguments: {[key:string]: any}}

そして PostMessage 関数で適当にリクエストの body をそのままクライアントに渡すように作成しました。

import{ AzureFunction, Context, HttpRequest }from"@azure/functions"import{ SignalRMessage }from"../signalr/signalrmessage"const httpTrigger: AzureFunction =asyncfunction(context: Context, 
    req: HttpRequest): Promise<void>{
    context.log(JSON.stringify(req.body));
    context.bindings.signalRMessages =newArray<SignalRMessage>({
        target: "receiveMessage",arguments: [
            req.body,],});};exportdefault httpTrigger;

テスト

ここまで出来たらテストしてみます。Azure に SignalR Service を作成しましょう。 大量のデータはさばけないけど開発用には Free プランで作っておけばよさそうです。エミュレーターとかあればもうちょっと捗りそうだけど、まぁ仕方ないのかな。

作成した SignalR Service の Keys を見ると接続文字列があるのでコピーします。

f:id:okazuki:20190309134201p:plain

ローカル用のアプリケーション設定を書く local.settings.jsonを開いて function.jsonで使ってた SignalRConnectionという名前で先ほどの接続文字列を追加します。

{"IsEncrypted": false,
  "Values": {"AzureWebJobsStorage": "",
    "FUNCTIONS_WORKER_RUNTIME": "node",
    "SignalRConnection": "ここに接続文字列を張り付け"
  }}

テスト用クライアントを作成します。クライアントは何でもいいのですが、今回は Vue.js で試してみようと思います。因みに現時点では JavaScript, Java, C# あたりに対応しています。Swift とかはプレビューみたいです。

docs.microsoft.com

vue の cli を入れてる状態で vue create vue-signalr-clientといった感じで任意の場所に TypeScript のプロジェクトを作ります。 プロジェクトを作ったら npm i @aspnet/signalrで SignalR を追加します。

src/components/HelloWorld.vueをいじっていきます。

<template><div><div><input type="text" v-model="message" /><button @click="onPostMessageClick">PostMessage</button></div><div><div :key="index" v-for="(chatMessage, index) in chatMessages">{{ chatMessage }}</div></div></div></template><script lang="ts">import{ Component, Prop, Vue }from'vue-property-decorator';import{ HubConnection, HubConnectionBuilder, LogLevel, JsonHubProtocol }from'@aspnet/signalr';@Componentexportdefaultclass HelloWorld extends Vue {public message: string='';public chatMessages: string[]=[];private connection!: HubConnection;publicasync onPostMessageClick(): Promise<void>{if(this.connection ==null){return;}await fetch('http://localhost:7071/api/PostMessage',{
      method: 'POST',
      body: JSON.stringify({ text: this.message }),});this.message ='';}publicasync created(): Promise<void>{
    console.log('created called');this.connection =new HubConnectionBuilder()
      .withUrl('http://localhost:7071/api')
      .configureLogging(LogLevel.Information)
      .build();this.connection.on('receiveMessage',(message)=>{
      console.log(JSON.stringify(message));this.chatMessages.push(message.text);});awaitthis.connection.start();
    console.log('created finished: ' + this.connection);}}</script><!-- Add "scoped" attribute to limit CSS to this component only --><style scoped></style>

created 関数で SignalR の接続を作成しています。HubConnectionBuilderwithUrlメソッドで Azure Functions の URL の api の部分までを渡します。そうすると勝手に negotiate関数を探しに行って接続を確立してくれます。

本番では、これだけじゃなくて再接続処理も入れないといけないっぽいですね。

ASP.NET Core SignalR JavaScript クライアント | Microsoft Docs

CORS の設定

さて、Vue のプロジェクトを実行すると http://localhost:8080で起動するので Azure Functions のローカルのランタイムに CORS の設定をしてやります。 local.settings.jsonを開いて以下のように CORS の設定を追加します。

{"IsEncrypted": false,
  "Values": {"AzureWebJobsStorage": "",
    "FUNCTIONS_WORKER_RUNTIME": "node",
    "SignalRConnection": "Endpoint=https://okazukisignalr.service.signalr.net;AccessKey=bH4HAffm8395oxBUyA2D3kBPYSOyhv0it1B5teCXkdA=;Version=1.0;"
  },
  "Host": {"CORS": "http://localhost:8080",
    "CORSCredentials": true}}

動かす

Azure Functions の方のプロジェクトを実行して、Vue のクライアント側を実行します。2つのブラウザーで Vue のクライアントのページを開いて動かしてみると…

動いた!

f:id:okazuki:20190309154432p:plain

クラウドにデプロイ

では Azure SignalR Service を作成したところに Function App を従量課金プランで作成します。 ランタイムスタックは今回の場合は JavaScript ですね。

Function App が作成されたら、アプリケーション設定を開いて SignalRConnection というキーで SignalR Service への接続文字列を追加しておきます。

追加したら func azure functionapp publish FunctionAppの名前と打ち込んでデプロイしましょう。

テスト用の Vue のアプリもデプロイしましょう。デプロイ先は Azure のほうに Storage Account を作って静的 Web サイトのオプションをオンにします。インデックスドキュメントは index.htmlにしておきましょう。

f:id:okazuki:20190309160039p:plain

HelloWorld.vueで SignalR の接続先が http://localhost:8080となっている部分を https://FunctionApp名.azurewebsites.netに変えます。そして、Vue のプロジェクトで npm run buildしてプロダクション用にビルドします。 dist フォルダーの中身を先ほど作ったストレージアカウントの BLOB の $web というコンテナにアップロードします。 Azure Storage Explorer 使うと楽です。

そして Function App の CORS の設定でストレージアカウントのプライマリエンドポイントの名前を設定します。

f:id:okazuki:20190309160817p:plain

ここまで完了すると以下のように Azure 上でも動くはずです。

f:id:okazuki:20190309160946p:plain

おまけ

.NET クライアントライブラリや Java クライアントライブラリもあるので…

docs.microsoft.com

docs.microsoft.com

こんな感じで Web ページと C#, Java あたりで作ったアプリで同じデータを受信するようにもできます。

f:id:okazuki:20190309174018p:plain

参考

docs.microsoft.com

ソースコード

GitHub にあげてます。因みに Azure のリソースは全て削除してあるので動かすときはソースコード内にハードコーディングされてる URL は書き換えてください。

github.com

最後に

今回は気分で TypeScript を使いましたが C#, Java とかでもサーバーサイドは書けます。

Durable Functions あたりと組み合わせるとオーケストレーター関数の進捗状況のプッシュ通知的なものに SignalR Service 使って通知とかできそうで面白そうですね。

中身のない記事

$
0
0

ちょっと中身のない記事失礼しますよっと

スマートスピーカーのバックエンドとしての Azure Functions のコールドスタート対策

$
0
0

やろうとしてるのは、この記事の内容を API Management を使ってやってみようというやつです!

qiita.com

Azure Functions のコールドスタートよりも早い何かを手前に挟んで初回は定型文を返して二回目以降は本番にディスパッチしましょうというアプローチ。なるほど。

上記記事では Logic App を使ってます。

ということで同じアプローチとして 2018 年の 12 月にプレビューとして公開された Azure API Management の従量課金プランでもやってみようと思います。因みに現時点でプレビューなので、実運用考えると一般提供開始のステータスの Logic App が今のところ現実的だと思います。

やってみよう

とりあえず Google アシスタントで試そうと思います。まずは Azure Functions で現在時間を返すだけの関数とインスタンスを起こすときに呼ぶ関数を作成します。

using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;

namespace EchoBot
{
    publicstaticclass Dialogflow
    {
        [FunctionName("Dialogflow")]
        publicstatic IActionResult Run(
            [HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest req)
        {
            returnnew JsonResult(new 
            {
                fulfillmentText = $"現在のUTC時間は{DateTime.UtcNow.ToString("yyyy年MM月dd日のHH時mm分ss秒です。")}",
            });
        }

        [FunctionName("Wakeup")]
        publicstatic IActionResult Wakeup(
            [HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest request)
        {
            returnnew OkResult();
        }
    }
}

これを従量課金の Function App にデプロイします。

そして、API Management を Consumption レベルで作ります。日本はまだ無いっぽいので東南アジアで作りました。 くっ、Function App を東日本に作ったのでレスポンス速度的には痛手です。まぁこの場合でもマイクロソフトのバックボーンネットワークでの通信なのでインターネット経由じゃないから早いはず。どうせなのでこのままやってみましょう。

f:id:okazuki:20190309214729p:plain

作成した API Management で API を選んで Function App を選択します。そして Function App をインポートします。

f:id:okazuki:20190309231810p:plain

そして POST DialogflowInbound processing'のPoliciesDefault Welcome Intentの時はWakeup` を呼んで固定の JSON を返して、それ以外はバックエンドに処理を回すような XML を書きます。

<policies><inbound><base /><set-backend-service id="apim-generated-policy"backend-id="coldstartapp" /><set-variable name="isWelcomeIntent"value="@((context.Request.Body.As<JObject>(preserveContent: true)["queryResult"]?["intent"]?["displayName"]?.ToObject<string>() ?? "Default Welcome Intent") == "Default Welcome Intent")" /><choose><when condition="@((bool)context.Variables["isWelcomeIntent"])"><set-variable name="wakeupUrl"value="@{                    var url = context.Request.OriginalUrl;                    return $"{url.Scheme}://{url.Host}:{url.Port}{context.Api.Path}/Wakeup{url.QueryString}";                }" /><send-one-way-request mode="new"><set-url>@((string)context.Variables["wakeupUrl"])</set-url><set-method>POST</set-method><set-header name="Content-Type"exists-action="override"><value>application/json; charset=utf8</value></set-header><set-header name="Ocp-Apim-Subscription-Key"exists-action="override"><value>@(context.Request.Headers.GetValueOrDefault("Ocp-Apim-Subscription-Key", ""))</value></set-header></send-one-way-request><return-response><set-status code="200"reason="OK" /><set-header name="Content-Type"exists-action="override"><value>application/json; charset=utf-8</value></set-header><set-body>@(new JObject(
                            new JProperty("fulfillmentText", "Hello")
                        ).ToString())</set-body></return-response></when></choose></inbound><backend><base /></backend><outbound><base /></outbound><on-error><base /></on-error></policies>

API Management は初心者なので、もっといい書き方があるかも…。

そして、サブスクリプションを追加します。

f:id:okazuki:20190310002317p:plain

サブスクリプションのキーを取得したら Dialogflow の Fulfilment の Webhook の URL に API Management で定義した Dialogflow のエンドポイントの URL と HEADERS に Ocp-Apim-Subscription-Key をキーにしてサブスクリプションキーの値を設定します。

f:id:okazuki:20190310002512p:plain

動かしてみます。

f:id:okazuki:20190310111115p:plain

動いた!!!でもコンサンプションプランの Azure API Management のコールドスタートがどれくらいの時間かかるのかは測ってないので、しばらく時間を置いて後で試してみよう。


Azure Bot Service の LINE Connector でスタンプみたいな LINE 固有機能を使おう

$
0
0

先日書いた Azure Bot Service の LINE 対応ですが、スタンプみたいな LINE 固有機能使いたいんだけどどうするの?という疑問がわいてくるような気がします。

こういう複数のプラットフォームに対応するときは、機能が最大公約数になってしまうのですが、ちゃんと Azure Bot Service の SDK にはチャンネル固有機能への対応方法が用意されています。

チャンネル固有の機能の実装というドキュメントが用意されていて、なんとそこにちゃんと LINE のことも書いてありました。

docs.microsoft.com

ドキュメントから引用すると以下のように結構いける気がする。

次の LINE メッセージのタイプがサポートされています。
- スタンプ
- イメージマップ
- テンプレート (ボタン、確認、カルーセル)
- Flex

対応早いね!!って思ったのでドキュメントのコミットログを見てみたら…

Release 'live' 2-21-2019 (#1320)と書いてあるコミットで、LINE の追記がされてたので、私が前に試したのが 2019/02/22 なので、その時にはドキュメントも整理されてたっぽい。気づかなかった。

github.com

では、実際に話しかけるとスタンプを返すボットを作ってみようと思います。

プロジェクトの作成

とりあえず Bot Builder SDK のテンプレートにある EchoBot を土台にします。

f:id:okazuki:20190311173841p:plain

あ、余談ですが英語だと Stickerってなってるスタンプが日本語ドキュメントでステッカーとなってないあたりは、機械翻訳オンリーのドキュメントではなく、ちゃんとされてるドキュメントなんだなぁって思いました。

プロジェクトが作成されたら プロジェクト名Bot.csというファイルにエコーボットのロジックが書かれてるのでそこを編集します。

チャンネル固有のデータを突っ込むには Activity (Bot でやりとりするメッセージを表すクラス) を作って、そこの ChannelData プロパティに所定の JSON のオブジェクトを突っ込めばいいと書いてあります。 .NET で作る場合には JSON.NET の JObject を突っ込めば OK とあるのでさくっとやりましょう。

スタンプを送るためのメッセージは LINE のドキュメントによると typestickerを指定して packageIdstickerIdを指定してやればいいみたいです。

developers.line.biz

スタンプのリストも何故か PDF 形式で公開されてます(個人的には、普通に Web ページでいいのでは…と思いました)

https://developers.line.biz/media/messaging-api/sticker_list.pdf

ということで素直に実装すると…こんな感じ?

public async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken))
{
    // Handle Message activity type, which is the main activity type for shown within a conversational interface// Message activities may contain text, speech, interactive cards, and binary or unknown attachments.// see https://aka.ms/about-bot-activity-message to learn more about the message and other activity typesif (turnContext.Activity.Type == ActivityTypes.Message)
    {
        // Get the conversation state from the turn context.
        var state = await _accessors.CounterState.GetAsync(turnContext, () => new CounterState());

        // Bump the turn count for this conversation.
        state.TurnCount++;

        // Set the property using the accessor.
        await _accessors.CounterState.SetAsync(turnContext, state);

        // Save the new turn count into the conversation state.
        await _accessors.ConversationState.SaveChangesAsync(turnContext);

        if (turnContext.Activity.ChannelId == "line")
        {
            var reply = new Activity();
            reply.ChannelData = new JObject(
                new JProperty("type", "sticker"),
                new JProperty("packageId", "1"),
                new JProperty("stickerId", "1"));
        }
        else
        {
            var responseMessage = $"Turn {state.TurnCount}: You sent '{turnContext.Activity.Text}'\n";
            await turnContext.SendActivityAsync(responseMessage);
        }
    }
    else
    {
        await turnContext.SendActivityAsync($"{turnContext.Activity.Type} event detected");
    }
}

でも、

一応 ChannelIdが LINE のときだけスタンプにしました。

Azure で WebApp と Bot Channel Registration を作成して諸々設定します。ここらへんのデプロイ手順とかはドキュメント見るか、私の以下の記事とかが LINE に繋いだ時のものなので参考にしてください。

blog.okazuki.jp

動かしてみると…

f:id:okazuki:20190311183611p:plain

動いた!!因みに JObject で JSON くみたてるのだるいので試してみたら、普通にクラスを突っ込んでも JSON.NET でシリアライズしてくれるみたいでしたので、自分で JSON.NET で意図したとおりにシリアライズされるクラスを定義するか、以下のようにさくっとその場で匿名クラス作っても OK です。

if (turnContext.Activity.ChannelId == "line")
{
    var reply = turnContext.Activity.CreateReply();
    // これでも OK
    reply.ChannelData = new
    {
        type = "sticker",
        packageId = "1",
        stickerId = "1",
    };
    //reply.ChannelData = new JObject(//    new JProperty("type", "sticker"),//    new JProperty("packageId", "1"),//    new JProperty("stickerId", "1"));
    await turnContext.SendActivityAsync(reply);
}

もう一つくらいやってみよう

同じ要領で画像を出してみたいと思います。 先ほどの LINE Developers のドキュメントのスタンプの下に画像の場合の JSON がありました。

{"type": "image",
    "originalContentUrl": "https://example.com/original.jpg",
    "previewImageUrl": "https://example.com/preview.jpg"
}

簡単ですね。ということで Twitter の自分のヘッダー画像の URL をぺたっと貼るようにしてみました。

if (turnContext.Activity.ChannelId == "line")
{
    var reply = turnContext.Activity.CreateReply();
    reply.ChannelData = new
    {
        type = "image",
        originalContentUrl = "https://pbs.twimg.com/profile_banners/3272341/1552195235/1500x500",
        previewImageUrl = "https://pbs.twimg.com/profile_banners/3272341/1552195235/1500x500",
    };
    await turnContext.SendActivityAsync(reply);
}

動かしてみると…

f:id:okazuki:20190311184348p:plain

ばっちり!!

まとめ

ということで、Bot Framework SDK で提供されている ChannelData プロパティを使うとチャンネル固有の機能が使えます。 Flex とかにも対応しているみたいなので本格的な LINE ボットを使いたい人は是非見てみてね!!

Xamarin.Forms の Shell のプロジェクトテンプレート見てみた

$
0
0

VS 2019 RC で Xamarin.Forms プロジェクト作成しようとしたら Shell がありました。(前はなかったよね?

f:id:okazuki:20190313104523p:plain

ということで、プロジェクト新規作成して Shell ってどんなもの?っていうのを見てみようと思います。 最終的にはドキュメントもちゃんと見ないといけないけど、とりあえずどんなの?っていうのを見るときにはテンプレートが吐いてくれるコードを見るのも個人的には好きです。ということで、私が知らないプロジェクトを見るときに、どういった順番で見てるのか?とかっていうのも参考になればと思うので、このブログは普通に作業ログ的に書いていきます。

問題は、プロジェクトテンプレートの吐くコードが難解だったときですよね。例えば今回の場合は Shell の使い方を見たいって思ってるのに Shell 以外の部分がたくさんあるときです。

今回はそんなんじゃないといいなぁ。

ちなみに Shell は最新の Xamarin.Forms 3.6 時点でプレビュー機能なので、今後なにかしら変わる可能性があります。

作ってみよう

ということで Shell のテンプレートを作ってみます。MainActivity.cs(Android) や AppDelegate.cs(iOS) を見てみると以下のように実験的な機能のフラグが軒並みオンになってます。いいね。

global::Xamarin.Forms.Forms.SetFlags("Shell_Experimental", "Visual_Experimental", "CollectionView_Experimental", "FastRenderers_Experimental");

参照している Xamarin.Forms のバージョンを確認したら 3.6 の Pre 版だったので正式版アップデートしておきました。

f:id:okazuki:20190313105700p:plain

まずは全体を眺める

木を見る前に森を見ましょう。木を見ることが本当に目的の場合でも、目的の木がどこにあるのかは森全体を把握してないとたどり着くのに無駄に時間を食ってしまう可能性が高いです。

さまようことでも、新たな発見はあるのでさまようのが目的のときはいいけど、そうじゃないときは森をまず少しでもいいので見ましょう。

ということで .NET Standard のプロジェクトを開くと以下のようになってます。

f:id:okazuki:20190313110114p:plain

参照ライブラリは上記では見えてないですが確認したら Xamarin.Forms と Xamarin.Essentials だけです。その状態で MVVM パターンのソースコードを吐いているように見えます。

なので、このプロジェクトには MVVM を実現するために必要な基本的なクラスと、ロジックとかを担当するクラスが入り混じってるということになりそうです。

エントリーポイントの確認

次にエントリーポイントを見ます。Xamarin.Forms だと厳密なエントリーポイントは各プラットフォーム固有プロジェクトですが、Xamarin.Forms という単体で見たときには App.xaml.cs クラスなので、そこを見ましょう。

App クラスのコンストラクターで AppShell を MainPage にしてます。名前からして AppShelll クラスが今回目的の Shell っぽいですね。

public App()
{
    InitializeComponent();


    MainPage = new AppShell();
}

一応念のため App.xaml のほうも見て全体に影響するような仕組みが入ってないか確認します。特になさそうでした。

AppShell を確認

AppShell.xaml.cs を開いてみると単なる Shell を継承したクラスでした。

using System;
using System.Collections.Generic;

using Xamarin.Forms;

namespace MyXamarinFormsApp
{
    publicpartialclass AppShell : Xamarin.Forms.Shell
    {
        public AppShell()
        {
            InitializeComponent();
        }
    }
}

拍子抜けするくらいに何もないですね!AppShell.xaml を開きます。 AA(アスキーアート) 見るとは思わなかった

f:id:okazuki:20190313110949p:plain

そこで簡単な Shell の説明してくれてます。ありがたい。

Shell is an all new way to quickly get started with your application. There are 3 levels to a Shell app:
* ShellItem: populates the Flyout menu along with special menu items
* ShellSection: groups of 1 or more ContentPage displayed as bottom tabs
* ShellContent: a ContentPage host. Multiple ShellContent within a ShellSection are navigable by top tabs

For more details about building apps with Shell, visit these resources:
* MSDN Article: https://aka.ms/xf-msdn
* Preview Documentation: https://aka.ms/xf-shell-docs
* Blog Introduction: https://aka.ms/xf-40-blog

PLEASE give us feedback on your experience: good, bad, and ugly.
https://www.surveymonkey.com/r/VTJNWTM

少し下に下がるとリソースでスタイル定義してます。

<Shell.Resources><ResourceDictionary><Color x:Key="NavigationPrimary">#2196F3</Color><Style x:Key="BaseStyle"TargetType="Element"><Setter Property="Shell.ShellBackgroundColor"Value="{StaticResource NavigationPrimary}" /><Setter Property="Shell.ShellForegroundColor"Value="White" /><Setter Property="Shell.ShellTitleColor"Value="White" /><Setter Property="Shell.ShellDisabledColor"Value="#B4FFFFFF" /><Setter Property="Shell.ShellUnselectedColor"Value="#95FFFFFF" /><Setter Property="Shell.ShellTabBarBackgroundColor"Value="{StaticResource NavigationPrimary}" /><Setter Property="Shell.ShellTabBarForegroundColor"Value="White"/><Setter Property="Shell.ShellTabBarUnselectedColor"Value="#95FFFFFF"/><Setter Property="Shell.ShellTabBarTitleColor"Value="White"/></Style><Style TargetType="ShellItem"BasedOn="{StaticResource BaseStyle}" /></ResourceDictionary></Shell.Resources>

ShellItem のみ見た目定義かな…

その下に ShellItem, ShellSection, ShellContent の定義があります。あとはコメントで親切に各種見た目のテンプレートの例が書いてありました。

<!-- Your Pages --><ShellItem><ShellSection Title="Browse"Icon="tab_feed.png"><ShellContent ContentTemplate="{DataTemplate local:ItemsPage}" /></ShellSection><ShellSection Title="About"Icon="tab_about.png"><ShellContent ContentTemplate="{DataTemplate local:AboutPage}" /></ShellSection></ShellItem><!-- Optional Templates // These may be provided inline as below or as separate classes.// This header appears at the top of the Flyout.<Shell.FlyoutHeader><DataTemplate><Grid>ContentHere</Grid></DataTemplate></Shell.FlyoutHeader>// ItemTemplate is for ShellItems as displayed in a Flyout<Shell.ItemTemplate><DataTemplate><ContentView>            Bindable Properties: Title, Icon</ContentView></DataTemplate></Shell.ItemTemplate>// MenuItemTemplate is for MenuItems as displayed in a Flyout<Shell.MenuItemTemplate><DataTemplate><ContentView>            Bindable Properties: Text, Icon</ContentView></DataTemplate></Shell.MenuItemTemplate>

動きを見てみよう

なんとなく Shell に関するコードはここらへんまでかなぁ?と思ったので動かしてみます。

起動するとこんな画面になりました。

f:id:okazuki:20190313113331p:plain

なるほど、ShellSection が下にボタンで出て ShellContent がページですね。

AppShell の XAML を見てみると以下のようなプロパティが設定されてます。

<Shell xmlns="http://xamarin.com/schemas/2014/forms"xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"xmlns:local="clr-namespace:MyXamarinFormsApp.Views"RouteHost="companyname.com"RouteScheme="app"Route="MyXamarinFormsApp"FlyoutBehavior="Disabled"Title="MyXamarinFormsApp"x:Class="MyXamarinFormsApp.AppShell">

コメントには Flyout がどうのって書いてありましたが Disable されてますね。FlyoutBehavior を Flyout にして、ShellItem を複数定義してみました。

<!-- FlyoutBehavior を Flyout にして ShellItem を増殖させてみた。Title プロパティもあったのでつけてみた --><ShellItem Title="A"><ShellSection Title="Browse"Icon="tab_feed.png"><ShellContent ContentTemplate="{DataTemplate local:ItemsPage}" /></ShellSection><ShellSection Title="About"Icon="tab_about.png"><ShellContent ContentTemplate="{DataTemplate local:AboutPage}" /></ShellSection></ShellItem><ShellItem Title="B"><ShellSection Title="Browse"Icon="tab_feed.png"><ShellContent ContentTemplate="{DataTemplate local:ItemsPage}" /></ShellSection><ShellSection Title="About"Icon="tab_about.png"><ShellContent ContentTemplate="{DataTemplate local:AboutPage}" /></ShellSection></ShellItem><ShellItem Title="C"><ShellSection Title="Browse"Icon="tab_feed.png"><ShellContent ContentTemplate="{DataTemplate local:ItemsPage}" /></ShellSection><ShellSection Title="About"Icon="tab_about.png"><ShellContent ContentTemplate="{DataTemplate local:AboutPage}" /></ShellSection></ShellItem>

こうするとページの左上にハンバーガーメニューが表示されて…

f:id:okazuki:20190313114232p:plain

こんな感じで画面切り替えが出来ました。

f:id:okazuki:20190313114301p:plain

いいね。

気になるところが…

ShellContent の中のページが ContentTemplate で {DataTemplate local:AboutPage}のように指定してるところです。

Content が無いのだろうか?と思ってこうしてみました。

<ShellItem Title="C"><ShellSection Title="Browse"Icon="tab_feed.png"><ShellContent><local:ItemsPage /></ShellContent></ShellSection><ShellSection Title="About"Icon="tab_about.png"><ShellContent><local:AboutPage /></ShellContent></ShellSection></ShellItem>

実行してみても普通に動きました。タイプ数が少ないから、DataTemplate 使ってるのかな?

Shell 内の画面遷移

ItemsPage で項目をクリックすると以下のような画面に遷移します。

f:id:okazuki:20190313114857p:plain

ListView を見てみると OnItemSelected イベントハンドラーで処理をしてるみたいなので確認してみます。

<ListView x:Name="ItemsListView"ItemsSource="{Binding Items}"VerticalOptions="FillAndExpand"HasUnevenRows="true"RefreshCommand="{Binding LoadItemsCommand}"IsPullToRefreshEnabled="true"IsRefreshing="{Binding IsBusy, Mode=OneWay}"CachingStrategy="RecycleElement"ItemSelected="OnItemSelected">

チェックしてみると普通に PushAsync してるだけでした。

async void OnItemSelected(object sender, SelectedItemChangedEventArgs args)
{
    var item = args.SelectedItem as Item;
    if (item == null)
        return;

    await Navigation.PushAsync(new ItemDetailPage(new ItemDetailViewModel(item)));

    // Manually deselect item.
    ItemsListView.SelectedItem = null;
}

右上の ADD を押すと以下のように Shell とは別の感じの画面が出てくるのですが

f:id:okazuki:20190313115242p:plain

こちらについては、単に PushModalAsyncでした。

async void AddItem_Clicked(object sender, EventArgs e)
{
    await Navigation.PushModalAsync(new NavigationPage(new NewItemPage()));
}

ついでに NavigationPage でラップしてますね。NavigationPage をとっぱらってみると以下のようになって、戻れませんね…

f:id:okazuki:20190313115436p:plain

なので NavigationPage でラップしてたのはタイトル出すのと、ToolbarItems を使うためっぽいです。

ついでに NewItemPage.xaml.cs では MessageCenter で AddItem というメッセージを飛ばしてます。

async void Save_Clicked(object sender, EventArgs e)
{
    MessagingCenter.Send(this, "AddItem", Item);
    await Navigation.PopModalAsync();
}
``

これを受け取ってるのは ItemsViewModel.cs でした。

public ItemsViewModel() { Title = "Browse"; Items = new ObservableCollection(); LoadItemsCommand = new Command(async () => await ExecuteLoadItemsCommand());

MessagingCenter.Subscribe<NewItemPage, Item>(this, "AddItem", async (obj, item) =>
{
    var newItem = item as Item;
    Items.Add(newItem);
    await DataStore.AddItemAsync(newItem);
});

}

ここで使ってる DataStore は BaseViewModel.cs で以下のように宣言されていて、今回のコードでは MockDataStore.cs のインスタンスが返される。(どこにも DependencyService を初期化してるようなコードは見当たらないので)

MockDataSource クラスは特にデータを static に管理してる風には見えないので実際には `Items.Add` で見た目上は追加されてるけど特に裏では永続化されてないのでリフレッシュしたらデータは消えちゃうみたいですね。


## まとめ

Shell で提供される形の仕様ですむように落とし込めたら、これは強そうだと思った!楽できそう。

Azure Bot Service で Microsoft Teams のボットにボタンで選択肢を出したい

$
0
0

これでハマったのでメモ。 もし、もっと楽な方法があったら教えてください。

やりたかったこと

以下のようなメッセージを生成したかった。

xxxxですか?
[ はい ] [ いいえ ]

質問に対して「はい」か「いいえ」をボタンで出したかった。

最初にやったこと

ConfirmPrompt の出番やろ。ということでサンプルの下のような感じで作ってみました。

BotBuilder-Samples/MultiTurnPromptsBot.cs at master · Microsoft/BotBuilder-Samples · GitHub

BotBuilder-Samples/MultiTurnPromptsBot.cs at master · Microsoft/BotBuilder-Samples · GitHub

エミュレーターで確認すると「はい」と「いいえ」のボタンが出てめでたしめでたし…だったのに Teams に繋いでみると

xxxxですか?
(1) Yes, (2) No

と出て数字をユーザーが手入力するものになってしまいました…なんでや…。

次にやったこと

それなら自分で好きな UI 組み立てて見せる!と思って AdapriveCards を試してみました。

adaptivecards.io

ここらへんで JSON 組み立ててサクッと完了。

actions に Action.Submitを指定しておいたらエミュレーターでは動いたんですが…。Teams に繋ぐとボタンを押しても反応なし。残念。(深追いはしてません)

最終的にやったこと

QnA Maker の ActiveLearning のサンプルボットが質問に対して曖昧な回答が複数あると選択肢を出すように動いていたので該当部分をチェックしてみると HeroCard を使ってました。

BotBuilder-Samples/CardHelper.cs at master · Microsoft/BotBuilder-Samples · GitHub

ということで最終的には以下のような Prompt を作成して ConfirmPrompt と差し替えて対応しました。

publicclass FeedbackPrompt : Prompt<bool>
{
    public FeedbackPrompt(string dialogId) : base(dialogId)
    {

    }

    protectedoverride async Task OnPromptAsync(ITurnContext turnContext, IDictionary<string, object> state, PromptOptions options, bool isRetry, CancellationToken cancellationToken = default(CancellationToken))
    {
        var reply = turnContext.Activity.CreateReply();
        reply.Attachments = new List<Attachment>
        {
            new HeroCard
            {
                Text = Messages.AskingFeedbackMessage,
                Buttons = new List<CardAction>
                {
                    new CardAction { Title = Messages.Yes, Type = "imBack", Value = Messages.Yes },
                    new CardAction { Title = Messages.No, Type = "imBack", Value = Messages.No },
                },
            }.ToAttachment(),
        };

        await turnContext.SendActivityAsync(reply, cancellationToken);
    }

    protectedoverride Task<PromptRecognizerResult<bool>> OnRecognizeAsync(ITurnContext turnContext, IDictionary<string, object> state, PromptOptions options, CancellationToken cancellationToken = default(CancellationToken))
    {
        switch (Messages.RemoveTag(turnContext.Activity.Text.Trim()))
        {
            case Messages.Yes:
                return Task.FromResult(new PromptRecognizerResult<bool>
                {
                    Succeeded = true,
                    Value = true,
                });
            case Messages.No:
                return Task.FromResult(new PromptRecognizerResult<bool>
                {
                    Succeeded = true,
                    Value = false,
                });
            default:
                return Task.FromResult(new PromptRecognizerResult<bool>
                {
                    Succeeded = false,
                });
        }
    }
}

まとめ

Bot Framework Service の Teams 対応部分が、もうちょっと頑張って各種 Prompt の見た目それっぽくしてくれたらうれしいなぁと思った今日この頃でした。

自分が見逃してるだけで何かいい感じに見せるオプションとかあるのかな?

Visual Studio Code で快適 git 生活

$
0
0

今まで Visual Studio Code の git サポート機能ちゃんと使ってなかったのですが使ってみたら便利だったので使い方のメモ。

基本的にコマンドパレットからやります。 コマンドパレットは Windows の場合は Ctrl + Shift + Pで、macOS の場合は Command + Shift + Pになります。 コマンドパレットを開いたら、例えばリポジトリーの初期化したいな…と思ったらそのままおもむろに git initと打ち込むと該当機能が出てきます。

f:id:okazuki:20190326165543p:plain

正直、これだと VS Code でターミナル開いて git initうつのと変わらないのですが、このコマンドパレットからやると、確実にその機能があるということが確証が取れた状態で実行できます。

コミットもコマンドパレットで git commitと入れるとコミットしてくれる選択肢が出ます。

f:id:okazuki:20190326165851p:plain

個人的にポイント高いのは、ファイルが保存されてないと保存してくれるところです。 保存してないファイルがあると、こういうダイアログが出ます。

f:id:okazuki:20190326170206p:plain

これはターミナルで何も考えずに git add --allして git commit -m "xxx"とか git commit -a -m "xxx" みたいにしたときに、保存されてないファイルがあると「やってしまった」ってなることが個人的にあるので助かります。

あとは git pull / git pushをしてくれる git syncとかもあっていいです。

f:id:okazuki:20190326170531p:plain

コマンドパレットで git logで探すとログもいい感じに出してくれます。

f:id:okazuki:20190326170801p:plain

まとめ

とりあえずコマンドパレットで git って打ち込んで何が出来るのか眺めておくといいと思います。

因みに、もっと詳しい内容が、今月の Software Design の VS Code 特集にのってるのでお勧め。

gihyo.jp

やっぱり、コマンドパレットからやると最終的に選択肢から選ぶことになるので、typo の心配が少なくて済むので快適です。

Visual Studio でコードを .editorconfig に従って一括フォーマット

$
0
0

Visual Studio 2019 のリリースノートにしれっと入ってる dotnet-formatコマンド。

github.com

このコマンド一発でインストールできるのが素敵です。

dotnet tool install -g dotnet-format

パッケージマネージャーコンソールか立ち上げて dotnet-format って打つだけなのですが、パッケージマネージャーコンソールの起動に若干時間がかかるので Visual Studio のツールの外部ツールに以下のように登録しておくとちょっと捗ります。

f:id:okazuki:20190403130119j:plain

  • Title Format all codes
  • Command dotnet-format.exe
  • Arguments -w $(SolutionDir)\$(SolutionFileName)

Use Output window とかあたりにもチェック入れておきます。

これで、ツールメニューの Format all codes をクリックするだけで、ソリューション一括フォーマットが出来ます。

あんまり使わないかな?でもとりあえず登録してみました。

Viewing all 1388 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>