目前,我使用了一个自定义的异常记录器,它继承自System.Web.Http.ExceptionHandling.ExceptionLogger,并使用NLog将所有异常作为下面的代码记录:
public class NLogExceptionLogger : ExceptionLogger { private static readonly Logger Nlog = LogManager.GetCurrentClassLogger(); public override void Log(ExceptionLoggerContext context) { //Log using NLog } }
我想将所有API异常的响应体更改为友好的统一响应,它将使用System.Web.Http.ExceptionHandling.ExceptionHandler隐藏所有异常详细信息作为以下代码:
public class ContentNegotiatedExceptionHandler : ExceptionHandler { public override void Handle(ExceptionHandlerContext context) { var errorDataModel = new ErrorDataModel { Message = "Internal server error occurred,error has been reported!",Details = context.Exception.Message,ErrorReference = context.Exception.Data["ErrorReference"] != null ? context.Exception.Data["ErrorReference"].ToString() : string.Empty,DateTime = DateTime.UtcNow }; var response = context.Request.CreateResponse(HttpStatusCode.InternalServerError,errorDataModel); context.Result = new ResponseMessageResult(response); } }
当异常发生时,这将返回以下客户端的响应:
{ "Message": "Internal server error occurred,"Details": "Ooops!","ErrorReference": "56627a45d23732d2","DateTime": "2015-12-27T09:42:40.2982314Z" }
现在,如果在Api Controller请求管道中发生任何异常,这一切都会很好。
但是在我的情况下,我正在使用中间件Microsoft.Owin.Security.OAuth来生成承载令牌,而这个中间件并不知道有关Web API异常处理的任何内容,所以例如如果异常已经被抛出在ValidateClientAuthentication方法中NLogExceptionLogger没有ContentNegotiatedExceptionHandler将知道有关此异常的任何操作,也不会尝试处理它,AuthorizationServerProvider中使用的示例代码如下所示:
public class AuthorizationServerProvider : OAuthAuthorizationServerProvider { public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context) { //Expcetion occurred here int x = int.Parse(""); context.Validated(); return Task.FromResult<object>(null); } public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context) { if (context.UserName != context.Password) { context.SetError("invalid_credentials","The user name or password is incorrect."); return; } var identity = new ClaimsIdentity(context.Options.AuthenticationType); identity.AddClaim(new Claim(ClaimTypes.Name,context.UserName)); context.Validated(identity); } }
所以我会感谢执行下面2个问题的任何指导:
1 – 创建一个全局异常处理程序,仅处理由OWIN中间商产生的异常?我跟着this answer,创建了一个异常处理目的的中间件,并将其注册为第一个,并且我能够执行源自“OAuthAuthorizationServerProvider”的日志异常,但是我不知道这是否是最佳的方法。
2 – 现在当我在上一步中实现日志记录时,我真的不知道如何更改异常的响应,因为在“OAuthAuthorizationServerProvider”中发生异常时,我需要向客户端返回一个标准的JSON模型。有一个相关的answer here我试图依赖,但它没有工作。
这是我的启动类和我为异常捕获/记录创建的自定义GlobalExceptionMiddleware。失踪的和平正在为任何异常返回统一的JSON响应。任何想法将不胜感激。
public class Startup { public void Configuration(IAppBuilder app) { var httpConfig = new HttpConfiguration(); httpConfig.MapHttpAttributeRoutes(); httpConfig.Services.Replace(typeof(IExceptionHandler),new ContentNegotiatedExceptionHandler()); httpConfig.Services.Add(typeof(IExceptionLogger),new NLogExceptionLogger()); OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions() { AllowInsecureHttp = true,TokenEndpointPath = new PathString("/token"),AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),Provider = new AuthorizationServerProvider() }; app.Use<GlobalExceptionMiddleware>(); app.USEOAuthAuthorizationServer(OAuthServerOptions); app.USEOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions()); app.UseWebApi(httpConfig); } } public class GlobalExceptionMiddleware : OwinMiddleware { public GlobalExceptionMiddleware(OwinMiddleware next) : base(next) { } public override async Task Invoke(IOwinContext context) { try { await Next.Invoke(context); } catch (Exception ex) { NLogLogger.LogError(ex,context); } } }
解决方法
您需要将这个中间件注册为Startup类中的第一个,如下所示:
public class Startup { public void Configuration(IAppBuilder app) { var httpConfig = new HttpConfiguration(); httpConfig.MapHttpAttributeRoutes(); httpConfig.Services.Replace(typeof(IExceptionHandler),Provider = new AuthorizationServerProvider() }; //Should be the first handler to handle any exception happening in OWIN middlewares app.USEOwinExceptionHandler(); // Token Generation app.USEOAuthAuthorizationServer(OAuthServerOptions); app.USEOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions()); app.UseWebApi(httpConfig); } }
而在OwinExceptionHandlerMiddleware中使用的代码如下所示:
using AppFunc = Func<IDictionary<string,object>,Task>; public class OwinExceptionHandlerMiddleware { private readonly AppFunc _next; public OwinExceptionHandlerMiddleware(AppFunc next) { if (next == null) { throw new ArgumentNullException("next"); } _next = next; } public async Task Invoke(IDictionary<string,object> environment) { try { await _next(environment); } catch (Exception ex) { try { var owinContext = new OwinContext(environment); NLogLogger.LogError(ex,owinContext); HandleException(ex,owinContext); return; } catch (Exception) { // If there's a Exception while generating the error page,re-throw the original exception. } throw; } } private void HandleException(Exception ex,IOwinContext context) { var request = context.Request; //Build a model to represet the error for the client var errorDataModel = NLogLogger.BuildErrorDataModel(ex); context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; context.Response.ReasonPhrase = "Internal Server Error"; context.Response.ContentType = "application/json"; context.Response.Write(JsonConvert.SerializeObject(errorDataModel)); } } public static class OwinExceptionHandlerMiddlewareAppBuilderExtensions { public static void USEOwinExceptionHandler(this IAppBuilder app) { app.Use<OwinExceptionHandlerMiddleware>(); } }