c# - 将响应与异常一起包装的正确方法
问题描述
我有一个看起来像这样的控制器:
[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);
}
}
我还使用自定义中间件实现了同样的事情,其中有很多关于堆栈溢出的发现。
我使用中间件的实现:
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# - 从最后一个场景开始游戏 Unity 3D
- c++ - 得到不同字节序的结果
- oracle - 更改 Oracle APEX 搜索占位符文本
- java - 如何使 JSpinner 中的 TextField 不可编辑,以便用户只能使用向上和向下按钮?
- java - 在首次启动时检查 Android 是否拥有应用内购买
- mercurial - 如何 hg bundle 仅在分支中更改?
- c# - 无法获取 WPF DataGrid 单元格中的单元格详细信息
- c# - C# EPPlus 不计算公式 SUM(A3:B3)
- python - 如何让 python 数据类继承 __hash__?
- java - 如何组合三个连续的 Modbus 寄存器得到一个整数值?