Home > Software engineering >  Wrapping a complex object as Response with ResultFilterAttribute
Wrapping a complex object as Response with ResultFilterAttribute

Time:07-30

In my controller, I've inherited from a ControllerBase which there is a Result method that is used to wrap the response into a ResponseBase object like this :

[ApiController]
[Route("api/v{version:apiVersion}/[controller]")]
public abstract class BaseApiController : ControllerBase
{ 
 protected async Task Result<T>(T content, Dictionary<string, string> headers = null,
    HttpStatusCode statusCode = HttpStatusCode.OK, string contentType = 
    "application/json")
{
    Response.StatusCode = (int)statusCode;
    Response.ContentType = contentType;
    if (headers != null)
    {
        foreach (var header in headers)
        {
            Response.Headers.Add(header.Key, header.Value);
        }
    }
    var data = Encoding.UTF8.GetBytes
       (MySerializer.Serialize(new ResponseBase<T> { Data = content }));

    await Response.Body.WriteAsync(data.AsMemory(0, data.Length));
 } 
}

And the ResponseBase is like :

public class ResponseBase
{
  [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
  public List<ErrorBase> Errors { get; set; }
}

public class ResponseBase<T> : ResponseBase
{ 
  [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
  public T Data { get; set; }
}


public class ErrorBase
{
  [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
  public string FieldName { get; set; }

  [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
  public string ErrorMessage { get; set; }
}

And finally my controller :

 [ApiVersion("1")]
 public class ConfigurationController : BaseApiController
 {
   private readonly IConfigurationService _configurationService;

   public ConfigurationController(IConfigurationService configurationService)
   {
    _configurationService = configurationService;
   }
 

   [HttpGet("getData")]  
   public async Task GetData()
   {
    await Result(await _configurationService.GetRelatedData());
   }
 }

Now, the question here is, how can I wrap my response into a ResponseBase with a help of ResultFilterAttribute without explicitly calling the Result method in the ControllerBase

I've tried to use a ResultFilter to wrap my response but I couldn't find any sample to do this. I've also read this solution but didn't help.

I appreciate any help.

CodePudding user response:

  1. Implement ResultFilter.

In short,

1.1. Get the values of context.Result such as StatusCode, ContentType, Value.

1.2. Bind the Value to the root class (ResponseBase).

1.3. Lastly, produce a new Response.

public async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
{
    Dictionary<string, string> headers = null;
    int statusCode = (int)HttpStatusCode.OK;
    string contentType = "application/json";

    var responseBaseType = typeof(ResponseBase<>).MakeGenericType(typeof(object));
    dynamic? responseBase = Activator.CreateInstance(responseBaseType);

    var result = context.Result as ObjectResult;
    if (result?.Value == null)
    {
        await next();
        return;
    }

    if (result.StatusCode != null)
        statusCode = (int)result.StatusCode;

    if (result.ContentTypes != null
        && result.ContentTypes.Any())
        contentType = result.ContentTypes[0];

    if (statusCode == (int)HttpStatusCode.OK)
        responseBase.Data = result.Value;

    byte[] data = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(responseBase));

    context.HttpContext.Response.StatusCode = statusCode;
    context.HttpContext.Response.ContentType = contentType;

    await context.HttpContext.Response.Body.WriteAsync(data.AsMemory(0, data.Length));
}
  1. Modify the API method to return the value with IActionResult type.
[HttpGet("getData")]
public async Task<IActionResult> GetData()
{
    return new ObjectResult(await _configurationService.GetRelatedData());
}
  1. Register ResultFilter as global filter to controller in Program.cs.
builder.Services.AddControllers(options =>
{
    options.Filters.Add<ResultFilter>();
});

Note: The ResultFilter isn't a complete solution, while implementing you should consider different scenarios of IActionResult such as BadRequestObjectResult etc.


Reference

Result Filter in ASP.NET CORE MVC

  • Related