Home > database >  Need to specify return type of a generic method that calls an API endpoint
Need to specify return type of a generic method that calls an API endpoint

Time:01-06

I have a generic method that does a Post:

protected async Task<T> PostAsync<T>(string resource, object value = null)
{
    T result = default(T);

    var client = _httpClientFactory.CreateClient();

    var requestMessage = new HttpRequestMessage(HttpMethod.Post, _baseUri   resource);

    requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _token);

    if (value != null)
        requestMessage.Content = new StringContent(JsonConvert.SerializeObject(value), Encoding.UTF8, "application/json");

    using (var response = await client.SendAsync(requestMessage))
    {
        try
        {
            response.EnsureSuccessStatusCode();
            var responseContent = await response.Content.ReadAsStringAsync();
            result = JsonConvert.DeserializeObject<T>(responseContent);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, ex.Message);
            //if (!response.IsSuccessStatusCode)
                //throw new HttpStatusCodeException((int)response.StatusCode, await response.Content.ReadAsStringAsync());

        }
    }

    return result;
}

The API controller method that I am calling looks like this (I have omitted some implementation details). As you can see, it doesn't return any data:

[HttpPost("{contactId}/opt-out/test")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> UpdateTestAsync(Guid contactId, [FromQuery] string source)
{
    try
    {
        return Ok();
    }
    catch (Exception exception)
    {

    }
}

Now using the generic Post method, I would like to know how to call the API endpoint. I have tried the following:

public async Task UpdateContactOptOutAsync(Guid contactId, string source)
{
    return await base.PostAsync<Task>($"contacts/{contactId}/opt-out/test?source={source}");
}

But I get the following error:

Since 'BeamApiRepository.UpdateContactOptOutAsync(Guid, string)' is an async method that returns 'Task', a return keyword must not be followed by an object expression. Did you intend to return 'Task<T>'?

So how do I specify a return type to call an API endpoint that doesn't return any value?

CodePudding user response:

it is better not to use it without T since it used to deserialize result. So I recomend to use it this way (PostAsync< Task > will work too)

public async Task UpdateContactOptOutAsync(Guid contactId, string source)
{
     await base.PostAsync<Task<object>>($"contacts/{contactId}/opt-out/test?source={source}");
}

if your API dosn't return any data it will return null, otherwise it will still return the result as an object instance. But the code ignores it since you have void method. You can use var result = await base.Post.... and put some error code if it is not null, but it is out of the scope of this question.

CodePudding user response:

Your Http client will likely not return a Task<Task> so why are you calling PostAsync<Task>(...)?

If you are using C# 7 or lower, you can simply call PostAsync<object>(...) which will always return null for the API you are calling.

If you are using C# 8 or higher, you can call PostAsync<object?>(...).

Better yet, you can add a generic method which simply returns Task (with no return type), so it for API POST calls with no response body. That way, it is clear that the response has nothing.

protected async Task PostAsync(string resource, object value = null)
{
   ....
}

CodePudding user response:

Generally you can't unify "returns nothing" and "returns some type" cases into one method.

Options:

  • separate methods for "nothing" and "type" cases - Task<T> Post<T>(...) and Task Post(...)
  • return type that wraps the desired response class Result<T> { T Result; HttpStatusCode Status;}
  • some special type to indicate "expecting no result" like struct NoResult {} and use it in Post: await Post<NoResult>(...);.

I would personally go for two separate methods (possibly with some refactoring to share some code) for basic cases where I don't care too much about special return values. It is often necessary to analyze exact return values of the HTTP call (like 200/201/204) so returning wrapper object may be a good idea.

Note that even your existing code probably fine as-is as long as you have "return default if response is empty" logic added. Then you can use any type in case there is no response expected: await Post<string>(....); and Post has something like:

  var responseContent = await response.Content.ReadAsStringAsync();
  return string.IsNullOrWhitespace(responseContent) ? default(T) : 
       JsonConvert.DeserializeObject<T>(responseContent);

CodePudding user response:

You may need to specify the return Task type. something like below.

public async Task<IActionResult> UpdateContactOptOutAsync(Guid contactId, string source)
{
    return await base.PostAsync<Task>($"contacts/{contactId}/opt-out/test?source={source}");
}

CodePudding user response:

Assuming you have your own implementation of Task (and the generic type in the call to PostAsync<Task> is not a mistake) then you need UpdateContactOptOutAsync to return System.Threading.Tasks.Task<Task>.

(just make sure to not mix-up the namespaces)

public async Task<Task> UpdateContactOptOutAsync(Guid contactId, string source)

Otherwise, if you haven't implemented your own Task class, then UpdateContactOptOutAsync should look something like this:

public async Task<MyResponse> UpdateContactOptOutAsync(Guid contactId, string source)
{
    return await base.PostAsync<MyResponse>($"contacts/{contactId}/opt-out/test?source={source}");
}

Where MyResponse is the actual type you expect to deserialize from the response.

  •  Tags:  
  • Related