Showing posts with label form action changes. Show all posts
Showing posts with label form action changes. Show all posts

Oct 24, 2009

Problems getting Telerik, UrlRewriting.Net, Ajax and Validators all working at once

Hello all.

It all started with me deciding to aggree with a friend of mine, who wanted some guy he could trust to develop a games portal for him.

After some analysis on what he needs, I have decided to use the following:

1.    UrlRewriter.NET (or Intelligencia as I prefer naming it), for URL rewriting obviously and Search Engine Optimization.

2.    Telerik for the great flexibility on reporting, custom controls and Ajax implementations.

3.    Simple custom validators to fulfill the specific kind of globalization / multilanguage support. (Just deriving their ErrorText properties from some cached Database source.)

4.    Masterpages for simplicity during client customization and ease of use in custom web controls. Such as header, footer, basket, titles, etc.

Sounds yummy, yeah? Well, it all started that way. Until some conflicts started occurring between the components I have choosen.

Here are the problems and solutions I have managed to compile:

1.    On an Ajax enabled page listing types of games that could be added to basket (call it GameDetail.Aspx), the basket container resided in a user control in the masterpage; while the “add to basket” buttons resided within the page that inherits that masterpage.

Running some tests, I found a solution to provide that.

    • In the page needing the ability to change the contents of the basket box, I added a reference to the the basket control:

      <%
      @ Reference VirtualPath="~/UserControls/BasketBox.ascx" %>
    • This way, I could access the user control within the masterpage from the page that inherited that masterpage as follows:

      UserControls_BasketBox objBox = this.Master.FindControl("ucBasketBox1") as UserControls_BasketBox;
      objBox.AddToBasket(ProductID, Count);


      You can see for yourselves that you can not access the same user control without the Reference.

2.    Solving the access issue, I thought I could ajax add the games to the basket without any postback (and errors to come!). But this wasn’t the case again.

To solve the unwanted postback issue, there was one more thing I had to do:

    • I moved the RadAjaxPanel of Telerik from the page to a temporary masterpage that would only be used by this page. I moved the AjaxPanel up to a level that it could contain both the basket box user control and the ContentPlaceHolder. (This could have been solved in other way[s] I assume, I’m just too lazy to read the Telerik samples.)
      I later added a RadScriptManager to the top of the masterpage, above the AjaxPanel I just mentioned.

      Testing the page by calling say localhost:1000/myGamez/GameDetail.aspx, all seemed fine. “Add to basket” buttons worked without postbacks, and I could update the BasketBox.ascx ajaxifically (yeah, new word).

      However, when I tested the rewritten url which is something like localhost:1000/myGamez/GameList/SeoFriendlyGameNameAndStuff_123 using Httpwatch, I saw that the “action” property of the one-and-only runat=server form on my page changed from that fancy seo-friendly name to the original GameDetail.aspx during ajax partial postbacks. It was still fine with me as long as it caused no errors. It actually didn’t cause any errors, not on this page at least!

3.    Real Problem: Solving the ajax-enabled basket issues, the client could now buy games. And I had to create a page in which the client could see his/her (well, not “her” actually, no girl pays money for games in my world) previous orders. This was supposed to be a simple page. I named it MyOrders.aspx, and created a rewrite rule that let me access it like “localhost:1000/myGamez/Financial/MyOrders”.

I used a Telerik RadGrid with its MasterTableView showing one specific Order summary in a row, while the only DetailTable showed the Product related sub-items within the Order Summary row. That is, I wanted the client to have the ability to first see previous orders in a neat page and later “click to expand” the order to see its contents if he needs to.

I handled the
grdOrders_DetailTableDataBind of the RadGrid to bind a DataSource to the DetailTable. This event was triggered by the default “expand” arrows of the RadGrid.
I continued adding some more orders to the application, and then decided it was time to implement paging as well as using Ajax for all the expand/collapse/paging stuff.
I handled the
grdOrders_PageIndex event again to do the paging as needed. And added a RadAjaxPanel to the page that encapsulated the Telerik RadGrid.
I tested the page and saw everyting working fine in peace by visiting
localhost:1000/myGamez/MyOrders.aspx.

Then I decided to visit the same page with the rewritten url,
localhost:1000/myGamez/Financial/MyOrders.

Data bound nicely, Ajax worked nicely, expand/collapse worked nicely, paging worked nicely. But these nicely tested events started failing randomly when they were fired for the second / third / some random number of time!

The error I received was a javascript alert window, saying:

Sys.WebForms.PageRequestManagerServerErrorException:
An unknown error occurred while processing the request on the server. The status code returned from the server was: 404


I then used HttpWatch and some javascript to see what might have caused the 404, namely “File Not Found” error.

I examined the page source, posted and received ajax contents and noticed two problems with this specific page:

    • The “action” property of the only form on the page wasn’t rendered to be “myGamez/Financial/MyOrders” on the page load as rewritten.
      It was rendered to be “../MyOrders.aspx”.

      To get this rendered correctly, I overrided the Render function of the page and interfere when the form was being rendered. I also used a CustomHtmlWriter class implementing HtmlTextWriter that the overridden Render function needed to call.

      MyOrders.aspx.cs:

protected override void Render(HtmlTextWriter writer)

{

writer = new CustomHtmlWriter(ref writer, " ", Request.RawUrl);

        base.Render(writer);
}

CustomHtmlWriter.cs:

public class CustomHtmlWriter : HtmlTextWriter

{

    private string _actionURL;

    public string ActionURL

    {

        get { return _actionURL; }

        set { _actionURL = value; }

    }

 

    public CustomHtmlWriter(ref HtmlTextWriter writer, string tabstring, string RewrittenAction)

        : base(writer, tabstring)

    {

        this.ActionURL = RewrittenAction;

    }

 

    public override void WriteAttribute (string name, string value, bool fEncode)

    {

        if (string.Compare(name, "action", true) == 0)

            value = ActionURL;

        base.WriteAttribute(name, value, fEncode);

    }
}

Before this fix, the form rendered:
<form name="Form1" method="post" action="
.. /MyOrders.aspx" onsubmit="javascript:return WebForm_OnSubmit();" id="Form1">

After, the form rendered:
<form name="Form1" method="post" action="
/myGamez/Financial/MyOrders" onsubmit="javascript:return WebForm_OnSubmit();" id="Form1">

Important note for this solution:
If you decide to use the form control within the UrlRewriter.net dll instead of what I explained above, you will get two new headaches: You will lose the ability to see your web pages during design time (some fancy conversion / cast error and all your controls are invisible but just a red error title).

And your validators will start working after the whole page posting back!
Well, I did test that too before overriding Render.

    • The so-hardly-rendered-correctly “action” property of the only form on the page was changing between these Ajax calls!
      This process took me really a long time, for not being sure what component caused the issue. After the tracking process, I found out that the javascript functions doing that was delivered from “
      localhost:1000/myGamez/Telerik.Web.UI.WebResource.axd?...”
      ...
      if(formActionNode){this._form.action=formActionNode.content;this._form._initialAction=this._form.action}
      ...

      After any Ajax postback occurs, I had to find a way to change the “action” and “_initialAction” of the form back to the original form. I noticed that the RadAjaxPanel had two methods that were suitable for this: ClientEvents-OnRequestStart and ClientEvents-OnResponseEnd.

      So I have handled these two client-side events:

<script type="text/javascript" language="javascript">

var _oldAction;

function reqStart(){_oldAction=document.forms[0].action;}

function reqEnd(){document.forms[0].action=document.forms[0]._initialAction=_oldAction;}

</script>

And:

<telerik:RadAjaxPanel ID="RadAjaxPanel1" runat="server" .... ClientEvents-OnRequestStart="reqStart" ClientEvents-OnResponseEnd="reqEnd">

 

You have read all my problems within the project and my ways to solve them.

These are the steps to replicate the problems:

1.    Create a new Web Project, call it testApplication.

2.    Download and implement UrlRewriter.net component / dll in the project following the samples on their web site.

3.    Create a master page (say it testMaster) and put some non-html text to show it comes from the master (i.e. "Rendering Master")

4.    Put a ContentPlaceHolder to the testMaster.

5.    Create a mastered web page using testMaster within a folder named “testFolder” and name this new page “test.aspx”.

6.    Create a corresponding rewriting rule which does not reside on the same level with the page according to the root.
Something like
:
<rewrite url="~/TestTime"  to="~/testFolder/test.aspx"/>

7.    Put some non-html text to show it comes from the page (i.e. "Rendering test.aspx")

8.    (Automatically) edit Web.config to get things working properly. Check both localhost:someport/testApplication/testFolder/test.aspx and localhost:someport/testApplication/TestTime both working as intended.

9.    Add a Telerik RadScriptManager, RadAjaxPanel and RadGrid to the page. Enable paging on the grid and  set PageSize of the RadGrid to a small number like 10.

10.  On the PageLoad of test.aspx.cs, checking "if (!Page.IsPostback)", bind some random data to the radgrid which is longer than 10 objects. This will force the RadGrid to have more than 1 pages and have ajax enabled postbacks. Implement the Grid’s paging event as well to actually see the paging working as intended. You should have some kind of input that triggers the grid to ajax-postback.

11.  First checkpoint: Your paging will start raising errors at random times (not random indeed, whenever the action property changes).
To get rid of that error, override the page’s Render method as explained in the way above in “MyOrders.aspx.cs”. This way your form’s action will render pointing to the rewritten url.

12.  Put the following javascript declaration which displays the html form's action property in a certain time interval (10 milliseconds in this example) on test.aspx:
<script type="text/javascript">
window.setInterval("window.status=document.forms[0].action;", 10);
</script

13.  This should display something like "/testApplication/TestTime" in this phase.

14.  Now keeping an eye on the left-bottom corner on your window’s status, navigate to the page 2 of the RadGrid.

15.  Second checkpoint: See the window.status changing from "/testApplication/ TestTime " to something like "../test.aspx".
Now if you try to navigate back to page 1, you get an error indicated by an alert message (404).
Weird observation: If the real page and the redirected page resides on the same conceptional folder level, this causes a full page postback and validators to mulfunction. If they are not, it causes an error. Neither of which is desired.
To get rid of that error, implement the ClientEvents-OnRequestStart and ClientEvents-OnResponseEnd functions as explained above.

16.  Finally: You should now have Telerik RadGrid, Ajax Postbacks, UrlRewriting and Validators all working at the same time!

I hope you don’t spend so much time like me to figure all those stuff out.

Happy programming!