OWIN中间件中的全局异常处理

时间:2015-12-27 10:18:06

标签: asp.net-web-api asp.net-web-api2 owin katana owin-middleware

我正在尝试在构建于OWIN中间件(使用Owin.Host.SystemWeb的IIS HOST)之上的ASP.NET Web API 2.1项目中创建统一的错误处理/报告。 目前我使用了一个自定义异常记录器,它继承自private String description; private String dailogdemovideo; private String downloadCode; public void setDailogdemovideo(String dailogdemovideo) { this.dailogdemovideo = dailogdemovideo; } public String getDailogdemovideo() { return dailogdemovideo; } public void setDownloadCode(String downloadCode) { this.downloadCode = downloadCode; } public String getDownloadCode() { return downloadCode; } public void setDescription(String description) { this.description = description; } public String getDescription() { return description; } } 并使用NLog记录所有异常,如下面的代码所示:

System.Web.Http.ExceptionHandling.ExceptionLogger

我想将所有API异常的响应正文更改为友好的统一响应,该响应使用public class NLogExceptionLogger : ExceptionLogger { private static readonly Logger Nlog = LogManager.GetCurrentClassLogger(); public override void Log(ExceptionLoggerContext context) { //Log using NLog } } 隐藏所有异常详细信息,如下所示:

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);
    }
}

现在,如果在Api Controller请求管道中发生任何异常,这一切都很有效。

但在我的情况下,我使用中间件{ "Message": "Internal server error occurred, error has been reported!", "Details": "Ooops!", "ErrorReference": "56627a45d23732d2", "DateTime": "2015-12-27T09:42:40.2982314Z" } 来生成承载令牌,而这个中间件对Web API异常处理一无所知,例如,如果在方法{中抛出异常{1}}我的Microsoft.Owin.Security.OAuth而不是ValidateClientAuthentication会知道有关此异常的任何内容,也不会尝试处理它,我在NLogExceptionLogger中使用的示例代码如下所示:

ContentNegotiatedExceptionHandler

因此,我将理解实施以下2个问题的任何指导:

1 - 创建一个全局异常处理程序,只处理由OWIN中间件产生的异常?我跟着this answer创建了一个用于异常处理目的的中间件,并将其注册为第一个,我能够执行源自“OAuthAuthorizationServerProvider”的日志异常,但我确定这是否是最佳方法它

2 - 现在,当我按照上一步实现日志记录时,我真的不知道如何更改异常的响应,因为我需要返回客户端标准的JSON模型,以便在“ OAuthAuthorizationServerProvider”。我试图依赖一个相关的answer here,但它没有用。

这是我的Startup类和我为异常捕获/日志记录创建的自定义AuthorizationServerProvider。失踪的和平正在为任何异常返回统一的JSON响应。任何想法将不胜感激。

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);
    }
}

3 个答案:

答案 0 :(得分:31)

好的,所以这比预期的要容易,感谢@Khalid的提升,我最终创建了一个名为OwinExceptionHandlerMiddleware的owin中间件,专门用于处理任何Owin Middleware中发生的异常(记录它)并在将响应返回给客户端之前操纵响应。)

您需要将此中间件注册为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>();
    }
}

答案 1 :(得分:7)

有几种方法可以做你想做的事:

  1. 创建首先注册 的中间件,然后所有异常都会冒泡到该中间件。此时,只需通过响应对象通过OWIN上下文写出你的JSON。

  2. 您还可以创建一个包装Oauth中间件的包装中间件。在这种情况下,它将捕获源自此特定代码路径的捕获​​错误。

  3. 最终编写JSON消息是关于创建它,序列化它,并通过OWIN上下文将它写入Response。

    看起来你在#1的正确道路上。希望这会有所帮助,祝你好运:)

答案 2 :(得分:0)

接受的答案不必要地复杂,并且不继承自 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) }