首页 > 解决方案 > 将响应与异常一起包装的正确方法

问题描述

我有一个看起来像这样的控制器:

[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("{id}")]
public async Task<ActionResult> GetById(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));
}

在控制器中,在CreateAsync操作中,我尝试返回用消息和数据包装的对象。

Get动作中我抛出了一个NotFoundException.

为了包装返回响应以及异常处理,我尝试了两种不同的方法:

一种方法是继承ObjectResultExecutor,它看起来非常简单明了,我.

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);
    }
}

我还使用自定义中间件实现了同样的事情,其中​​有很多关于堆栈溢出的发现。

  1. 如何包装 Web API 响应(在 .net 核心中)以保持一致性?
  2. 如何阅读 ASP.NET Core Response.Body?

我使用中间件的实现:

using (var memoryStream = new MemoryStream())
{
    //set the current response to the memorystream.
    httpContext.Response.Body = memoryStream;

    await _next(httpContext);

    //reset the body 
    httpContext.Response.Body = currentBody;
    memoryStream.Seek(0, SeekOrigin.Begin);

    var readToEnd = new StreamReader(memoryStream).ReadToEnd();

    var objResult = JsonConvert.DeserializeObject<ApiResponse>(readToEnd);

    _logger.Information($"The Response return from the middleware is " +
        $"{JsonConvert.SerializeObject(objResult)}");

    await httpContext.Response.WriteAsync(JsonConvert.SerializeObject(objResult));
}

目前这两种方法似乎都可以正常工作,但第一种方法对我来说看起来很干净。但我仍然不确定哪种方式好?谁能向我解释这两种情况之间的区别以及它的缺点是什么?

未来使用这两种技术需要考虑哪些可能的变化?

我一直在阅读这个答案,其中第 3 点(缺点)用于在您的操作中明确格式化/包装您的结果,说,

您的操作负责返回格式正确的响应。类似于: return new ApiResponse(obj); 或者您可以创建扩展方法并像 obj.ToResponse() 一样调用它,但您始终必须考虑正确的响应格式。

就我而言,我总是包装我的响应格式。阅读该文档后,我发现我以相同的方式实现它,这说明了这两种技术的缺点。

在 dot-net-core5 中进行这种格式化的正确方法是什么?

我也使用相同的自定义包装器来装饰异常。

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;
    }
}

我想让我的回复以这种 json 格式返回:

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

如果有任何异常消息,它应该进入message字段。

标签: c#.net-coreasp.net-core-5.0

解决方案


推荐阅读