Pourquoi Response.Redirect provoque System.Threading.ThreadAbortException?

230

Lorsque j'utilise Response.Redirect (...) pour rediriger mon formulaire vers une nouvelle page, j'obtiens l'erreur:

Une exception de première chance de type «System.Threading.ThreadAbortException» s'est produite dans mscorlib.dll
Une exception de type «System.Threading.ThreadAbortException» s'est produite dans mscorlib.dll mais n'a pas été gérée dans le code utilisateur

Je crois comprendre que l'erreur est due au fait que le serveur Web a abandonné le reste de la page sur laquelle response.redirect a été appelé.

Je sais que je peux ajouter un deuxième paramètre Response.Redirectappelé endResponse. Si je mets endResponse à True, j'obtiens toujours l'erreur, mais si je la mets à False, je ne le fais pas. Je suis à peu près sûr que cela signifie que le serveur Web exécute le reste de la page que j'ai redirigée. Ce qui semble pour le moins inefficace. Y a-t-il une meilleure manière de faire cela? Autre chose Response.Redirectou existe-t-il un moyen de forcer l’ancienne page à arrêter de se charger là où je n’obtiendrai pas ThreadAbortException?

Ben Hoffman
la source

Réponses:

332

Le modèle correct consiste à appeler la surcharge de redirection avec endResponse = false et à effectuer un appel pour indiquer au pipeline IIS qu'il doit passer directement à l'étape EndRequest une fois que vous retournez le contrôle:

Response.Redirect(url, false);
Context.ApplicationInstance.CompleteRequest();

Ce billet de blog de Thomas Marquardt fournit des détails supplémentaires, notamment comment gérer le cas particulier de la redirection à l'intérieur d'un gestionnaire Application_Error.

Joel Fillmore
la source
6
Il exécute le code après Context.ApplicationInstance.CompleteRequest();. Pourquoi? Dois-je returnle soumettre au gestionnaire d'événements de manière conditionnelle?
IsmailS
4
@Ismail: L'ancienne version de Redirect lève une ThreadAbortException pour empêcher l'exécution de tout code ultérieur. La nouvelle version préférée ne lance pas, mais vous êtes responsable de retourner le contrôle tôt si vous avez du code supplémentaire dans le gestionnaire.
Joel Fillmore
12
Je pense qu'il est plus exact de dire "la deuxième surcharge" plutôt que l' The old version of Redirectexpression que vous utilisez dans votre commentaire, ce n'est pas comme si MS a changé la mise en œuvre, c'est juste une autre surcharge.
BornToCode
2
Je ne pense pas que ce soit un modèle idéal. Vous demandez à la page de ne pas terminer la réponse et de poursuivre l'exécution, puis de terminer la demande par programme. Mais qu'en est-il du rendu des pages aspx et des gestionnaires d'événements? ne pas terminer le moyen de réponse, il terminera le rendu de la page aspx avant d'appuyer sur "completeRequest ()". Maintenant, si j'utilise une propriété côté serveur dans ma page, disons une variable de session pour déterminer une connexion valide, qui, si elle expire, lèvera une exception nulle avant même de rediriger. Et la seule façon de résoudre ce problème est de redonner à endResponse la valeur true.
Abs
1
J'allais voter pour cette réponse, mais ce code de page a continué de s'exécuter. Ce n'est pas idéal dans mon cas. Beaucoup plus propre pour gérer ou ignorer la "ThreadAbortException"
DaniDev
159

Il n'y a pas de solution simple et élégante au Redirectproblème dans ASP.Net WebForms. Vous pouvez choisir entre la solution Dirty et la solution Tedious

Dirty : Response.Redirect(url)envoie une redirection vers le navigateur, puis lance un ThreadAbortedExceptionpour terminer le thread actuel. Donc, aucun code n'est exécuté après l'appel Redirect (). Inconvénients: c'est une mauvaise pratique et avoir des implications sur les performances de tuer des threads comme celui-ci. En outre, ThreadAbortedExceptionsapparaîtra dans la journalisation des exceptions.

Fastidieux : la méthode recommandée consiste à appeler Response.Redirect(url, false)puis à Context.ApplicationInstance.CompleteRequest()exécuter. Cependant, l'exécution du code se poursuivra et le reste des gestionnaires d'événements du cycle de vie de la page sera toujours exécuté. (Par exemple, si vous effectuez la redirection dans▶ Load, non seulement le reste du gestionnaire sera exécuté, également la fonction PrendPenderRender, etc. sera également appelée - la page rendue ne sera simplement pas envoyée au navigateur. Vous pouvez éviter le traitement supplémentaire Par exemple, définir un indicateur sur la page, puis laisser les gestionnaires d'événements suivants vérifier cet indicateur avant de procéder à tout traitement.

(La documentation CompleteRequestindique qu'il " oblige ASP.NET à contourner tous les événements et le filtrage dans la chaîne d'exécution du pipeline HTTP ". Cela peut facilement être mal compris. Il contourne d'autres filtres et modules HTTP, mais il ne contourne pas d'autres événements dans le cycle de vie de la page en cours .)

Le problème le plus profond est que WebForms n'a pas un niveau d'abstraction. Lorsque vous êtes dans un gestionnaire d'événements, vous êtes déjà en train de créer une page à afficher. La redirection dans un gestionnaire d'événements est moche car vous terminez une page partiellement générée afin de générer une page différente. MVC n'a pas ce problème car le flux de contrôle est distinct des vues de rendu, vous pouvez donc effectuer une redirection propre en renvoyant simplement un RedirectActiondans le contrôleur, sans générer de vue.

JacquesB
la source
7
Je crois que la meilleure description des formulaires Web que j'aie jamais entendue était "la sauce au mensonge".
mcfea
9
J'adore la quantité de détails dans cette réponse. Mieux que la réponse acceptée
Jess
Si vous utilisez l' option sale , vous pouvez désactiver la coupure sur ThreadAbortException dans Visual Studio. DEBUG> Exceptions ... . Développez CLR> System.Threading> Décochez System.Threading.ThreadAbortException .
Jess
Dieu merci, nous avons quelqu'un avec une bonne réponse, et cela devrait être la réponse la plus votée.
Abs
1
Dans mon cas, cette exception ne vient pas à chaque fois, seulement pour quelques fois entre les deux. Signifie Si et en cliquant sur le même bouton de l'application Live, cela fonctionne, mais lorsque le même lien et le même bouton sont cliqués sur une autre machine, cela donne System.Threading.ThreadAbortException. Une idée pourquoi cela ne se produit pas à chaque fois ??
Sagar Shirke
33

Je sais que je suis en retard, mais je n'ai jamais eu cette erreur que si mon Response.Redirectest dans un Try...Catchbloc.

Ne placez jamais un Response.Redirect dans un bloc Try ... Catch. C'est une mauvaise pratique

Éditer

En réponse au commentaire de @ Kiquenet, voici ce que je ferais comme alternative à la mise de Response.Redirect dans le bloc Try ... Catch.

Je décomposerais la méthode / fonction en deux étapes.

La première étape à l'intérieur du bloc Try ... Catch exécute les actions demandées et définit une valeur "résultat" pour indiquer le succès ou l'échec des actions.

La deuxième étape en dehors du bloc Try ... Catch effectue la redirection (ou non) en fonction de la valeur du "résultat".

Ce code est loin d'être parfait et ne devrait probablement pas être copié car je ne l'ai pas testé

public void btnLogin_Click(UserLoginViewModel model)
{
    bool ValidLogin = false; // this is our "result value"
    try
    {
        using (Context Db = new Context)
        {
            User User = new User();

            if (String.IsNullOrEmpty(model.EmailAddress))
                ValidLogin = false; // no email address was entered
            else
                User = Db.FirstOrDefault(x => x.EmailAddress == model.EmailAddress);

            if (User != null && User.PasswordHash == Hashing.CreateHash(model.Password))
                ValidLogin = true; // login succeeded
        }
    }
    catch (Exception ex)
    {
        throw ex; // something went wrong so throw an error
    }

    if (ValidLogin)
    {
        GenerateCookie(User);
        Response.Redirect("~/Members/Default.aspx");
    }
    else
    {
        // do something to indicate that the login failed.
    }
}
Ortund
la source
@Kiquenet s'il vous plaît voir ma réponse mise à jour pour un exemple de ce que je ferais. Pour ne pas dire que c'est le meilleur cours, mais c'est une alternative viable je pense.
Ortund
N'a pas eu le problème jusqu'à ce que j'encapsule mon code dans un essai, attrape ... Je me demande quels autres appels de code provoquent ce comportement dans .NET
intéressant-nom-ici
8

Response.Redirect() lève une exception pour abandonner la demande en cours.

Cet article de la base de connaissances décrit ce comportement (également pour les méthodes Request.End()et Server.Transfer()).

Car Response.Redirect()il existe une surcharge:

Response.Redirect(String url, bool endResponse)

Si vous passez endResponse = false , l'exception n'est pas levée (mais le runtime continuera de traiter la demande en cours).

Si endResponse = true (ou si l'autre surcharge est utilisée), l'exception est levée et la demande en cours sera immédiatement terminée.

M4N
la source
7

Voici la ligne officielle sur le problème (je n'ai pas pu trouver la dernière, mais je ne pense pas que la situation ait changé pour les versions ultérieures de .net)

dépensier
la source
5
@svick Indépendamment de la pourriture de lien, les réponses de lien uniquement ne sont pas vraiment de bonnes réponses. meta.stackexchange.com/q/8231 I think that links are fantastic, but they should never be the only piece of information in your answer.
Ryan Gates
7

Voilà comment ça Response.Redirect(url, true) marche. Il lance le ThreadAbortExceptionpour abandonner le fil. Ignorez juste cette exception. (Je suppose que c'est un gestionnaire / enregistreur d'erreurs global où vous le voyez?)

Une discussion connexe intéressante est Response.End()considérée comme nuisible? .

Martin Smith
la source
4
L'abandon d'un fil semble être une manière très lourde de gérer la fin prématurée de la réponse. Je trouve étrange que le framework ne préfère pas réutiliser le thread au lieu d'en faire tourner un nouveau pour le remplacer.
dépenser
3

J'ai également essayé une autre solution, mais une partie du code a été exécutée après la redirection.

public static void ResponseRedirect(HttpResponse iResponse, string iUrl)
    {
        ResponseRedirect(iResponse, iUrl, HttpContext.Current);
    }

    public static void ResponseRedirect(HttpResponse iResponse, string iUrl, HttpContext iContext)
    {
        iResponse.Redirect(iUrl, false);

        iContext.ApplicationInstance.CompleteRequest();

        iResponse.BufferOutput = true;
        iResponse.Flush();
        iResponse.Close();
    }

Donc, si vous avez besoin d'empêcher l'exécution de code après la redirection

try
{
   //other code
   Response.Redirect("")
  // code not to be executed
}
catch(ThreadAbortException){}//do there id nothing here
catch(Exception ex)
{
  //Logging
}
Maxim Lavrov
la source
1
il suffit de suivre la réponse de Jorge. Cela supprimera effectivement la journalisation de l'exception d'abandon de thread.
Maxim Lavrov
Quand quelqu'un demande pourquoi il obtient une exception, lui dire de jouer avec try..catch n'est pas une réponse. Voir la réponse acceptée. J'ai commenté votre réponse lors de l'examen de "réponse tardive"
manuell
Cela a le même effet que de mettre false pour le 2ème argument de Response.Redirect, mais le "false" est une meilleure solution que de capturer la ThreadAbortException. Je ne vois pas qu'il y ait jamais une bonne raison de procéder ainsi.
NickG
2

j'ai même essayé d'éviter cela, juste au cas où faire l'abandon sur le fil manuellement, mais je le laisse plutôt avec le "CompleteRequest" et continue - mon code a des commandes de retour après les redirections de toute façon. Cela peut donc être fait

public static void Redirect(string VPathRedirect, global::System.Web.UI.Page Sender)
{
    Sender.Response.Redirect(VPathRedirect, false);
    global::System.Web.UI.HttpContext.Current.ApplicationInstance.CompleteRequest();
}
SammuelMiranda
la source
1

Ce que je fais, c'est attraper cette exception, ainsi que d'autres exceptions possibles. J'espère que cela aidera quelqu'un.

 catch (ThreadAbortException ex1)
 {
    writeToLog(ex1.Message);
 }
 catch(Exception ex)
 {
     writeToLog(ex.Message);
 }
Jorge
la source
2
Mieux vaut éviter l' exception ThreadAbortException que catch et ne rien faire ?
Kiquenet
-1

J'ai aussi eu ce problème.

Essayez d'utiliser Server.Transferau lieu deResponse.Redirect

A travaillé pour moi.

Marko
la source
2
Server.Transfer doit toujours générer une exception ThreadAbortException: support.microsoft.com/kb/312629 , il ne s'agit donc pas d'une solution recommandée.
Joel Beckham
9
Server.Transfer n'enverra pas de redirection à l'utilisateur. Il a un but tout à fait différent!
Marcel
1
Server.transfer et responce.redirect sont différents
mzonerz