Archive

Archive for the ‘ASP.NET’ Category

ASP.NET: PostBackUrl and How It Can Break ViewState

February 15, 2011 1 comment

I hadn’t done pure ASP.NET Web Forms development for quite a while and I had to create a really simple edit form. The requirement was to show a confirmation message if there are unsaved changes when the user clicks on a button which would redirect him to another page. It’s really straight forward but as most seemingly simple things it turned out that it is somewhat trickier. I will explain every step I took down the road to get it done and what problems I encountered.

Buttons and PostBackUrl

I started with two buttons on the page:
Save button that triggers a postback to the server and persist the changes that the user made
Return button that posts back to the previous page – has its PostBackUrl property set to the url of the corresponding page

Then I wrote some JavaScript code that detects if there is any kind of change between the initial state of the page data and the current one when the user clicks the Return button. If nothing is changed the user was taken to the previous page. So far so good. But if there are some changes a confirmation message appears (I’m using a custom modal dialog, not the default browser’s confirmation dialog implementation) that notifies the user of unsaved pending changes and whether or not he/she wishes to navigate away from the current page anyway. This is where everything goes wrong. By default the ASP.NET Button control renders some JavaScript code for the click event of the button when PostBackUrl is set and it is executed regardless of my event handler.

Note: I am not attaching event handlers directly as attributes on DOM elements. Had I done this nothing would have messed up in my page. But this is really a bad way of doing client side development that is against unobtrusive JavaScript style.

Anyway this JavaScript code changes the form element’s action attribute with the value set for PostBackUrl and if the user decides that he/she wants to stay on the page and to save his/her changes the next time he/she clicks the Save button it will try to post back to the wrong page. Let’s say my page is called Default.aspx and I have another page Defautl2.aspx. Here is the rendered button:

<input type="submit" id="btnReturn" onclick="javascript:WebForm_DoPostBackWithOptions(new WebForm_PostBackOptions("btnReturn", "", false, "", "Default2.aspx", false, false))" value="Return" name="btnReturn">

As you can see there is a JavaScript function WebForm_DoPostBackWithOptions that gets called and receives a single parameter a helper object with all the necessary data to initiate a post back. As you can see the fifth parameter for the WebForm_PostBackOptions constructor is the page to which the post back would occur. Inside the WebForm_DoPostBackWithOptions the action attribute of the page’s form element is changed to Default2.aspx. This won’t be a problem if I was not preventing the postback from occurring. However I do prevent it and from now on any subsequent postback triggered by a control on the page would post back to Default2.aspx. But since the ViewState of the first page differs from that of the second one an exception is thrown:

Validation of viewstate MAC failed. If this application is hosted by a Web Farm or cluster, ensure that <machineKey> configuration specifies the same validationKey and validation algorithm. AutoGenerate cannot be used in a cluster.

What is my solution then?

New form element

I decided to go for a solution with another form element for the second page and get rid of the PostBackUrl property on the button. Now when the user decides not to leave the page no JavaScript code changes the action attribute of the form element and everything is the way it is supposed to be. If he/she chooses to go ahead and go to the previous page then the second form element is submitted and the user is taken to the previous page. Keep in mind that you should not define more than one form server element with runat=”server” attribute because ASP.NET supports only a single form element. You could either create it as a standard HTML element or add it dynamically on the page.

Summary

This is not the first time I encounter this kind of exception but it was the first time I did for this kind of scenario and I was a bit puzzled in the beginning. So I needed to inspect what was going on behind the scenes and come up with a simple and quick solution. It was obvious that something is wrong when the button is clicked and debugging the injected function that makes the actual postback revealed it. There might be more elegant and clean solutions for resolving this issue so everyone is welcomed to share them.

Advertisements
Categories: ASP.NET, CSharp, JavaScript

How to Handle Forms Authentication Timeout During an AJAX Request

October 26, 2010 3 comments

I stumbled upon an interesting issue the other day – how to respond to an error during an AJAX request after a period of user inactivity and a session expiration of the logged in user. In such situation you should either redirect the user to the login page or show a modal dialog where he/she could enter his or her credentials and continue their work. But this seemingly simple task turned out to be not so trivial. Here is the scenario:

  • the user logs in
  • after a period of inactivity the session times out
  • the user makes some action which triggers an AJAX request to the server which results in a response with a status code of 302 (Redirect) and the URL to the login page
  • the browser parses the response and issues a request to the provided redirection URL
  • the AJAX request completes, the response is the HTML markup of the login page with a status code of 200 (OK) which results in an error because the expected data type is JSON and it cannot be parsed correctly

The problem occurs during the last two points and the inability to determine the right cause of the error. It took me an hour or so to pinpoint and understand why this is happening. After this I tried some ideas and came up with a really simple solution.

What I needed was to pass the information to the XmlHttpRequest object that the user’s session had expired and to show a modal dialog so he or she could enter his or her credentials. That’s why I had to get rid of the redirection but only for AJAX requests because I still needed unauthenticated users to be redirected to the login page for non-AJAX requests. In order to do that I removed the Response.RedirectLocation in the Application_EndRequest event inside the Global.asax file:

protected void Application_EndRequest(object sender, EventArgs args)
{
    HttpContextWrapper context = new HttpContextWrapper(Context);
    if (context.Response.StatusCode == 302 && context.Request.IsAjaxRequest())
        context.Response.RedirectLocation = string.Empty;
}

After that it was easy to check the status code in the error handler of the AJAX request and since I knew that a 302 status code is sent by my application only when the user is not authenticated I was able to respond accordingly.

Categories: ASP.NET, ASP.NET MVC, CSharp