ASP.NET核心WebAPI捕获模型绑定异常



我正在努力寻找一种方法来捕获模型属性(实际上是其类型struct(引发的异常,该异常必须绑定到POST请求体数据。

我有一个一般的场景,需要处理非常特定的数据类型,所以我使用structs来对它们进行相应的验证。

尽管以下代码只是草案,但欢迎所有建议!

下面是一个Controller:的例子

[ApiController]
[TypeFilter(typeof(CustomExceptionFilter))]
public class OrdersController : ControllerBase
{
public OrdersController(ILogger<OrdersController> logger, IDataAccess dataAccess)
{
_dataAccess = dataAccess;
_logger = logger;
}
private readonly IDataAccess _dataAccess;
private readonly ILogger<OrdersController> _logger;

[EnableCors]
[Route("api/[controller]/Sales")]
[HttpPost]
public async Task<ActionResult> PostSaleAsync(
[FromBody] SaleOrder saleOrder)
{
try
{
Guid saleOrderId = Guid.NewGuid();
saleOrder.SaleOrderId = saleOrderId;
foreach (SaleOrderItem item in saleOrder.items)
item.SaleOrderId = saleOrderId;
OrderQuery query = new OrderQuery(_dataAccess);
await query.SaveAsync(saleOrder);
_dataAccess.Commit();
var response = new
{
Error = false,
Message = "OK",
Data = new
{
SaleOrderId = saleOrderId
}
};
return Ok(response);
}
catch (DataAccessException)
{
_dataAccess.Rollback();
//[...]
}
//[...]
}
}

以及模型Order和结构StockItemSerialNumber:的示例

public class SaleOrder : Order
{
public Guid SaleOrderId { get => OrderId; set => OrderId = value; }
public Guid CustomerId { get => StakeholderId; set => StakeholderId = value; }
public Guid? SellerId { get; set; }
public SaleModelType SaleModelType { get; set; }
public SaleOrderItem[] items { get; set; }
}
public class SaleOrderItem : OrderItem
{
public Guid SaleOrderId { get; set; }
public StockItemSerialNumber StockItemSerialNumber { get; set; }
//[JsonConverter(typeof(StockItemSerialNumberJsonConverter))]
//public StockItemSerialNumber? StockItemSerialNumber { get; set; }
}
public struct StockItemSerialNumber
{
public StockItemSerialNumber(string value)
{
try
{
if ((value.Length != 68) || Regex.IsMatch(value, @"[^w]"))
throw new ArgumentOutOfRangeException("StockItemSerialNumber");
_value = value;
}
catch(RegexMatchTimeoutException)
{
throw new ArgumentOutOfRangeException("StockItemSerialNumber");
}
}
private string _value;
public static implicit operator string(StockItemSerialNumber value) => value._value;
public override string ToString() => _value;
}

我想捕获StockItemSerialNumber结构抛出的ArgumentOutOfRangeException,然后返回一条相应的响应消息,通知自定义错误。

由于Controller中的try...catch块无法捕获此异常,因此我尝试构建一个扩展IExceptionFilter并添加为筛选器的类:

public class CustomExceptionFilter : IExceptionFilter
{
private readonly IWebHostEnvironment _hostingEnvironment;
private readonly IModelMetadataProvider _modelMetadataProvider;
public CustomExceptionFilter(
IWebHostEnvironment hostingEnvironment,
IModelMetadataProvider modelMetadataProvider)
{
_hostingEnvironment = hostingEnvironment;
_modelMetadataProvider = modelMetadataProvider;
}
public void OnException(ExceptionContext context)
{
context.Result = new BadRequestObjectResult(new {
Error = false,
Message = $"OPS! Something bad happened, Harry :( [{context.Exception}]."
});
}
}

Startup.cs:

public void ConfigureServices(IServiceCollection services)
{
services.AddCors(options =>
// [...]
});
services.AddTransient<IDataAccess>(_ => new DataAccess(Config.DBCredentials));
services.AddControllers(options => options.Filters.Add(typeof(CustomExceptionFilter)));
}

但这种方法也不起作用,我的意思是,ArgumentOutOfRangeException仍然没有被捕获,并在POST请求期间崩溃执行。

最后,这里有一个带有请求数据的JSON示例:

{
"CustomerId":"fb2b0555-6d32-404b-b2f0-50032a7e0f59",
"SellerId":null,
"items": [
{
"StockItemSerialNumber":"22B6E75510AB459B8DB2874F20C722B6F3DC19C6E474337D5F73BB87699E9A1001"
}, // Invalid
{
"StockItemSerialNumber":"022B6E755122B659B8DB2874F20C780030F3DC19C6E47465AS1673BB87699E9A1001"
}  // Valid
]
}

所以我非常感谢任何帮助或建议!谢谢

根据ASP.NET Core for.NET 6的文档,您现在应该能够使用异常过滤器来处理模型绑定期间抛出的异常:

[Exception filters]处理Razor Page或控制器创建、模型绑定、操作筛选器或操作方法中发生的未处理异常。[它们]不会捕获资源筛选器、结果筛选器或MVC结果执行中发生的异常。

我已经测试过了,它对我有用,使用这个类:

public class UnhandledExceptionFilter: IExceptionFilter
{
/// <inheritdoc/>
public void OnException(ExceptionContext argContext) {
// we have nothing to do if no exception information is provided (this should not happen).
if(argContext.Exception is null) return;
// we also have nothing to do if an exception was thrown but has been handled.
if(argContext.ExceptionHandled) return;
// at this point we know that an exception was thrown and it hasn't been handled
// so return our standard error response and mark the exception as handled so that
// ASP.NET doesn't return an Internal Server Error response.
argContext.Result = new OkObjectResult(new BaseResponse(argContext.Exception));
argContext.ExceptionHandled = true;
}
}

在我的代码中,BaseResponse是所有API响应对象继承的类。当它被赋予一个Exception对象时,它会设置响应中的所有相关字段,以指示发生了错误。

最新更新