Я пытаюсь создать единую систему обработки ошибок / отчетов в проекте ASP.NET Web API 2.1, построенном на промежуточном программном обеспечении OWIN (IIS HOST с использованием Owin.Host.SystemWeb). В настоящее время я использую настраиваемый регистратор исключений, который наследуется от 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 для генерации токенов-носителей, и это промежуточное программное обеспечение ничего не знает об обработке исключений веб-API, поэтому, например, если исключение было выброшено в методе {{X1} } мой 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);
    }
}

Поэтому я буду признателен за любые рекомендации по реализации следующих двух проблем:

1. Создать глобальный обработчик исключений, который обрабатывает только исключения, сгенерированные промежуточным оборудованием OWIN ? Я выполнил этот ответ и создал промежуточное ПО для обработки исключений и зарегистрировал он был первым, и мне удалось зарегистрировать исключения, исходящие от «OAuthAuthorizationServerProvider», но я не уверен, что это оптимальный способ сделать это.

2 - Теперь, когда я реализовал ведение журнала как на предыдущем шаге, я действительно понятия не имею, как изменить ответ исключения, поскольку мне нужно вернуть клиенту стандартную модель JSON для любого исключения, происходящего в «OAuthAuthorizationServerProvider». Здесь есть связанный ответ, на который я пытался положиться, но это не сработало.

Вот мой класс 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);
        }
    }
}
35
Taiseer Joudeh 27 Дек 2015 в 13:18

2 ответа

Лучший ответ

Хорошо, так что это было проще, чем ожидалось, спасибо @Khalid за внимание, я закончил тем, что создал промежуточное ПО owin с именем OwinExceptionHandlerMiddleware, которое предназначено для обработки любых исключений, происходящих в любом промежуточном ПО 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>();
    }
}
39
BanksySan 29 Июн 2017 в 21:14

Есть несколько способов сделать то, что вы хотите:

  1. Создайте промежуточное ПО, которое будет зарегистрировано первым , затем все исключения будут перемещаться в это промежуточное ПО. На этом этапе просто напишите свой JSON через объект Response через контекст OWIN.

  2. Вы также можете создать промежуточное ПО-оболочку, которое обертывает промежуточное ПО Oauth. В этом случае он будет обнаруживать ошибки, возникающие из этого конкретного пути кода.

В конечном итоге написание сообщения JSON - это его создание, сериализация и запись в ответ через контекст OWIN.

Похоже, вы на правильном пути с №1. Надеюсь, что это помогает и удачи :)

8
Khalid Abuhakmeh 27 Дек 2015 в 15:42