OWIN 中间件中的全局异常处理
Posted
技术标签:
【中文标题】OWIN 中间件中的全局异常处理【英文标题】:Global exception handling in OWIN middleware 【发布时间】:2016-04-01 11:21:19 【问题描述】:我正在尝试在基于 OWIN 中间件(使用 Owin.Host.SystemWeb 的 IIS HOST)之上构建的 ASP.NET Web API 2.1 项目中创建统一的错误处理/报告。
目前我使用了一个自定义异常记录器,它继承自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, error has been reported!",
"Details": "Ooops!",
"ErrorReference": "56627a45d23732d2",
"DateTime": "2015-12-27T09:42:40.2982314Z"
现在,如果 在 Api 控制器请求管道中发生任何异常,这一切都很好。
但在我的情况下,我使用中间件 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 - 现在,当我在上一步中实现日志记录时,我真的不知道如何更改异常的响应,因为我需要向客户端返回标准 JSON 模型,以应对“ OAuthAuthorizationServerProvider”。有一个相关的 answer here 我试图依赖但它没有用。
这是我的 Startup 类和我为异常捕获/记录创建的自定义 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);
【问题讨论】:
可以直接在中间件(response.write)中写入响应。如果您想使用其他方法,请检查 global.asax Application_Error 【参考方案1】:好的,所以这比预期的要容易,感谢 @Khalid 的提醒,我最终创建了一个名为 OwinExceptionHandlerMiddleware
的 owin 中间件,它专门用于处理任何 Owin 中间件中发生的任何异常(记录并操作返回给客户端之前的响应)。
您需要将此中间件注册为Startup
类中的第一个,如下所示:
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()
;
//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>();
【讨论】:
原来这是你必须自己写的东西。这里有一个很好的例子:github.com/filipw/apress-recipes-webapi/blob/master/… 我想知道:你为什么停止从OwinMiddleware
继承?既然您没有从 OwinExceptionHandlerMiddleware
中的任何内容继承,那么现在如何调用 Invoke()
方法?
@Zero3,请参阅下面的link,了解编写 OWIN 中间件的 5 种不同方法。
@Pascal 据我记得'NLogLogger' 是一个静态类,我可以直接引用方法'LogError',我没有注入它。如果你不能这样做,我会尝试检查这个项目的源代码并回复你。如果您需要进一步的帮助,请告诉我。
我尝试了您的方法,但仍然无法达到 OWIN 在 Web Api 中引发的异常。似乎 Api 会自动处理它或吞下它......【参考方案2】:
有几种方法可以做你想做的事:
首先创建已注册的中间件,然后所有异常都会冒泡到该中间件。此时只需通过 OWIN 上下文通过 Response 对象写出您的 JSON。
您还可以创建一个包装中间件来包装 Oauth 中间件。在这种情况下,它将捕获源自此特定代码路径的错误。
最终编写您的 JSON 消息是关于创建它、序列化它并通过 OWIN 上下文将其写入响应。
看来你在#1 的道路上是正确的。希望这会有所帮助,祝你好运:)
【讨论】:
【参考方案3】:接受的答案过于复杂,并且不继承自 OwinMiddleware 类
您需要做的就是:
public class HttpLogger : OwinMiddleware
public HttpLogger(OwinMiddleware next) : base(next)
public override async Task Invoke(IOwinContext context)
await Next.Invoke(context);
Log(context)
另外,不需要创建扩展方法..它很简单,无需引用
appBuilder.Use(typeof(HttpErrorLogger));
如果您只想记录特定请求,您可以过滤上下文属性:
例如:
if (context.Response.StatusCode != 200) Log(context)
【讨论】:
以上是关于OWIN 中间件中的全局异常处理的主要内容,如果未能解决你的问题,请参考以下文章
Configure中间件与ErrorHandlingMiddleware全局异常捕获