C#でRestSharpを使う

# はじめに
最近HttpClientを使用してソフトウェアを開発していたのですが、[HttpClientにはDisposeしてはいけない制約](https://qiita.com/superriver/items/91781bca04a76aec7dc0)というものがありまして.NET Coreから追加された[HttpClientFactory](https://blog.nnasaki.com/entry/2019/10/04/143936)を使用して開発を行っていました。ただ、私の経験不足のためどうしてもうまく使いこなすことができず、ふとRestSharpのことを思い出したので書き残していきます。

# 使用環境
・Windows 11 21H2 Build 22000.318
・Docker on WSL 2 (Ubuntu 20.04 LTS)
・[httpbin.org](https://httpbin.org/) (Dockerコンテナでの動作)
・JetBrains Rider 2021.2.2
・.NET 5
・Charles Proxy

# 使用ライブラリ
・[RestSharp](https://www.nuget.org/packages/RestSharp) v107.0.0-preview.8

# 使い方
役割をわかりやすくするため、メソッドとして書いています。各自読み解いてください

## 全体のコード
```csharp:Program.cs
using System;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using RestSharp;

namespace restsharptest {
    class Program {
        private static RestClient _client;

        static async Task<RestResponse> Post() {
            // RestRequestを作成
            var request = new RestRequest("http://localhost/post", Method.Post);

            // ヘッダー
            request.AddHeader("hoge", "hogee");
            // クッキー
            request.AddParameter("hoge", "hogee", ParameterType.Cookie);
            // クエリ
            request.AddParameter("hoge", "hogee", ParameterType.QueryString);
            // クエリ (エンコードなし)
            request.AddParameter("hoge", "hogee", ParameterType.QueryStringWithoutEncode);
            // ボディ
            request.AddParameter("hoge", "hoge1", ParameterType.GetOrPost); // application/x-www-form-urlencoded
            request.AddJsonBody(new {hoge = "hoge1"}); // application/json; charset=utf-8
            request.AddFile("file", "yaju.png"); // multipart/form-data

            // 送信
            var response = await _client.ExecuteAsync(request);

            return response;
        }

        static async Task<RestResponse> Get() {
            // RestRequestを作成
            var request = new RestRequest("http://localhost/get", Method.Get);
            // クエリ
            request.AddParameter("hoge", "hogee", ParameterType.QueryString);
            // クエリ (エンコードなし)
            request.AddParameter("hoge", "hogee", ParameterType.QueryStringWithoutEncode);

            // 送信
            var response = await _client.ExecuteAsync(request);

            return response;
        }

        static RestClient CreateClient() {
            // HttpClientHandlerを生成
            var handler = new HttpClientHandler();
            // Charles Proxyを使用（通信確認用)
            handler.Proxy = new WebProxy("localhost", 8888);

            // RestClient生成、引数にhandlerを指定 (handlerはなくてもよい)
            var client = new RestClient(handler);
            return client;
        }

        static async Task Main(string[] args) {
            _client = CreateClient();

            var response = await Post();

            // ステータスコード
            var statusCode = response.StatusCode;

            // レスポンスボディ
            var content = response.Content;
            // レスポンスボディ (Bytes)
            var bytes = response.RawBytes;

            // クッキー
            var cookies = response.Cookies;

            // 例外
            var errorException = response.ErrorException;
            // エラーメッセージ
            var errorMessage = response.ErrorMessage;
        }
    }
}
```

## RestClient生成
```csharp:Program.cs
static RestClient CreateClient() {
            // HttpClientHandlerを生成
            var handler = new HttpClientHandler();
            // Charles Proxyを使用（通信確認用)
            handler.Proxy = new WebProxy("localhost", 8888);

            // RestClient生成、引数にhandlerを指定 (handlerはなくてもよい)
            var client = new RestClient(handler);
            return client;
        }
```
これびっくりしたのですが、なんとHttpClientで使用できるハンドラーが流用できます。すごい便利です（通信監視用としてCharles Proxyを使用しています）
ユーザーエージェントはヘッダーとして指定しない限り**RestSharp/107.0.0.0**となります。
## GET
```csharp:Program.cs
static async Task<RestResponse> Get() {
            // RestRequestを作成
            var request = new RestRequest("http://localhost/get", Method.Get);
            // クエリ
            request.AddParameter("hoge", "hogee", ParameterType.QueryString);
            // クエリ (エンコードなし)
            request.AddParameter("hoge", "hogee", ParameterType.QueryStringWithoutEncode);

            // 送信
            var response = await _client.ExecuteAsync(request);

            return response;
        }
```
クエリは任意でつけれます。その他クッキーやヘッダーも追加・更新が可能です。
## POST
```csharp:Program.cs
static async Task<RestResponse> Post() {
            // RestRequestを作成
            var request = new RestRequest("http://localhost/post", Method.Post);

            // ヘッダー
            request.AddHeader("hoge", "hogee");
            // クッキー
            request.AddParameter("hoge", "hogee", ParameterType.Cookie);
            // クエリ
            request.AddParameter("hoge", "hogee", ParameterType.QueryString);
            // クエリ (エンコードなし)
            request.AddParameter("hoge", "hogee", ParameterType.QueryStringWithoutEncode);
            // ボディ
            request.AddParameter("hoge", "hoge1", ParameterType.GetOrPost); // application/x-www-form-urlencoded
            request.AddJsonBody(new {hoge = "hoge1"}); // application/json; charset=utf-8
            request.AddFile("file", "yaju.png"); // multipart/form-data

            // 送信
            var response = await _client.ExecuteAsync(request);

            return response;
        }
```
リクエストには説明のため様々なボディを付与しておりますが、２つ以上つけてしまうとエラーが出てしまいますのでご注意ください。
FormDataをつけたい場合、**ParameterTypeをGetOrPost**にすることで**application/x-www-form-urlencoded**として送信することができます。
**AddJsonBody**という専用メソッドが用意されており、ここにJsonクラスを放りこむことで勝手にシリアライズしてくれます__（ただしUTF8にエンコードされる）__

## レスポンスの詳細
```csharp:Program.cs
　　　　　　 // POSTで実践
            var response = await Post();

            // ステータスコード
            var statusCode = response.StatusCode;

            // レスポンスボディ
            var content = response.Content;
            // レスポンスボディ (Bytes)
            var bytes = response.RawBytes;

            // クッキー
            var cookies = response.Cookies;

            // 例外
            var errorException = response.ErrorException;
            // エラーメッセージ
            var errorMessage = response.ErrorMessage;
```
何かうまくいかなかったり、思った動作にならない場合**response.ErrorException**を確認してみるといいです。私はこの存在に気付かずにどうしたものかとさまよっていました。

## 個人的につまずいたこと
```csharp:Program.cs
request.AddJsonBody("{\"hoge\": 100}");
```
割と直接Jsonデータを送信したい時があるため、このように書き込んで送信したところ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/294563/838b15e6-d5d5-4c82-8420-8f13f511a248.png)
このようにUTF8にエンコードされて送信されます。
ほとんどのサーバー（httpbin含め）はUTF8に対応しているため読み取ることが可能ですが、一部サーバーは対応していないためどうしたものかと悩んでいたところ、RestSharpで用意されているParameterクラスをインスタンス化し、リクエストにAddParameterで付与することで解決しました。（これで３時間ほど悩んでいました）

```csharp:Program.cs
            // 上から名前・値・ContentType・RequestBody
            var parameter = new Parameter(
                "application/json",
                "{\"hoge\": 100}",
                "application/json",
                ParameterType.RequestBody);
            
            // 以上のパラメーターを追加
            request.AddParameter(parameter);
```
~~ただひとつ不明な点は、名前にContentTypeを指定することでどうしてボディとなるのか気になっています。もしわかる方がいらっしゃいましたらご教授くださると嬉しいです。~~
21/12/17 コメント欄で理由を教えていただきました。ありがとうございます。

# 最後に
私がRestSharpについて書き始めたのは、調べた限りあまり記事が出てこなかったからです。おそらく調べた方が悪いと思うのですが、理解を深めるためにも書き留めておこうと思います。

C#を初めてちょうど１年立ちました。プログラムはPythonから始め、C#に移ったため今ではすっかりC#の書き方に慣れました。まだまだC#について知らない部分もたくさんあるため、１万時間の法則を目指して頑張ります！
不明な点や、間違っている点などは気軽にコメントください。修正いたします。

ご清聴ありがとうございました。

# 引用元
https://qiita.com/superriver/items/91781bca04a76aec7dc0
https://blog.nnasaki.com/entry/2019/10/04/143936
https://qiita.com/kawaidainf/items/032d831187c08702fce9
とても役に立ちました、よろしければこちらもご拝見ください。