UnityのNCMBでWhereContainedIn等が使えない問題と解決案

## 2020/10/26追記
ニフクラ公式ブログに、この記事で扱っている不具合に関する記事がありましたので、こちらに記載しておきます。この記事の変更2が採用されています。まだSDKには反映されてなさそうなので、まだ適宜修正する必要がありそうです。
https://blog.mbaas.nifcloud.com/entry/2020/07/29/133909

# はじめに
Unityでニフクラmobile backendを使用していたら、データストアの取得をする際、リストの要素のうちどれかを含む要素を取得する`WhereContainedIn`が使えませんでした。

### 実行環境

* Windows10
* Unity2018.3.8f1
* ncmb_unity SDK : ver4.0.0
* Scripting Runtime Version : .NET 4.x __（実はこれが重要でした）__

``` C#:NCMBTest.cs（一部）
List<int> findNumList = new List<int> { 21, 30 };

query.WhereContainedIn("old", findNumList);

query.FindAsync((List<NCMBObject> objList, NCMBException e) =>
{
     if (e != null)
     {
           Debug.Log("取得不可");
           Debug.Log(e.Message);
     }
     else
     {
           Debug.Log("取得可");
           Debug.Log(objList[0].ObjectId);
     }
});
```

``` text:エラー内容
【StatusCode】:403
【Error】:NCMB.NCMBException: Unauthorized operations for signature.
```

# 試したこと
### 1.使える関数の把握
まず、他の関数が使えるかどうかをためしたところ、使えたものと使えなかったものがありました。

| 使えた | 使えなかった |
|:-:|:-:|
| WhereEqualTo | WhereContainedIn |
| WhereGreaterThan | WhereContainedAll |

これらにより生成されるURLを確認したところ、`[`や`]`が含まれていました。

### 2.エラー検索
403エラーとそのメッセージについて調べたところ、[公式リファレンス](https://mbaas.nifcloud.com/doc/current/rest/common/error.html)には以下のように書かれていました。
> Unauthorized operations for {0}.
> コラボレータ/管理者（サポート）権限なし

これについてさらに調べたところ、シグネチャが正しく生成されていない場合にもこのエラーになるそうです。
シグネチャは、APIを送信するときに正しいデータの形をしているか検証するための文字列です（と認識していますが雑な解釈なので参考にしないでください）。

### 3.Unityの別バージョンでの検証
もともと知り合いが発見したエラーだったので、ここまでは知り合いに遠隔で指示を出しつつ調査をしていました。
すると、知り合いが「Unity2017で動いた」と言ってきたので、暇を作って自分で検証してみることにしました。

上記の環境を最低限そろえたテスト用のプロジェクトを2018向けに作成し、うまくいかないことを確かめたのち、強制的にバージョンを2017.2.0p4に下げ、実行しましたが、自分の環境ではうまくいきませんでした。

### 4.NCMBのソースコードの調査
自分の環境を作ったので、いろいろ試しつつ考えた結果、URLの内容などがシグネチャの生成に関与しているため、URLの生成が怪しそうだと思い、URLのエンコードをしている部分を見ていきました。
すると、以下のような行がありました。

```C#:NCMBQuery.cs(772行目付近)
string encodeJsonValue = Uri.EscapeUriString (jsonValue);//JSON化された値をエンコードされた文字列
encodeJsonValue = encodeJsonValue.Replace (":", "%3A");
```
おそらく、ここでクエリ部分をエンコードしているようです。
`Uri.EscapeUriString`はC#が用意している関数です。

### 5. .NETのバージョンを古くしてみる
Unityの2017と2018で異なる可能性の一つとして、.NETのバージョンがあたらしくなったことがあげられます。
試しに自分の2017環境でのバージョンを確認したところ、2018から強制的に下げたため、バージョンが4.6になっていました。
これを、3.5に変更したところ、正しくデータストアから取得することができました。

# 原因
.NET4.0以前と.NET4.5以降で、`Uri.EscapeUriString`の仕様が変更されていたようで、`[`や`]`は変換対象から除外されたようです。
なんでもRFC3986の予約文字になっているようで、Uriとして変換されなくても認識されるようになったのかもしれません（詳しくないので優しくしてください）
（参考）https://dobon.net/vb/dotnet/internet/urlencode.html

おそらく、いままでは正しくエンコードされていたんですが、.NET4.xになったことで、`[`や`]`がエンコードされなくなり、別の文字としてサーバー側で認識され、シグネチャが間違っている、と解釈されたのかもしれません。

# 解決案
自分はNCMBQueryを変更しました。

```C#:NCMBQuery.cs(変更1)
    string encodeJsonValue = Uri.EscapeUriString (jsonValue);//JSON化された値をエンコードされた文字列
    encodeJsonValue = encodeJsonValue.Replace (":", "%3A");
+++ encodeJsonValue = encodeJsonValue.Replace("[", "%5B");
+++ encodeJsonValue = encodeJsonValue.Replace("]", "%5D");
```

こうすることで変更されなかった文字を`:`と同様に変換するようにしています。

他に、例外なくすべてエンコードする関数`Uri.EscapeDataString`があるので、それを使えば少し変えるよさそうですが、NCMB側でエンコードしてほしくない文字が存在する可能性があるので、使うべきでは無いかもしれません。

```C#:NCMBQuery.cs(変更2)
//  string encodeJsonValue = Uri.EscapeUriString (jsonValue);//JSON化された値をエンコードされた文字列
+++ string encodeJsonValue = Uri.EscapeDataString (jsonValue);//JSON化された値をエンコードされた文字列
    encodeJsonValue = encodeJsonValue.Replace (":", "%3A");
```

~~一番平和なのは、Unity2017系で.NET3.5のまま動かすことかもしれません~~

# 最後に
これはSDKのバグ、ということでいいのでしょうか...？
NCMB側で問題としてあがっていればよいのですが、GitHubのissuesに投稿する勇気が無いので、Qiitaに投稿しました。
ほかにこの問題が起きている人がいたら参考にしてください。
もっといい解決方法があるか、すでに解決されている問題なのであれば教えてください。