Azure SDKでサービスの応答をJSONでも取得したい


# 例えばこんなとき
例えば、AI Document IntelligenceなどのサービスをSDKを使って呼び出して、その応答を処理するとき、
SDKで用意されたオブジェクトだけでなく、**REST APIから返される生のJSONも取得したいよ**ってとき。  

REST APIを直接呼び出せばいいけど、せっかく用意されたSDKを使って便利に実装できないか調べてみた。

https://github.com/Azure/azure-sdk-for-net/blob/main/sdk/formrecognizer/Azure.AI.FormRecognizer/README.md#use-prebuilt-models

```cs:SDKを使ったサンプルコード
// Create DocumentAnalysisClient with AzureKeyCredential
string endpoint = "<endpoint>";
string apiKey = "<apiKey>";
var credential = new AzureKeyCredential(apiKey);
var client = new DocumentAnalysisClient(new Uri(endpoint), credential);

// Use Prebuilt Models
string filePath = "<filePath>";
using var stream = new FileStream(filePath, FileMode.Open);
AnalyzeDocumentOperation operation = await client.AnalyzeDocumentAsync(WaitUntil.Completed, "prebuilt-invoice", stream);
AnalyzeResult result = operation.Value;

```

# 生のJSONを取得する方法 その1
この方法が一番簡単でした。  
大体のSDKが対応していると思います。  

```cs
var operation = await client.AnalyzeDocumentAsync(WaitUntil.Completed, "prebuilt-invoice", stream);

// ResponseMessageを取得する
var response = operation.GetRawResponse();
var json = row.Content;
```

**参考**

https://github.com/Azure/azure-sdk-for-net/blob/main/sdk/core/Azure.Core/samples/Response.md


# 生のJSONを取得する方法 その2
- ちょっとややこしいですが、ロギングやデバッグ用のHttpPipelinePolicyを使用する方法です
- まずはAzure.Core.PipelineにあるHttpPipelinePolicyを継承したクラスを作成
- そして、SDKのクライアントインスタンスを生成するときに ClientOptionsとして指定してあげる



```cs:HttpPipelinePolicyを継承したクラス
    public class HttpResponseLoggingPolicy : HttpPipelinePolicy
    {
        
        public Action<Response>? LogResponseAction { get; init; }

        public override void Process(HttpMessage message, ReadOnlyMemory<HttpPipelinePolicy> pipeline)
        {
            ProcessNext(message, pipeline);
            LogResponse(message.Response);
        }

        public override async ValueTask ProcessAsync(HttpMessage message, ReadOnlyMemory<HttpPipelinePolicy> pipeline)
        {
            await ProcessNextAsync(message, pipeline).ConfigureAwait(false);
            LogResponse(message.Response);
        }

        private void LogResponse(Response response)
        {
            // 202はAzure非同期の承認済みResult
            //https://learn.microsoft.com/ja-jp/azure/azure-resource-manager/management/async-operations

            if (response.Status != 200) return;
            
            // アクション呼び出し
            LogResponseAction?.Invoke(response);
            
            
        }
    }

```

```cs:クライアント生成とサービス呼び出し

    // ClientOptionsにAddPolicyでHttpResponseLoggingPolicyを追加
    var options = new DocumentAnalysisClientOptions();
    var policy = new HttpResponseLoggingPolicy()
    {
        // Action<Response>のメソッドを登録
        LogResponseAction = ParseResultJson
    };
    options.AddPolicy(policy, HttpPipelinePosition.PerCall);

    // インスタンス生成時の引数でClientOptionsを渡す
    client = new DocumentAnalysisClient(new Uri(endPoint), credential, options);

    // すると、サービスを呼び出したときに
    var operation = await client.AnalyzeDocumentAsync(WaitUntil.Completed, "prebuilt-invoice", stream);
```


```cs:
    public void ParseResultJson(Response response)
    {
        // Reponseの処理
        // Azure.ResponseはHttpResponseMessageと扱い方は同じ
        var content = response.Content.ToString();
        if (content == null) return;

        var node = JsonNode.Parse(content);
        if (node == null) return;

        if(node["status"]?.ToString() == "succeeded"){
            AnalyzeResultJson = content;
        }

    }

```

## 解説
1. クライアント生成時にAddPolocyでHttpResponseLoggingPolicyクラスを登録します。 
1. このとき、独自のLogResponseActionに、サービス呼び出し時に実行されるメソッドを登録します。
1. SDKからAzureサービスが呼び出されると、直前にHttpResponseLoggingPolicyクラスのProcessAsyncが呼び出されます。
1. ProcessAsyncが呼び出された時点ではまだAzureサービスに要求はされていません。
1. ここで、基底クラスのProcessNextAsyncを呼び出すと、pipelineの情報を元にAPIに要求しmessageにResponseMessageが格納されます
1. 最後に、LogResponseを呼び出すことで、APIの呼び出し後に独自の処理を実行できます。
<br>

今回のAzure AI Documentはサービスの仕様上、少なくとも3回程度LogResponseが呼び出されています。
- まずStatusCode202で応答
- 次に解析完了まで定期的にAPIに完了確認の要求を行う
- JSONのstatusがsucceededに変われば処理完了し、解析された本文を含むJSONが返される。

なので、ParseResultJsonでは最終的に結果が返ってきたときだけ後続の処理を行っています。  

**参考**

https://github.com/Azure/azure-sdk-for-net/blob/main/sdk/core/Azure.Core/samples/Pipeline.md