Search code examples
c#asp.net-coreswaggerautorest

How is an auto-generated c# client supposed to handle an API call that can return different types?


I am consuming a service that has the following definition:

[HttpGet]
[SwaggerOperation(nameof(GetAnimal))]
[Route("{animalId:long}", Name = nameof(GetAnimal))]
[ProducesResponseType(typeof(AnimalModel), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ErrorModel), StatusCodes.Status500InternalServerError)]
public Task<IActionResult> GetAnimal(string tenantId, long animalId)
{
    try
    {
        // Find the actual animal.. somewhere.

        return Ok(new AnimalModel());      

    }
    catch (Exception exception)
    {
        return InternalServerError(new ErrorModel());
    }    
}

This seems to cause autorest to generate a C# client with an object as a return type (I guess because of the ProducesResponseType attribute being specified twice):

public async Task<HttpOperationResponse<object>> GetAnimalWithHttpMessagesAsync(string tenantId, long animalId, [..])

Question

What is the recommended way to handle an API that return different objects?

Potential solutions

  • I could fix the client code and cast the result to find the right type (not good).
  • I could modify the API (if possible) so that it returns only one object composed of the AnimalModel and the ErrorModel (could be better).

Solution

  • ActionResult<T>

    ASP.NET Core 2.1 introduces the ActionResult<T> return type for Web API controller actions. It enables you to return a type deriving from ActionResult or return a specific type. ActionResult<T> offers the following benefits over the IActionResult type:

    • The [ProducesResponseType] attribute's Type property can be excluded. For example, [ProducesResponseType(200, Type = typeof(Product))] is simplified to [ProducesResponseType(200)]. The action's expected return type is instead inferred from the T in ActionResult<T>.
    • Implicit cast operators support the conversion of both T and ActionResult to ActionResult<T>. T converts to ObjectResult, which means return new ObjectResult(T); is simplified to return T;.

    Consider using the new ActionResult<T> and remove the produces response attribute Type altogether.

    [HttpGet]
    [SwaggerOperation(nameof(GetAnimal))]
    [Route("{animalId:long}", Name = nameof(GetAnimal))]
    [ProducesResponseType(StatusCodes.Status200OK)]
    [ProducesResponseType(StatusCodes.Status500InternalServerError)]
    public async Task<ActionResult<AnimalModel>> GetAnimal(string tenantId, long animalId) {
        try {
            // Find the actual animal.. somewhere...using await.
    
            var model = new AnimalModel();
    
            //populate model    
    
            return model;
        } catch (Exception exception) {
            return InternalServerError(new ErrorModel());
        }
    }