3

Net core application and calling other .Net Core Web API application. I am calling controller methods as below

public async Task<T> SendRequest<T, U>(U request, string methodName, HttpMethod reqtype, string token)
        {
            T res = default(T);
            try
            {
                string url = methodName;
                string contentJson = JsonConvert.SerializeObject(request);
                var content = new StringContent(contentJson, Encoding.UTF8, "application/json");
                var actualRequest = new HttpRequestMessage(reqtype, url);
                actualRequest.Content = content;
                _httpClient.DefaultRequestHeaders.Clear();
                _httpClient.DefaultRequestHeaders.Add("Authorization", token);
                var rawData = await _httpClient.SendAsync(actualRequest);
                var processedData = await rawData.Content.ReadAsStringAsync().ConfigureAwait(false);
                res = JsonConvert.DeserializeObject<T>(processedData);
            }
            catch (Exception ex)
            {
                _reqHandler.RaiseBusinessException(MethodBase.GetCurrentMethod()?.DeclaringType?.DeclaringType?.Name
                    , MethodBase.GetCurrentMethod().Name,
                    "InputType: " + typeof(U) + " || OutputType: " + typeof(T) + " || Error Message: " + ex.Message);
            }
            return res;
        }  

Below are the samples returning from API controller

return StatusCode(404, result.MapFileFileUploadSummary);
 return StatusCode((int)System.Net.HttpStatusCode.Unauthorized);

In the above code when I return some status code I want to bind it to genric type T. For example I am calling one method

 _Client.SendRequest<ObjectResult, requestModel>()

I am sending ObjectResult of type as return type. I am getting below response in processedData for example,

"{\"type\":\"https://tools.ietf.org/html/rfc7235#section-3.1\",\"title\":\"Unauthorized\",\"status\":401,\"traceId\":\"00-e517f28f9c0b0441a8634c1c703a1cae-f64589d11056ad45-00\"}"

But unable to bind it to

res = JsonConvert.DeserializeObject<T>(processedData); This returns nothing I am adding screenshot here

enter image description here here T is of type ObjectResult. I am looking for generic response type here so that I can always bind it to type T. Can some one help me with this implementation. Any help would be appreciated. Thank you

Tieson T.
  • 20,774
  • 6
  • 77
  • 92
Mr Perfect
  • 585
  • 8
  • 30
  • What do you mean by "no able to bind it"? The code should compile according to me. Or is it a runtime issue? – Arcord Mar 24 '21 at 14:08
  • Hi Arcord I have updated my question. I want to bind "{\"type\":\"https://tools.ietf.org/html/rfc7235#section-3.1\",\"title\":\"Unauthorized\",\"status\":401,\"traceId\":\"00-e517f28f9c0b0441a8634c1c703a1cae-f64589d11056ad45-00\"}" to JsonConvert.DeserializeObject(processedData) but I have added screenshot above here status code is null I am expecting Unauthorized – Mr Perfect Mar 24 '21 at 14:11
  • If I understand correctly I will never work. By example in your object ObjectResult the field for the status code is named "StatusCode" but in your JSON it's named "status" (or "title", depend if you want the code or the name). You should create an class corresponding to your JSON, deserialize the JSON into that type and map to your "ObjectResult". – Arcord Mar 24 '21 at 14:16
  • I understood I can create custom class with properties type, title, status and I can send That is Okay. It may not return same response always. As per the above example It will return Unauthorized but in some cases It may return actual data right? Its upto my API what data it returns. So I am looking for some way whatever data comes I want to handle. Is there any way we can handle better? – Mr Perfect Mar 24 '21 at 14:26
  • Ok now your issue is more clear to me... I will try to formulate an answer :-) – Arcord Mar 24 '21 at 14:49

1 Answers1

1

The issue you have is: In case of error the JSON will be totally different than if it succeed.

For the sake of example I will hide most part about the HTTP request :

public async Task<ActionResult<T>> SendRequest<T, U>(U request, string methodName, HttpMethod reqtype, string token);

For me you should not return "T" but "ActionResult<T>". Your code still doesn't know what will be the actual content (T) but know it will return the ActionResult. It will ease the rest.

var response = await _httpClient.SendAsync(actualRequest);
if (!response.IsSuccessStatusCode) 
{
   return new StatusCodeResult((int)response.StatusCode)
}
var responseData = JsonConvert.DeserializeObject<T>(await rawData.Content.ReadAsStringAsync());
return new ObjectResult(responseData);

In all cases you will have an ActionResult that can be directly return from your controller.

In case of error the result will only be the status code returned by the other server.

In case of success you'll have the data send by the other server.

I think in case of error returning the status code to your user is enough but if to want to send the whole object you also can do it. You just need to create first a class that will represent the data sent in case of error then:

var response = await _httpClient.SendAsync(actualRequest);

object responseData;

if (response.IsSuccessStatusCode) 
{
    responseData = JsonConvert.DeserializeObject<T>(await rawData.Content.ReadAsStringAsync());
}
else 
{
   responseData = JsonConvert.DeserializeObject<YourClassInCaseOfError>(await rawData.Content.ReadAsStringAsync());
}

return new ObjectResult(responseData) 
{
   StatusCode = (int)response.StatusCode,  
}

And you also have to change the method signature to :

public async Task<ActionResult> SendRequest<T, U>(U request, string methodName, HttpMethod reqtype, string token);

Since the second example will potentially return 2 differents types (the data type or the error type) you have to use the ActionResult, not the ActionResult<>

Arcord
  • 1,724
  • 1
  • 11
  • 16
  • Hi Arcord. Thanks for understanding and providing answer. i just stuck at last step. StatusCodeResult(responseData) StatusCodeResult will accept int as parameter but we are passing responseData which is causing error. Any idea how we can fix this – Mr Perfect Mar 25 '21 at 03:31
  • Seems I was a little bit too quick so I replace the StatusCodeResult by ObjectResult which accept data and a status code. If you're using the second solution the method signature should be slightly different and I put the new version in the response as well :-) – Arcord Mar 25 '21 at 08:11
  • Hi Arcord I changed method signature to public async Task SendRequest(U request, string methodName, HttpMethod reqtype, string token) but this line doesnt work anymore responseData = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); because i dont have T right. Am I correct – Mr Perfect Mar 25 '21 at 09:46
  • Totally correct I fixed that. I remove the T from ActionResult because it was not needed anymore but it was still necessary to have it in the signature ! :-) – Arcord Mar 25 '21 at 10:00
  • Everything working fine. I am returning for example return StatusCode(200, result.MapFileFileUploadSummary); In calling method I have something like this var result = await _geoClient.SendRequestMapFileUpload( new MapFileRequestModel { MapFileDetailsEntity = mapFileDetailsEntity, SiteAssessmentRequestId = SiteAssessmentRequestId }, "CheckDuplicateMapFiles", HttpMethod.Post).ConfigureAwait(false); return Ok(result); – Mr Perfect Mar 25 '21 at 10:22
  • I am passing generic type DuplicateMapFileResponseModel but returning I will get result.value when debugging. But when I try to write something return Ok(Result.Value) here .value is not coming – Mr Perfect Mar 25 '21 at 10:23