让我与您分享我们在模型绑定错误的情况下自定义响应的实现。
首先,让我们定义一个接口,其中包含一个可以传递给InvalidModelStateResponseFactory的方法:
public interface IModelBindingErrorHandler
{
IActionResult HandleInvalidModelState(ActionContext context);
}
让我们继续定义两个模型。一个用于记录,另一个用于响应:
public class InvalidInputModel
{
public string FieldName { get; init; }
public string[] Errors { get; init; }
public override string ToString() => $"{FieldName}: {string.Join("; ", Errors)}";
}
public class GlobalErrorModel
{
public string ErrorMessage { get; init; }
public string ErrorTracingId { get; init; }
}
如您所见,它们都足够通用,也可以在其他错误处理程序中使用。
现在让我们实现IModelBindingErrorHandler 接口:
public class ModelBindingErrorHandler : IModelBindingErrorHandler
{
private ILogger<ModelBindingErrorHandler> logger;
public ModelBindingErrorHandler(ILogger<ModelBindingErrorHandler> logger)
=> this.logger = logger;
public IActionResult HandleInvalidModelState(ActionContext context)
{
var modelErrors = context.ModelState
.Where(stateEntry => stateEntry.Value.Errors.Any())
.Select(stateEntry => new InvalidInputModel
{
FieldName = stateEntry.Key,
Errors = stateEntry.Value.Errors.Select(error => error.ErrorMessage).ToArray()
});
var traceId = Guid.NewGuid();
logger.LogError("Invalid input model has been captured. ModelState: {modelErrors}, TraceId: {traceId}", modelErrors, traceId);
return new BadRequestObjectResult(new GlobalErrorModel
{
ErrorMessage = "Sorry, the request contains invalid data. Please revise.",
ErrorTracingId = traceId.ToString()
});
}
}
- 所以,我们在这里基本上收集了所有有价值的信息 (
Errors),并且我们正在记录它们
- 我们使用
traceId 将日志条目与响应连接起来
- 为了简单起见,我在这里使用了
Guid.NewGuid() 而不是correlationId
为了方便使用这个实现,这里有两种自我注册的扩展方法:
public static class ModelBindingErrorHandlerRegister
{
public static IServiceCollection AddModelBinderErrorHandler(this IServiceCollection services)
{
return AddModelBinderErrorHandler<ModelBindingErrorHandler>(services);
}
public static IServiceCollection AddModelBinderErrorHandler<TImpl>(this IServiceCollection services)
where TImpl : class, IModelBindingErrorHandler
{
services.AddSingleton<IModelBindingErrorHandler, TImpl>();
var serviceProvider = services.BuildServiceProvider();
var handler = serviceProvider.GetService<IModelBindingErrorHandler>();
services.Configure((ApiBehaviorOptions options) =>
options.InvalidModelStateResponseFactory = handler.HandleInvalidModelState);
return services;
}
}
- 第一种方法注册了上面的实现
- 第二种方法允许在需要时注册一个自定义方法
-
InvalidModelStateResponseFactory 分配也可以在 PostConfigure 内完成
有了这些,我们可以用一行代码注册一个自定义模型绑定器处理程序:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddModelBinderErrorHandler();
...
}