#PowerApps カスタムコネクタのC#カスタムコードで遊んでみる

# 概要
- Power Apps/AutomateのカスタムコネクタでC#のコードを使って独自のビジネスロジックを実装できるようになりました。
- コードにより、外部のAPIに要求する際に認証情報を付加したり、APIからの応答内容を変換したり、外部APIには要求せずカスタムコード内で処理して応答するといったことも可能です。
- 以前はAzure Functionsで実装したような処理をカスタムコネクタ内で完結することが可能になりました。

※現在はプレビュー段階です
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/293667/7708d25d-709d-4bfa-0c49-e0617d6ad47e.png)


# やってみた事例
**１．Power Appsからテキストを受け取ってオウム返しする。(本記事)**

**２．複雑な認証を必要とするAPIに対してコードで認証情報を生成し付加する**

https://qiita.com/Rambosan/items/4fcc5b3055c86403f652

https://qiita.com/Rambosan/items/ea62d7767b58b152c842

**３．Power Appsから画像を受け取り簡単な画像処理を行ってPower Appsに応答する。**

https://qiita.com/Rambosan/items/5e093e5d111f8c07bb12


# 制約条件など
カスタムコードには以下のような制約事項がありできることが限られます。
C#の中でも比較的簡単なコードしか書けないということで、これも**ローコード**かもしれません(違

- コードは全てScriptクラス内に書く必要がある
- 使用可能なクラスライブラリに制限がある
- 5 秒以内に実行を終了、コードサイズが1MBまでなどの制限がある
- 呼び出し初回に数秒の起動時間あり、裏側はAzure Functions無料枠？

https://docs.microsoft.com/ja-jp/connectors/custom-connectors/write-code#custom-code-faq

それからカスタムコネクタなので、PowerAppsの開発者環境か有償プランが必要です。


# 試しに作ってみる
## 今回のコード
- リクエストボディに入力されたテキストをそのまま返す。
- 追加でContextやRequestの内容を確認用に表示する。
- カスタムコネクタの登録時にバックエンドAPIのURLを登録しますが、今回の例ではバックエンドにはアクセスしません。処理内容はコードでオーバーライドします。

**コード**

```CSharp:
    public class Script: ScriptBase {
        
        public override async Task<HttpResponseMessage> ExecuteAsync() {
            // Create a new response
            var response = new HttpResponseMessage();

            var contentAsJson = JObject.Parse(await Context.Request.Content.ReadAsStringAsync().ConfigureAwait(false));

            // Set the content
            // Initialize a new JObject and call .ToString() to get the serialized JSON
            response.Content = CreateJsonContent(new JObject {
                ["message"] = (string)contentAsJson["message"],
                ["request_uri"] = Context.Request.RequestUri,
                ["operation_id"] = Context.OperationId,
                ["correlation_id"] = Context.CorrelationId,
                ["content"] = await Context.Request.Content.ReadAsStringAsync()
            }.ToString());

            return response;
        }


    }
```

**簡単な解説**
コードには、ScriptBaseクラスを継承したScriptクラスがあり、カスタムコネクタが呼び出されるとExecuteAsyncメソッドが実行されます。
ExecuteAsyncはoverrideしているため、カスタムコネクタの処理内容はこのメソッド内のコードに上書きされます。

ContextやCreateJsonContentなどが登場しますが、これはベースクラスで定義されているものです。
Contextにはカスタムコネクタの情報やリクエスト情報が格納されています。
https://docs.microsoft.com/ja-jp/connectors/custom-connectors/write-code#definition-of-supporting-classes-and-interfaces

以下に開発環境を用意しました。

# カスタムコードの開発環境
現状はカスタムコネクタ上でコードを書いてもコンパイルエラーの内容を教えてくれないため、公式に従い自分で開発環境を整えます。

今回はVisualStudioでテスト環境を整えてみます。
方法は以下にのブログに投稿しました。

https://rambosan.hatenablog.com/entry/2021/08/17/235149

またはこちらのKUSOリポジトリをクローンで

https://github.com/Rambosan/CustomCodeTestEnv


# カスタムコネクタの作成

カスタムコネクタの基本操作についてはこちらが詳しいです。

https://qiita.com/MiyakeMito/items/800018d7642c0e4e86f3

https://www.youtube.com/watch?v=APNjMks5tRw


1. カスタムコネクタの画面で一から作成を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/293667/9ac03743-0c3a-b662-0d26-8e319a15f674.png)

2. 全般画面
ホストの部分は架空の「api.contoso.com」としておきます。
今回は実際のAPIは呼び出さずC#内で処理を行って応答しますが、形式上指定する必要があるためです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/293667/733bcc95-50ab-0866-bdd0-2764ea068970.png)
※設定保存のやり方によってはC#コードが無効になり入力したURLにリクエストされるため、送信されてもいいURLで。

3. セキュリティ
→無しでOKです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/293667/360c26f5-cc34-9258-2393-f08e3f3f3ddd.png)

4. 定義画面
新しいアクションを追加し「Hello」などのアクションを作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/293667/57b49b43-99f7-5a72-aec4-709173bff4cc.png)

要求内容はサンプルからのインポートで以下のように設定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/293667/85054172-6b94-c4f8-cc38-b28a1624f339.png)

5.コード画面
コードが有効をオンにして、作成したコードを貼り付けます。
特定のアクションのみコードを有効にできるようですが、今回は指定しません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/293667/a1af6399-3302-17c0-7181-c89e86fcabc9.png)

ここで必ずコネクタの更新をクリックします。
カスタムコードを有効にしていますと表示され、その後正常に保存できたことを確認します。
現状、他の画面でコネクタの更新を行ってしまうと、コードが無効になり指定のURIに要求されてしまいます。
なので最後は必ずこの画面で、一旦コードを編集してからコネクタの更新を行います。
※コードを編集せず更新をすると反映されない場合あり
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/293667/fa293c2b-cbff-ddff-f4ce-98e10c738751.png)

もしコードが反映されていないとテストのときに失敗し、api.contoso.comの名前解決ができないと表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/293667/8b6f68a1-9337-7af8-05cc-8eb7d402288a.png)


6.テスト画面
接続が作成されていない場合は接続を作成。
テストの前には上記の通り、最後はコード画面で編集し、コネクタの更新をクリックしてコードを反映させます。

パラメータを適当に設定して、テスト操作します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/293667/562c7ced-1244-1a3f-587d-46e473330722.png)

以下のように応答が返ってくれば成功です。
OperationIdは操作IDが入るようです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/293667/bf90d55f-5345-4828-80da-162c47413937.png)

7.応答の定義
先ほどのボディをコピーし、応答部分にインポートして定義を作成します。
最後は必ずコード画面でコードを編集して更新します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/293667/853e1c2d-e70e-2845-d5da-9b0e931d3c32.png)

# PowerAppsからの呼び出し
アプリを作成し、先ほど作成したカスタムコネクタへの接続を作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/293667/2032c9ac-d65d-1d88-1fc3-c4e12f0986cb.png)

以下のような感じで呼び出すと応答結果が得られました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/293667/ceea6ee4-7838-45ee-725d-d6331b1632d7.png)



以下その他
# クラスを利用する
Script以外にClassを書いて呼び出してもエラーとなります。
読みにくいですがClassの入れ子なら利用可能です。以下はRequestにあるようなJSONをクラスにデシリアライズする例です。

```json:Request
{
"SurName":"Tomioka",
"GivenName":"Giyu"
}
```


```CSharp:Custom Code
    public class Script : ScriptBase {

        public override async Task<HttpResponseMessage> ExecuteAsync() {

            // Parse request body
            var entity = JsonConvert.DeserializeObject<PersonEntity>(await Context.Request.Content.ReadAsStringAsync().ConfigureAwait(false));

            // Create a new response
            var response = new HttpResponseMessage();

            response.Content = CreateJsonContent(new JObject {
                ["fullname"] = entity.FullName
            }.ToString());

            return response;
        }

        class PersonEntity {
            public string SurName { get;}
            public string GivenName { get;}

            public string FullName =>
                $"{SurName} {GivenName}";
 
            [JsonConstructor]
            public PersonEntity(string surname, string givenname) {
                SurName = surname;
                GivenName = givenname;
            }
        }
    }
```

# APIを呼び出して応答を変形する(情報を削減)
QiitaAPIを呼び出し、渡されたワードで記事を検索して結果を返す例です。
標準の応答スキーマに不要の情報がありPower Appsで扱いにくいといった場合に、カスタムコネクタ側で応答を加工し情報を削減します。
QiitaAPIの呼び出し定義はカスタムコネクタ側で設定します。

要求の例
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/293667/6369679b-c6f1-3a18-3d5a-e396cae623ee.png)

情報を削減した応答
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/293667/e461f040-0f74-267d-75fb-95888948315a.png)

```CSharp:
    public class Script : ScriptBase {

        public override async Task<HttpResponseMessage> ExecuteAsync() {

            // カスタムコネクタで定義したAPIを呼び出し
            var responseMessage = await Context.SendAsync(Context.Request, CancellationToken).ConfigureAwait(false);

            if (responseMessage.StatusCode !=HttpStatusCode.OK ) {
                throw new ArgumentException(responseMessage.ReasonPhrase);
            }

            // レスポンスのコンテンツを取得
            var requestBody = await responseMessage.Content.ReadAsStringAsync();
            // Parse request body
            var entities = JsonConvert.DeserializeObject<List<QiitaEntity>>(requestBody);

            // Create a new response
            var response = new HttpResponseMessage();

            response.Content = CreateJsonContent(JsonConvert.SerializeObject(entities));

            return response;
        }

        //特定のフィールドしか用意しないことで不要なフィールドを削除
        class QiitaEntity {

            public string Title { get; private set; }
            public string Body { get; private set; }
            public DateTime UpdateAt { get; private set; }
            public int LikesCount { get; private set; }
            public string Url { get; private set; }

            [JsonConstructor]
            public QiitaEntity(string title, string body, DateTime updated_at, int likes_count, string url) {

                Title = title;
                var singleLineBody = Regex.Replace(body, @"\t|\n|\r", ""); 
                Body = SubstringSafe(singleLineBody, 0, 100);
                UpdateAt = updated_at;
                LikesCount = likes_count;
                Url = url;
            }

            static string SubstringSafe(string value, int startIndex, int length) {
                return new string((value ?? string.Empty).Skip(startIndex).Take(length).ToArray());
            }
        }

    }
```

# あとがき
カスタムコネクタ内で処理を完結させるとPower Appsの料金だけでよいですし、リソース管理が楽になりますね。
色々な活用の可能性がありそうです。


Power Appsにない関数をカスタムコードで実現！と思いましたが、頑張ればPower Appsでもかなりのことができるようですので、カスタムコードでしかできないことが思いつきませんね・・

カスタムコネクタで標準対応していない認証方法のAPIへの接続や、
画像リサイズなどの単純な画像処理なら活用できそうです。


次回の予定
画像処理の例

https://qiita.com/Rambosan/items/5e093e5d111f8c07bb12

カスタムコネクタでOAuth1.0のAPIに接続してみた

https://qiita.com/Rambosan/items/4fcc5b3055c86403f652