Mondial de la gestion des exceptions dans OWIN middleware

j'essaie de créer un traitement/rapport d'erreur unifié dans ASP.NET Web API 2.1 projet construit sur le dessus D'OWIN middleware (IIS HOST using Owin.Hôte.SystemWeb). Actuellement, j'ai utilisé un logger d'exception personnalisé qui hérite de System.Web.Http.ExceptionHandling.ExceptionLogger et utilise NLog pour enregistrer 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
    } 
}

Le je veux changer le corps de la réponse pour toutes les API des exceptions à un sympathique réponse unifiée qui cache tous les détails de l'exception à l'aide de System.Web.Http.ExceptionHandling.ExceptionHandler comme le code suivant:

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 ceci retournera la réponse ci-dessous pour le client quand 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 n'importe quelle exception se produit w ithin an Api Controller request pipeline .

mais dans ma situation j'utilise le middleware Microsoft.Owin.Security.OAuth pour générer des jetons au porteur, et ce middleware ne connaît rien à l'exception de L'API Web manipulation, donc par exemple si une exception a été lancée dans la méthode ValidateClientAuthentication mon NLogExceptionLogger pas ContentNegotiatedExceptionHandler saura quoi que ce soit au sujet de cette exception ni essayer de la manipuler, le code d'échantillon que j'ai utilisé dans le AuthorizationServerProvider est comme ci-dessous:

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

donc, je vais apprécier toute orientation dans la mise en œuvre des 2 questions suivantes:

1-Créer un gestionnaire d'exception global qui ne gère que les exceptions générées par OWIN middle marchandises ? J'ai suivi cette réponse et j'ai créé un middleware pour le traitement des exceptions et je l'ai enregistré comme le premier et j'ai été capable de faire des exceptions log provenues de "OAuthAuthorizationServerProvider", mais je ne suis pas sûr que ce soit la meilleure façon de le faire.

2-Maintenant, quand j'ai mis en œuvre la journalisation comme l'étape précédente, je n'ai vraiment aucune idée comment changer la réponse de l'exception que je dois retourner à la client un modèle JSON standard pour toute exception survenant dans le"OAuthAuthorizationServerProvider". Il ya un réponse ici j'ai essayé de dépendre de, mais il n'a pas fonctionné.

voici ma classe de démarrage et la coutume GlobalExceptionMiddleware que j'ai créée pour la capture d'exception/journalisation. La paix manquante renvoie une réponse JSON unifiée pour toute exception. Toutes les idées seront appréciées.

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);
        }
    }
}
27
demandé sur Community 2015-12-27 13:18:06

2 réponses

Ok, donc c'était plus facile que prévu, merci pour @Khalid pour les mises en garde, j'ai fini par créer un middleware owin nommé OwinExceptionHandlerMiddleware qui est dédié pour gérer toute exception se produisant dans un Middleware Owin (journalisation et manipulation de 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 suit:

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>();
    }
}
29
répondu Taiseer Joudeh 2017-06-29 21:14:37

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

  1. créer middleware qui est enregistré d'abord , puis toutes les exceptions va bulle jusqu'à cet middleware. À ce stade, vous n'avez qu'à écrire votre JSON via l'objet Response via le contexte OWIN.

  2. vous pouvez également créer un middleware enveloppant qui enveloppe l'autre middleware. Dans ce cas, il sera sur les erreurs de capture provenant de ce chemin de code spécifique.

en fin de Compte, écrire votre message JSON, c'est le créer, le sérialiser, et l'écrire à la réponse via le contexte OWIN.

il semble que vous êtes sur la bonne voie avec le #1. Espérons que cela aide, et bonne chance :)

7
répondu Khalid Abuhakmeh 2015-12-27 15:42:24