Autodesk Inventor API Hacking (Entitlement API)

# 0. はじめに
[AppStore](https://apps.autodesk.com/INVNTOR/ja/Home/Index)に有料AddInを公開する場合に使える、購入済みユーザーかどうかを確認する仕組みをAutodeskが提供しています。それをEntitlement APIと呼びます。
実際にcodeを書くにあたっての注意点をまとめます。

# 1. 大まかな流れ
権限確認は、次のフローで行います。

1. 前提として、AppStoreに登録してApplication IDを取得しておく。
2. CWebServicesManagerを使って、UserIdを取得する。
3. Application IDとUserIdをAutodeskのServerに送る(REST)。
4. 権限情報(json)が返ってくる。

# 2. Application IDの取得
このIDは、AppStoreに登録すると発行されます。多くの場合は、このIDをAddInにハードコードすることでしょう。
「登録するAddInに『登録すると発行されるID』をハードコードするなら、デッドロック(鶏と卵)じゃないか!」と思われた方は、正しいです。
厳密には、登録完了しなくても、下書き状態でIDが発行されるので、それを組み込んで提出してください。
![AppStore.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/424028/3ddc51c2-ce24-9989-00f3-6977edfba7a1.png)
上図の赤枠で囲った部分が、Application IDです。ステータスが不完全でも発行されています。

# 3. CWebServicesManagerを使う
# 3.1 これは何をするもの?
Autodesk Accountのユーザー名は後から変更される可能性があるので、Autodesk内では一意のUserIdがAccountに対して割り当てられているようです。
AutodeskにServerに権限確認するには、このUserIdが必要なので、CWebServicesManagerを使って取得します。
#3.2 CWebServicesManagerはどこにあるの?

```
<Inventorのinstall場所>\Bin\AddinNETFramework.AdWebServicesWrapper.dll
```
が実体です。
# 3.3 アセンブリが署名されていない!!
非常に残念なことに、`AddinNETFramework.AdWebServicesWrapper.dll`は署名されていません。ですので、AddIn自体に署名するならば、参照することが出来ません。
(署名されたアセンブリから、署名がないアセンブリを参照できない)

こういった場合は、次の2つの解決方法があります。
1. 自分で勝手に署名する。
2. 実行時にDLLを指定して動的Loadする。
(3. 開発元に署名してもらう)

もちろん3.がお勧めなのですが、現実的ではないので「2つの解決方法」としました。
ここでは、2.の方法を取ります。
# 3.4 アセンブリの動的Load
実行時にLoadすると、署名されてなくても警告もでないので、この方法で行きます。Load済みのDLLを再Load(重複Load)すると異常な挙動を取らないか心配でしたが、これは問題がありませんでした。
実際のcodeは以下の通りです。

```c#
/// <summary>
/// Autodesk User Idを取得します。
/// </summary>
/// <returns>取得に成功すれば、true。</returns>
private bool TryGetUserInformation()
{
    try
    {
        var cwsmAsm = System.Reflection.Assembly.LoadFrom(InventorApplication.InstallPath + @"\Bin\AddinNETFramework.AdWebServicesWrapper.dll");
        var typeInfo = cwsmAsm.GetType("Autodesk.WebServices.CWebServicesManager");
        using dynamic mgr = Activator.CreateInstance(typeInfo);
        bool isInitialized = mgr.Initialize();

        if (isInitialized)
        {
            InventorApplication.Login();
            string userId = "";
            mgr.GetUserId(ref userId);
            UserId = userId;
            string userName = "";
            mgr.GetLoginUserName(ref userName);
            UserName = userName;
        }
    }
    catch
    {
        return false;
    }

    if (!string.IsNullOrWhiteSpace(UserId))
    {
        return true;
    }
    else
    {
        return false;
    }
}

```
注意点として、Autodesk AccountにLoginしていない状態では、`GetUserId()`が空文字列を返すことです。ですので、本当に取得できたかどうかはUserIdの中身を確認する必要があります。

# 4. Autodeskの権限Serverとの通信
この内容は、RESTfulやjsonで検索すると優秀な情報があるでしょうから、最終codeを示しておきます。

```c#:GetEntitlement()
/// <summary>
/// Autodeskのサーバーに権限を問い合わせます。
/// </summary>
/// <returns></returns>
private async Task GetEntitlement()
{
    try
    {
        // 通信設定、データの下準備
        var parameters = new Dictionary<string, string>()
        {
            { "userid", UserId },
            { "appid", AppId },
        };
        var handler = new HttpClientHandler()
        {
            Proxy = WebRequest.GetSystemWebProxy(), // システムのProxyを使う
        };
        using var client = new HttpClient(handler)
        {
            Timeout = TimeSpan.FromSeconds(timeOut), // TimeOut秒数
        };
        client.DefaultRequestHeaders.ConnectionClose = true;    // KeepAliveしない

        // 通信する
        var response = await client.GetAsync($"https://apps.autodesk.com/webservices/checkentitlement?{await new FormUrlEncodedContent(parameters).ReadAsStringAsync()}");
        var st = await response.Content.ReadAsStreamAsync();

        if (response.StatusCode != HttpStatusCode.OK)
        {
            SetResult(Result.HttpStatusCodeIsNotOk, $"{response.ReasonPhrase} ({(int)response.StatusCode})");
            return;
        }

        // 得られたjsonを解析する
        var serializer = new DataContractJsonSerializer(typeof(ServerResponse));
        var serverResponse = (ServerResponse)serializer.ReadObject(st);

        // 結果の判定
        if (serverResponse.IsValid != true)
        {
            SetResult(Result.EntitlementIsNotValid, serverResponse.IsValid.ToString());
        }
        else if (serverResponse.Message?.ToUpper() != "OK")
        {
            SetResult(Result.MessageIsNotOk, serverResponse.Message ?? "(null)");
        }
        else
        {
            SetResult(Result.Success);
        }
    }
    catch (HttpRequestException ex)
    {
        SetResult(Result.HttpRequestException, ex.Message);
    }
    catch (Exception ex)
    {
        SetResult(Result.ExceptionThrown, ex.ToString());
    }
    finally
    {
        IsGetEntitlementRunning = false;
    }
}
```

```c#:class&nbsp;ServerResponse
public class ServerResponse
{
    public string? UserId;
    public string? AppId;
    public bool IsValid;
    public string? Message;
}
```

`SetResult()`は結果を保存するための自作関数です。排他制御が必要だったため、直接メンバ変数を変更せずに、関数経由で結果を保存しています。
その他にも名前しか出てこない変数がありますが、適宜調整してください。

# 5. 参考資料
* [Mod the Machine: Entitlement API changes in Inventor 2020](https://modthemachine.typepad.com/my_weblog/2019/09/entitlement-api-changes-in-inventor-2020.html)
* [Autodesk デベロッパー ネットワーク/App Store](https://www.autodesk.co.jp/developer-network/app-store)&nbsp;&nbsp;&nbsp;その中でも、特に、
* [デスクトップ アプリ用の Entitlement API](https://www.autodesk.co.jp/content/dam/autodesk/www/adn/pdf/autodesk-appstore-entitlement-api-for-desktop-apps-jp.pdf)


# 99. 親の記事に戻る
[Autodesk Inventor API Hacking (概略)](https://qiita.com/yamarah/items/cee08eb8f2b7aecbda83)