Convert ASP.NET Core ActionResult<T> object to another type in a filter

Viewed 2737

I have a generic Result<T> type that I'm using from my business-level services to return results. This type includes a property with the actual value (public T Value {get;}) as well as a status property that indicates Success, NotFound, ValidationError, or other options.

In an API controller, I can evaluate the result of a service call and return an appropriate ActionResult like NotFound or Ok or BadRequest.

I can manually do what I want in the method

public ActionResult<Result<Customer>> GetCustomer(int id)
{
    Result<Customer> result = _someService.GetGetCustomer(id);
    if (result.Status == ResultStatus.NotFound) return NotFound();
    if (result.Status == ResultStatus.Invalid)
    {
        foreach (var error in result.ValidationErrors)
        {
            ModelState.AddModelError(error.Key, error.Value);
        }
        return BadRequest(ModelState);
    }

    return Ok(result.Value);
}

Example based on source here

but I'd like to be able to do this from within an ActionFilter instead.

The problem is I'm not having much success figuring out how to cast the value since I want the filter to work for any kind of T. Some pseudo code may help:

[TranslateResultToHttp]
public ActionResult<Result<Customer>> GetCustomer(int id)
{
  Result<Customer> result = _someService.GetCustomer(int id);

  return Ok(result);
}

In my TranslateResultToHttpAttribute I'd need to take the resulting result, look at its value, and if it really was Ok I would replace Result<Customer> with just the Customer. But if it were a NotFound I would return NotFound, etc.

The problem is the filter has no idea what T the Result<T> might be, so I'm having a hard time unpacking the result to get its value, etc.

1 Answers

Assuming your Result<T> type has a non-generic base interface that contains the pure status, you could just return the result directly, getting an implicit object result. And then you can implement a result filter to update the status code:

public interface IResult
{
    int Result { get; set; } // could of course be something else
}

public class UpdateResultFilter : IResultFilter
{
    public void OnResultExecuting(ResultExecutingContext context)
    { }

    public void OnResultExecuted(ResultExecutedContext context)
    {
        if (context.Result is ObjectResult objectResult && objectResult.Value is IResult result)
        {
            // update status on object result
            objectResult.StatusCode = result.Status;
        }
    }
}

In the end, this is the same as returning specific results like NotFoundResult or BadRequestResult because these are just StatusCodeResults with a predefinied status code.

Related