プリザンターでAPIでファイルだけをアップロードする方法

## はじめに
プリザンターのAPIには「内容」や「説明項目」に文章と[画像をアップロードする機能](https://pleasanter.org/ja/manual/api-record-create)として`ImageHash`があります。ただこの`ImageHash`、1項目に対して1度に1つの画像しかアップロード出来ないという制約があり、少しハンドリングが難しい側面があります。
今回はこれを解消する方法として「内容」や「説明項目」に使用するためにファイルだけをアップロードする方法を紹介します。

## そんな機能どこに？
実はこの方法、マニュアルには記載がありません。編集画面で添付ファイル項目などにファイルをアップロードするときなどに使用されている実装の流用になります。

## 使い方をみてみる
プリザンターの他の公開されているAPIと使い方が大きく異なります。実装はここになります。

https://github.com/Implem/Implem.Pleasanter/blob/575e3d58851cd1e225e2c5970e9d3510cbe65587/Implem.Pleasanter/Controllers/Api/BinariesController.cs#L91

いろいろと細かい使い方が出来るのですが、今回は一番簡単な方法を紹介します。

### 前提
新規作成ではなく、更新で画像をセットします。更新で処理をする方法は簡単で、`ResultId・IssuesId・WikiId`(以下`ReferenceId`)が必要になるからです。
また、1つのAPI操作でアップロードできる画像の数は1つになります。2つ以上送ることは出来ますが、先頭以外は無視されます。

### リクエスト
```
POST /api/binaries/upload/?id={ReferenceId} HTTP/1.1
Content-Type: multipart/form-data; boundary={任意のキー}
Authorization: Bearer {APIキー}

--{任意のキー}
Content-Disposition: form-data; name="upfile"; filename="{ファイル名}"
Content-Type: {コンテンツタイプ}
{ファイルコンテンツ}
```

### レスポンス
#### 成功時
```
HTTP/1.1 200
Content-Type: application/json;charset=utf-8
{
    "Id": 113138,
    "StatusCode": 200,
    "Message": "593208BE819E4F29B112A53D67B4BF3B"
}
```
|Key|Type|Value|
|--|--|--|
|Id|long|ファイルのBinaryId|
|StatusCode|string|200|
|Message|string|ファイルのGuid|

#### 失敗時
```
HTTP/1.1 200
Content-Type: application/json;charset=utf-8
{
    "Id": 0,
    "StatusCode": 400,
    "Message": "要求が不正です。"
}
```
```
HTTP/1.1 404
```
|Key|Type|Value|
|--|--|--|
|Id|long|0|
|StatusCode|string|200以外のStatusCode|
|Message|string|エラーメッセージ|

エラー発生時のStatusCodeはプリザンターあるあるではありますが、ヘッダとボディの両方をチェックする必要があるので注意が必要です。

|ヘッダ:StatusCode|ボディ:StatusCode|詳細|
|--|--|--|
|200|400|Idがセットされていないか0である|
|200|401|認証ヘッダーのAPIキーが不正|
|404||ファイルがコンテンツに含まれない|
|404||コンテンツタイプのMIMEが不正|
|500||リクエスト内容を解釈出来なかったとき|

### 実装例
C#で実装した場合の実装例です。一部省略しているところもあるので、あくまで参考程度にどうぞ。

```csharp
public class PleasanterApi
{
    static PleasanterApi()
    {
        HttpClient = new HttpClient();
    }

    public PleasanterApi(string baseUrl, string apiKey)
    {
        BaseUrl = baseUrl;
        ApiKey = apiKey;
    }

    private static readonly HttpClient HttpClient;

    public string BaseUrl {get; private set;}
    public string ApiKey {get; private set;}
    
    public async Task<BinariesUploadResponse> BinariesUpload(string filePath, long id)
    {
        try
        {
            if (filePath == null || !Path.Exists(filePath))
            {
                return default;
            }
            string fileName = Path.GetFileName(filePath);
    
            using (var content = new MultipartFormDataContent())
            using (var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))
            {
                StreamContent streamContent = new StreamContent(fs);
                streamContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data")
                {
                    Name = "upfile",
                    FileName = fileName
                };
                content.Add(streamContent);
    
                // メソッド (POST) と送信先の URL 指定
                HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, $"{BaseUrl}/api/binaries/upload?id={id}");
                request.Content = content;
                request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", ApiKey);
    
                var result = await HttpClient.SendAsync(request);
    
                if (result.IsSuccessStatusCode)
                {
                    return await result.Content.ReadAsAsync<BinariesUploadResponse>();
                }
                else
                {
                    return default;
                }
            }
        }
        catch (Exception e)
        {
            throw e;
        }
    
        return default;
    }

    public class BinariesUploadResponse
    {
        public long Id {get; set;}
        public string StatusCode {get; set;}
        public string Message {get; set;}
    }
}
```

### 使い方
`api/create`で項目を作成後(=ReferenceIdが取得できるようになったあと)に、ファイルのアップロード処理を行ってください。その後、アップロードのレスポンスに含まれるGuidを使用して、`Body` `Desctiption*`に対して文字列をセットします。画像の埋込は`![image](/binaries/{Guid}/show)`という文字列で行います。サイトをサブディレクトリで運用している場合はパスをそれにあわせて書き換えてください。

## まとめ
今回はプリザンターで添付ファイル項目以外へのファイルアップロードを行う方法を紹介しました。この方法が使えると、文章と画像をミックスさせることが多いWikiなどでのファイルハンドリングが劇的に楽になります。是非活用してみてください。