ASP.net : Response.Redirect – Attention au piège. “Thread was being aborted.” / “Le thread a été abandonné.” - l’exception ThreadAbortException
La méthode Response.Redirect permet de rediriger un utilisateur vers une URL, pour cela ASP.net utilise le code d’erreur HTTP 302.
Je me suis récemment retrouvé face à un scénario où cette méthode m’a posé problème.
Au niveau de mon application, je lance une opération complexe utilisant des transactions et redirigeant l’utilisateur lorsque le traitement se déroule avec succès. En cas d’erreur, je fais un rollback sur mes transactions. Ci-dessous, le pseudo code illustrant le problème :
try {
// Complex operation call which use transaction
Response.Redirect("otherPage.aspx");
}
catch (Exception) {
// transaction.rollback()
throw;
}
Mon problème est que dans tous les cas le rollback est exécuté, c’est à dire que le code passe dans le catch, cependant rien n’est affiché au client.
Après quelques minutes de debug, je me suis rendu compte que le message d’erreur était “Thread was being aborted.” ou bien en français “Le thread a été abandonné.”. Ce problème vient du fonctionnement interne du Response.Redirect, en effet celui-ci lance une exception afin de stoper la requête active.
Voyons comment Response.Redirect fonctionne.
Response.Redirect possède 2 signatures, l’une qui nécessite seulement une url, et l’autre qui possède en plus un paramètre endResponse. Voici le pseudo code de la méthode Redirect.
public void Redirect(string url) {
this.Redirect(url, true);
}
public void Redirect(string url, bool endResponse) {
// writing some HTML and 302 status code
if(endResponse){
Response.End();
}
}
Le paramètre endResponse permet de mettre fin à l’exécution de la requête, pour cela ASP.net appelle la méthode Response.End qui met fin au thread courant via la méthode Thread.CurrentThread.Abort. C’est dans cette dernière qu’une exception de type ThreadAbortException est levé. Cette exception est cependant déclenché après l’envoie de la réponse au client, c’est pour cela que l’on ne voit aucune erreur coté client.
Pour résoudre ce soucis, le plus simple est d’ignorer les exceptions de type ThreadAbortException.
try {
// Complex operation call which use transaction
Response.Redirect("otherPage.aspx");
}
catch (ThreadAbortException) {/* do nothing, it might be a response.Redirect exception */}
catch (Exception) {
// transaction.rollback
}
Bien sur, ce bout de code n’est pas optimal, cela risque de cacher les vrais exceptions de type ThreadAbortException, celles-ci sont plutôt rares, on peut généralement les ignorer.
Cependant, si vous voulez faire les choses un peu plus proprement, il est possible d’analyser le callstack afin de savoir de quelle méthode provient l’exception.
private static MethodInfo _redirectMethod = typeof(HttpResponse)
.GetMethod("Redirect", new Type[] { typeof(String), typeof(Boolean) });
// ...
try {
Response.Redirect("otherPage.aspx");
}
catch (Exception ex) {
if (ex is ThreadAbortException) {
Boolean ignore = false;
// use with caution if internal implementation change,
// this solution can be broken
// a little expensive - see JBEvain's comment
StackTrace stackTrace = new StackTrace(ex);
for (int i = stackTrace.FrameCount - 1; i > 0; i--) {
StackFrame frame = stackTrace.GetFrame(i);
if (frame.GetMethod() == _redirectMethod) {
ignore = true;
break;
}
}
if (ignore) {
Debug.WriteLine("Redirect exception");
return;
}
}
Debug.WriteLine("Not a redirect exception");
}
Comme nous l’a fait remarquer JBEvain dans les commentaires, cette solution n’est pas idéale. L’analyse du StackTrace est un peu couteuse et surtout si l’implémentation interne du Redirect change, il se peut que le code ne fonctionne plus. De plus, cela rend le code moins lisible, cette dernière solution est donc à utiliser seulement si vous en avez vraiment besoin.
Des questions ?