question

AakashBashyal-7213 avatar image
0 Votes"
AakashBashyal-7213 asked ZhiLv-MSFT commented

Proper way of wrapping the response along with exception

I have a controller that looks like this:

 [HttpPost]
 public async Task<ActionResult> CreateAsync(TodoItems item)
 {
     await item.AddNewItemAsync(item.Id, item.Name);
    
     return Ok(new ApiOkResponse(item, $"An item has been added."));
 }
    
 [HttpGet("get")]
 public async Task<ActionResult> Get(int id)
 {        
     TodoItems items = new();
     if (!items.TryGetProduct(id, out var item))
     {
             throw new NotFoundException($"The item with id {id} not found");
      }
    
      return Ok(new ApiOkResponse(item));
 }

Here in the controller, in the CreateAsync action, I have tried to return the object wrapped with messages and data.

And in the Get action, I have thrown a NotFoundException.

To wrap the return responses, along with exception handling I have tried two different ways:

One way is by inheriting the ObjectResultExecutor, which looks so simple and plain, which I have implemented from.

 public class ResponseEnvelopeResultExecutor : ObjectResultExecutor
 {
     public ResponseEnvelopeResultExecutor(OutputFormatterSelector formatterSelector, IHttpResponseStreamWriterFactory writerFactory,
         ILoggerFactory loggerFactory, IOptions<MvcOptions> mvcOptions) : base(formatterSelector, writerFactory, loggerFactory, mvcOptions)
     {
     }
    
     public override Task ExecuteAsync(ActionContext context, ObjectResult result)
     {
         var response = (ApiOkResponse)(result.Value);
    
         TypeCode typeCode = Type.GetTypeCode(result.Value.GetType());
         if (typeCode == TypeCode.Object)
             result.Value = response;
    
         return base.ExecuteAsync(context, result);
     }
 }

I have also implemented the same thing using custom middleware, where there are plenty of findings on the stack overflow.

  1. How can I wrap Web API responses(in .net core) for consistency?

  2. How to read ASP.NET Core Response.Body?

Both ways seem to work fine currently, but the first approach looks clean to me. But I am still not sure which way is good? Can anyone explain to me the difference between both cases and what are its drawbacks?

And what could be the possible changes that need to be considered in the future using both techniques?

I have been reading this answer, wherein point 3 (disadvantages) for Explicitly formatting/wrapping your results in your actions, says,

Your actions are responsible for returning the correctly formatted response. Something like: return new ApiResponse(obj); or you can create extension method and call it like obj.ToResponse() but you always have to think about the correct response format.

In my case, I have always wrap my response format. After reading that document I found that I implemented it in the same way which states the drawbacks of both techniques.

What are the proper ways of doing this formatting in dot-net-core5?

I am also using the same custom wrapper to decorate the Exception as well.

 public class ApiExceptionFilterAttribute : ExceptionFilterAttribute
 {
    
     private readonly IDictionary<Type, Action<ExceptionContext>> _exceptionHandlers;
    
     public ApiExceptionFilterAttribute()
     {
         // Register known exception types and handlers.
         _exceptionHandlers = new Dictionary<Type, Action<ExceptionContext>>
         {
             { typeof(NotFoundException), HandleNotFoundException },
             { typeof(UnauthorizedAccessException), HandleUnauthorizedAccessException },
             { typeof(ForbiddenAccessException), HandleForbiddenAccessException },
         };
     }
    
     public override void OnException(ExceptionContext context)
     {
         HandleException(context);
    
         base.OnException(context);
     }
    
     private void HandleException(ExceptionContext context)
     {
         Type type = context.Exception.GetType();
         if (_exceptionHandlers.ContainsKey(type))
         {
             _exceptionHandlers[type].Invoke(context);
             return;
         }
    
         if (!context.ModelState.IsValid)
         {
             HandleInvalidModelStateException(context);
             return;
         }
    
         HandleUnknownException(context);
     }
    
     private void HandleInvalidModelStateException(ExceptionContext context)
     {
    
         var errorList = (from item in context.ModelState.Values
                          from error in item.Errors
                          select error.ErrorMessage).ToList();
    
         var details = new ApiOkResponse(StatusCodes.Status400BadRequest, String.Join(" ; ", errorList));
         context.Result = new BadRequestObjectResult(details);
    
         context.ExceptionHandled = true;
     }
    
     private void HandleNotFoundException(ExceptionContext context)
     {
         var exception = context.Exception as NotFoundException;
    
         var details = new ApiOkResponse(StatusCodes.Status404NotFound, exception.Message);
    
         context.Result = new NotFoundObjectResult(details);
         context.ExceptionHandled = true;
     }
    
     private void HandleUnauthorizedAccessException(ExceptionContext context)
     {
         var details = new ApiOkResponse(StatusCodes.Status401Unauthorized, "Unauthorized");
    
         context.Result = new ObjectResult(details)
         {
             StatusCode = StatusCodes.Status401Unauthorized
         };
    
         context.ExceptionHandled = true;
     }
    
     private void HandleForbiddenAccessException(ExceptionContext context)
     {
         var details = new ApiOkResponse(StatusCodes.Status403Forbidden, "Forbidden");
    
         context.Result = new ObjectResult(details)
         {
             StatusCode = StatusCodes.Status403Forbidden
         };
    
         context.ExceptionHandled = true;
     }
    
     private void HandleUnknownException(ExceptionContext context)
     {
         var details = new ApiOkResponse(StatusCodes.Status500InternalServerError, "An error occurred while processing your request.");
    
         context.Result = new ObjectResult(details)
         {
             StatusCode = StatusCodes.Status500InternalServerError
         };
    
         context.ExceptionHandled = true;
     }
 }

I want to have my responses return in this json format:

 {
     "statusCode": 200,
     "requestId": "6b801f87-e985-4092-938e-3723e4c6bb50",
     "message": "An item has been added.",
     "result": {
         "id": 0,
         "name": "d"
     }
 }

If there is any exception message it should go into message fields.






dotnet-csharpdotnet-aspnet-core-general
· 1
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

Hi @AakashBashyal-7213,

First, for the two methods, I prefer to use the clean one, because the method which have stacked overflow issue might cause the performance issue.

Second, for apps that require specialized exception handling, the Microsoft document suggests using exceptions to modify the response or customizing the exception handling middleware. You could consider using them.

You could refer the following threads:

ASP.NET Core Web API exception handling

How to wrap a route not found exception with the generic response model

0 Votes 0 ·

0 Answers