web-dev-qa-db-ja.com

C#で失敗したAPI呼び出しを処理する方法

APIサービスを作成し、WPFアプリケーションからの情報を利用したいと思います。
これまでに、APIエンドポイントの呼び出しに使用されるApiHelperを初期化して提供するクラスHttpClientと、取得するメソッドを公開するクラスMemberServiceを作成しましたAPIから取得するデータ。

public class ApiHelper : IApiHelper
{
    private readonly IConfiguration _config;

    public HttpClient ApiClient { get; private set; }

    public ApiHelper(IConfiguration config)
    {
        _config = config;
        InitializeClient();

    }

    private void InitializeClient()
    {
        string apiBase = _config["api"];

        ApiClient = new HttpClient
        {
            BaseAddress = new Uri(apiBase)
        };

        ApiClient.DefaultRequestHeaders.Clear();
        ApiClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
    }

これはサービスです:

public class MemberService : IMemberService
{
    private readonly IApiHelper _apiHelper;

    public MemberService(IApiHelper apiHelper)
    {
        _apiHelper = apiHelper;
    }

    public async Task<List<Member>> GetAllMembersAsync()
    {
        using (HttpResponseMessage response = await _apiHelper.ApiClient.GetAsync("/api/Members"))
        {
            // If the response has a successful code...
            if (response.IsSuccessStatusCode)
            {
                // Read the response
                var result = await response.Content.ReadAsAsync<List<MemberResponse>>();
                var output = AutoMapping.Mapper.Map<List<Member>>(result);
                return output;
            }
            else
            // If the response is not successful
            {
                // Error handling here
            }
        }

        return null;
    }

私の質問は、try catchブロックで処理できるHttpRequestExceptionなどのエラーの処理ではなく、2xxの範囲にないステータスコードで応答を処理する方法です。
特に、APIのエンドポイントはトークンで保護されているため、ユーザーはリクエストを行う前に認証を行う必要があり、トークンの有効期限が切れた後は、トークンを更新する必要があります。
私がしたいことは、応答が401 - Unauthorizedのステータスの場合、例外をスローしたり、エラーメッセージでオブジェクトを返したりするのではなく、メソッドがトークンを更新しようとすることです(呼び出し別のサービスの適切なメソッド)。期限切れになる可能性があり、呼び出しが再び失敗した場合にのみ、例外をスローします。すべてのAPI呼び出しでこの動作が必要です。
API呼び出しを扱うのは初めてなので、これは私がやろうと思っていたことですが、その方法についていくつかのヒントを教えてください。これを達成するための最良のアプローチは何ですか?または、API呼び出しを行い、その応答を管理する別の方法を提案しますか?

3
Concy

私がやりたいのは、応答のステータスが401-Unauthorizedの場合、例外をスローしたり、エラーメッセージでオブジェクトを返したりするのではなく、メソッドがトークンを更新しようとする必要があります(別のサービスで適切なメソッドを呼び出す)。期限切れになる可能性があり、呼び出しが再び失敗した場合にのみ、例外をスローします。すべてのAPI呼び出しでこの動作が必要です。

それは素晴らしいアイデアのように思えます。

それを行うためのヘルパーメソッドから始めましょう。

_public class ApiHelper : IApiHelper
{
    ...
    public async HttpContent GetAuthenticatedAsync(string requestUri)
    {
        var response = await ApiClient.GetAsync(requestUri);

        // Pseudo code:
        //
        // if (200): return response.Content
        // if (401): 
        //   refresh token
        //   try GetAsync again
        //   if (200): return response
        //   else: throw meaningful exception - something is wrong with our credentials
        // else: throw meaningful exception - unexpected return value
        ...
    }
}
_

次に、カプセル化を使用して、ビジネスロジックが誤って発生しないようにしますbypassヘルパーメソッド:ApiClientを非公開にし、必要に応じて新しい低レベルヘルパーメソッドを追加します。

要約トランスポートメカニズムへの最初のステップが完了しました。これにより、コードがトランスポート層の問題に対処するのではなく、問題ドメインの問題を解決することに集中できるようになるため、ビジネスロジックの可読性が向上します。常に同じデシリアライゼーションロジックを使用する場合は、それをApiHelperにも追加することをお勧めします(例:public async TResult GetAuthenticatedAsync<TResult, TResultMessage>(string requestUri))。

また、単体テストも簡単になります。認証の問題は、ApiHelperをテストするのではなく、MemberServiceをテストすることでテストする必要があります。

1
Heinzi