Sunday, March 11, 2012

UpdatePanel Callback Once Forms Auth Ticket Expired

Your best bet is going to be handling the error in your global.asax

Something like:

Sub Application_Error(ByVal senderAsObject,ByVal eAs EventArgs)

response.redirect("pagetimeout.aspx")

Context.ClearError()

EndSub

Good luck


jasonjanofsky,

Thanks for the reply. I don't think handling this particular situation in the Application_Error event is the desired solution, however. This method should be used as a last chance to prevent an ugly exception from being presented to the user (as well as a place to log the exception thru a common logging mechanism).

I've Googled this extensively and found the client-side Ajax methods like login, logout, and get_IsLoggedIn to access the forms authentication space from the client. get_IsLoggedIn() seemed exactly what I was looking for, but it doesn't "phone home" when its called. Instead, it checks the state of the authentication cookie at the time the page was rendered. So when the forms auth cookie expires from the time the page is rendered to the next Ajax partial postback, the get_IsLoggedIn() method always returns 'true'...even though that really isn't the case. So the UpdatePanel gets hosed b/c the response from the server is in fact an attempt to redirect to the login URL. Ajax catches the error and displays it in a dialog box.

I need for the async call to act like it's a regular postback when dealing with forms authentication -- i.e. forward the user to the login URL.

I could probably write my own webservice to check a user's authentication status, but I was really hoping the Ajax tools would allow me to check the status of the forms auth cookie w/out writing someting custom. That would be just another thing I would need to maintain, configure, and test in my app.

I would think that SOMEONE has hit this problem out there and has provided some clever solution ;-)


Thanks again,

/bc


Okay, so I found a solution. If anyone needs to solve the same problem I've been asking about, here's how I did it:

I got a copy of the ASP.NET 2.0 AJAX book from Wrox (by Gibbs and Wahlin) and found the information (some of the book text paraphrased below) that pointed me to a solution.

Service References (page 116) -- Create a web service and decorate it with the [ScriptService] attribute. This declares to ASP.NET that it should provide a JavaScript proxy class for use in calling the web service.

[ScriptService]
    [WebService(Namespace ="http://tempuri.org/")]
    [WebServiceBinding(ConformsTo =WsiProfiles.BasicProfile1_1)]
    [ToolboxItem(false)]
   publicclassAuthentication :WebService
    {
       privateIWebContext _webContext =ObjectFactory.GetInstance<IWebContext>();
 
        [WebMethod]
       publicbool IsLoggedIn()
        {
           returnthis._webContext.IsUserAuthenticated;
        }
    }

After creating it, you can then call this web service directly (append /js to the end) in the browser to see the actual JavaScript proxy code generated necessary to invoke the method directly from client side script.


Next, you add a ServiceReference within the ScriptManager to the web service you created.

<asp:ScriptManagerID="Manager"runat="server"EnablePartialRendering="true"EnablePageMethods="true">
       <Services>
           <asp:ServiceReferencepath="~/WebServices/Authentication.asmx"InlineScript="true"/>
       </Services>
   </asp:ScriptManager>


Just by doing this you should be able to load the page that youAjaxifiedin your browser and see the generated JavaScript code that will enable the page to "phone home" to the web service in the page source. You will see that to call the web service method, the JavaScript function will take three arguments: a success method callback, a failure method callback, and a variable to hold something useful from the calling context (you can use this in either the success or failure methods if you wish -- this is NOT passed across the wire, it's just "forwarded" on to the client side callback functions after the call completes).

One other thing I had to add in my web.config is add an AJAX HTTP Handler for .asmx files:

<httpHandlers>
      <addverb="GET,HEAD"path="ScriptResource.axd"type="System.Web.Handlers.ScriptResourceHandler, System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"validate="false"/>
      <removeverb="*"path="*.asmx"/>
      <addverb="*"path="*.asmx"     type="System.Web.Script.Services.ScriptHandlerFactory"     validate="false"/>     
    </httpHandlers>
 

After this, I just wrote the necessary JavaScript methods to call the proxy method that handles the call across the wire. In my code below, the loadSKUs() method is the entry point. This method makes the call across the wire, indicating the callback methods that should fire after completion. In the successfulAuthCallback() method, the result is inspected to determine if we are still logged in or not. Remember that my WebMethod returns either true or false.

All I'm looking for is a value of 'true' coming back from the call to my WebMethod. If this is the case, then we are still logged in and I can allow the UpdatePanel to do it's magic. If not, then I tell the entire page to reload b/c it will then be a real/normal postback and the normal flow will occur where the Forms Auth will validate the session and redirect to the login URL or allow the page to proceed normally. When we make the async call once the forms auth cookie has expired, the result that comes back is actually the HTML response telling the browser to redirect -- and is the markup that the UpdatePanel was choking on to begin with b/c it didn't know what to do with it.

 function loadSKUs(skuCriteria) {         
            WebServices.Authentication.IsLoggedIn(successfulAuthCallback, failedAuthCallback, skuCriteria);
        }
 
       function setProductAndHandAndPostBack(skuCriteria)
        {
           var hiddenField = $get(hiddenSkuCriteriaField);
 
           if (hiddenField) {
               if (hiddenField.value != skuCriteria)
                {
                    hiddenField.value = skuCriteria;
                    __doPostBack(hiddenSkuCriteriaField,'');
                }
            }
        }
 
 
       function successfulAuthCallback(isLoggedInResult, skuCriteria)
        {
           if (isLoggedInResult ==true)
            {
                setProductAndHandAndPostBack(skuCriteria);
            }
           else
            {
                window.location.reload(true);// reload the entire page so the Forms Auth will see that the cookie has expired and redirect to login
            }
        }
 
       function failedAuthCallback(result, skuCriteria)
        {
            alert("Auth Callback failed: Reason -- " + result);
        }

 

So that's it. It's working now and the user won't get an ugly parsing error from AJAX when an async call is made from the client after the forms auth cookie has expired on the server. I think this is too involved, however. I don't think we should be forced to write any custom code to handle the async calls to the server in order to handle forms auth issues. Shouldn't the partial postback play nicely and handle this situation automatically?

Maybe I'm missing something here, but this was a pain to overcome.

/bc


You have to add endRequest handler to PageRequestManager

Check, if the response is an error, then handle it however you want. Once you've handled it, you set errorHandled property to true on the client and that'll top the default behavior.

http://ajax.asp.net/docs/mref/E_System_Web_UI_ScriptManager_AsyncPostBackError.aspx

No comments:

Post a Comment