web-dev-qa-db-fra.com

Gestion globale des exceptions dans le middleware OWIN

J'essaie de créer un traitement/rapport d'erreurs unifié dans ASP.NET Web API 2.1 Project construit sur le middleware OWIN (hôte IIS utilisant Owin.Host.SystemWeb). Actuellement, j'ai utilisé un enregistreur d'exceptions personnalisé qui hérite de System.Web.Http.ExceptionHandling.ExceptionLogger et utilise NLog pour consigner toutes les exceptions comme le code ci-dessous:

public class NLogExceptionLogger : ExceptionLogger
{

    private static readonly Logger Nlog = LogManager.GetCurrentClassLogger();
    public override void Log(ExceptionLoggerContext context)
    {
       //Log using NLog
    } 
}

Je souhaite changer le corps de réponse pour toutes les exceptions d'API en une réponse unifiée conviviale qui masque tous les détails d'exception à l'aide de System.Web.Http.ExceptionHandling.ExceptionHandler comme le code ci-dessous:

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

Et cela retournera la réponse ci-dessous pour le client lorsqu'une exception se produit:

{
  "Message": "Internal server error occurred, error has been reported!",
  "Details": "Ooops!",
  "ErrorReference": "56627a45d23732d2",
  "DateTime": "2015-12-27T09:42:40.2982314Z"
}

Maintenant, cela fonctionne très bien si une exception se produit w dans un pipeline de demande de contrôleur Api .

Mais dans ma situation, j'utilise le middleware Microsoft.Owin.Security.OAuth pour générer des jetons au porteur, et ce middleware ne sait rien de la gestion des exceptions de l'API Web, donc par exemple si une exception a été lancée dans la méthode ValidateClientAuthentication my NLogExceptionLogger not ContentNegotiatedExceptionHandler ne saura rien de cette exception et n'essaiera pas de la gérer, l'exemple de code que j'ai utilisé dans le AuthorizationServerProvider est le suivant:

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

J'apprécierai donc toute orientation dans la mise en œuvre des 2 problèmes ci-dessous:

1 - Créer un gestionnaire d'exceptions global qui ne gère que les exceptions générées par les produits intermédiaires OWIN ? J'ai suivi cette réponse et créé un middleware à des fins de gestion des exceptions et je l'ai enregistré en tant que premier et j'ai pu créer des exceptions de journal provenant de "OAuthAuthorizationServerProvider", mais je ne suis pas sûr que ce soit le façon optimale de le faire.

2 - Maintenant, quand j'ai implémenté la journalisation comme à l'étape précédente, je n'ai vraiment aucune idée de comment changer la réponse de l'exception car j'ai besoin de retourner au client un modèle JSON standard pour toute exception se produisant dans le "OAuthAuthorizationServerProvider". Il y a un --- réponse ici J'ai essayé de dépendre mais cela n'a pas fonctionné.

Voici ma classe de démarrage et le GlobalExceptionMiddleware personnalisé que j'ai créé pour la capture/journalisation des exceptions. La paix manquante renvoie une réponse JSON unifiée pour toute exception. Toutes les idées sont les bienvenues.

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);
        }
    }
}
32
Taiseer Joudeh

Ok, donc c'était plus facile que prévu, merci pour @Khalid pour la tête, j'ai fini par créer un middleware owin nommé OwinExceptionHandlerMiddleware qui est dédié à la gestion de toute exception se produisant dans n'importe quel middleware Owin (journalisation et manipulation) la réponse avant de la retourner au client).

Vous devez enregistrer ce middleware comme le premier dans la classe Startup comme ci-dessous:

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

Et le code utilisé dans le OwinExceptionHandlerMiddleware comme ci-dessous:

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>();
    }
}
34
Taiseer Joudeh

Il y a plusieurs façons de faire ce que vous voulez:

  1. Créez un middleware enregistré en premier, puis toutes les exceptions remonteront jusqu'à ce middleware. À ce stade, écrivez simplement votre JSON via l'objet Response via le contexte OWIN.

  2. Vous pouvez également créer un middleware d'encapsulation qui encapsule le Oauth middleware. Dans ce cas, il capturera les erreurs provenant de ce chemin de code spécifique.

En fin de compte, l'écriture de votre message JSON consiste à le créer, à le sérialiser et à l'écrire dans la réponse via le contexte OWIN.

Il semble que vous soyez sur la bonne voie avec le n ° 1. J'espère que ça t'aide et bonne chance :)

7
Khalid Abuhakmeh