BUG : Microsoft ASP.net Ajax - UpdatePanel et Session (PageRequestManagerParserErrorException)
Suite à ce post expliquant les causes de l'erreur PageRequestManagerParserErrorException j'ai reçu une question qui ne se résolvait pas malgré les indications données. En analysant la réponse HTTP qui pose problème avec Fiddler on se rend compte que le flux HTTP était bel et bien modifié. En effet le détail d'une erreur ASP.net est écrit à la fin de la réponse. Voici le message d'erreur ainsi que le stack trace.
[HttpException (0x80004005): L'état de session a créé un ID de session,
mais il ne peut pas l'enregistrer, car la réponse a déjà été vidée par l'application.]
System.Web.SessionState.SessionIDManager.SaveSessionID(HttpContext context, String id, Boolean& redirected,
Boolean& cookieAdded) +163
System.Web.SessionState.SessionStateModule.CreateSessionId() +78
System.Web.SessionState.SessionStateModule.DelayedGetSessionId() +97
System.Web.SessionState.SessionStateModule.ReleaseStateGetSessionID() +30
System.Web.SessionState.SessionStateModule.OnReleaseState(Object source, EventArgs eventArgs) +681
System.Web.SyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +167
System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +117
En anglais le message d'erreur est "Session state has created a session id, but cannot save it because the response was already flushed by the application."
N'ayant pour l'instant pas la moindre idée d'où peut provenir l'erreur, je lance le meilleur ami du développeur pour analyser cette méthode SaveSessionID. Voici ce que me dit Reflector :
if (!context.Response.IsBuffered())
{
throw new HttpException(SR.GetString("Cant_save_session_id_because_response_was_flushed"));
}
A partir de là j'ai compris l'erreur. En interne, lors d'un AsyncPostback, ASP.net appelle la méthode Response.Flush() ce qui a pour effet d'envoyer au client tout le contenu du buffer c'est à dire le début de la réponse HTTP. On ne peut donc plus modifier le header de la requête ! Je me souviens très bien de l'appel à la méthode Flush() car cela m'avait déjà poser pas mal de problème lorsque j'avais implémenté l'upload avec Ajax et UpdatePanel. Je me suis posé la question du pourquoi et je n'ai pas trouvé de réponse valable ...
Mais revenons à nos moutons. Quelle est le rapport entre les sessions et la méthode Flush ? En fait lorsque vous utilisez pour la première fois une variable session, ASP.net va écrire un cookie dans le header de la réponse HTTP, header qui a déjà été envoyé au client suite au Flush() ...
Le code suivant déclenche donc une exception, car vous essayez d'accéder pour la première fois à une variable session lors d'un AsyncPostback.
<script type="text/C#" runat="server">
void btn1_Click(object sender, EventArgs e)
{
Session["dummy"] = "bug";
}
</script>
<asp:UpdatePanel ID="up1" runat="server">
<ContentTemplate>
<%=DateTime.Now.ToLongTimeString()%>
<asp:Button ID="btn1" runat="server" Text="go" OnClick="btn1_Click" />
</ContentTemplate>
</asp:UpdatePanel>
Pour corriger le problème, il suffit d'accéder à une variable session lorsque l'on n'est pas dans un AsyncPostback, par exemple dans le page_load :
void Page_Load(object sender, EventArgs e)
{
if (Session.IsNewSession)
{
Session["ASP.NetAjaxIsReallyStupid"] = true;
}
}
Ainsi la session sera créé lors d'une requête classique, le cookie sera donc correctement définit et il n'y aura pas de soucis pour définir une variable session lors d'un AsyncPostback puisqu'il ne sera pas nécessaire de redéfinir le cookie.