ASP.NET MVC - After RedirectToAction call, the target action fails to render a partial view correctly

by Stephen M. Redd 24. March 2009 22:57

Another undocumented "feature" I ran into when playing with the MVC.

I have an ajax action link that sorts a list on the page. To accomplish this, the link performs an ajax request to the controller's Sort action and in the end redraws the part of the page contianing the list's data with the new sort settings. 

No biggie... except that when using Mozilla Firefox, it never worked right. The list would be redrawn, but would always contain the entire page's content including the menus, headers, and all the other stuff.

This was another one of those things I had hoped would magically go away with the RTM of the MVC framework... but when it didn't I had to go figure it out.

The process here was a little more complex than what you'd see in the examples and tutorial apps, but not by much. Here is how it worked.

The controller has two actions: List and Sort

The List action reads the user's profile and gets data based on the user's sort and filter preferences. It will render a partial view containing just the list's data if the request is an ajax request, otherwise it renders the entire page view.

The Sort action simply updates the user's profile with their new sort preferences. Then it uses RedirectToAction to tell the browser to call the controller's List action again.... and the list action would do the actual list building using the updated settings.

Simple enough...  

After some debugging though, I found that some browsers don't resend the necessary headers on a subsequent ajax request when redirected this way. So the list action would think the request was a non-ajax request and it would thus render the whole page view.

There was a lot of discussion about some late beta changes to the MVC framework's IsAjaxRequest() method, so I had hoped it was just a beta bug... but apparently not.

In order to get this to work reliably, I had to have the sort method add a setting to TempData if it was called from ajax, then have the list method check both the TempData setting AND the IsAjaxRequest() method.

Annoying, but at least there is a workaround. 

 

Tags: , ,

Filed Under: Code

ASP.NET MVC - Ajax partial update fails in IE when updating a table's contents

by Stephen M. Redd 23. March 2009 09:17

I chased my tail for a while last week after the final release of the ASP.NET MVC framework went RTM. I'd been having a few "issues" getting my app working right, especially in IE 8.

I'd been hopeful that the RTM releases of either IE 8 or the MVC framework would magically fix these problems for me, but after both went to RTM last week I discovered that I was going to have to tackle the problem myself.

The main problem was that, in IE 8, I was unable to update a table when I was using Ajax  to fetch a partial view containing the table's contents. I had been planning to use this technique to handle paging and sorting of the table's data and for an auto-refresh of the data periodically.

This worked fine on other browsers, but with IE 8 this always threw an unusually crytpic and unformative Javascript error (an "unknown exception") and the update would not complete.

I did finally get to the bottom of the problem...

 

As it turns out it has nothing to do with MVC or the beta of IE 8. As it turns out, I was getting bitten by a very old limitation that all versions of IE back to IE 4 share in common.

The Ajax mechanism that performs the partial update operates by simply replacing the innerHTML value of the target elment with whatever it fetches back from the server... in my case it would replace the table's contents with new rows it got from the server. 

But IE doesn't support using the innerHTML method on tables directly. After some digging I actually came across an explaination for this limitation from the guy who actually wrote the innerHTML method. If you aren't interested in why, just know that he had really good reasons.

Once I knew that it was the innerHTML property of the table that was the problem, it wasn't too much trouble to fix. I just wrapped the table in a div tag, and set the MVC application's ajax call up so it would  targeted the div instead of the table.  

Not a new problem, but the fact that I was working with both a new browser and the new MVC framework made it hard to know I was dealing with a more general problem in the first place. 

Tags: , ,

Filed Under: Code

AjaxControlToolkit - Using the ReorderList with simple collections

by Stephen M. Redd 29. March 2008 09:02

Download Sample Project

Recently I've been working with the AjaxControlToolkit and Microsoft's Ajax technologies in .net 3.5. While I love the AjaxToolkit, I find the documentation and code-sample is somewhat lacking.

The ReorderList is also one of the lest intuitive controls in the Toolkit.

There are several other samples around the net, but all the ones I found assumed your were binding to a DataSource control of some sort, and most didn't demonstrate persisting changes to an underlying data store.

The few that showed persistence assumed a SqlDataSoruce control that would automate persistence with two-way binding (outside of demo-land, this is a a rare case  in my experience).

So when I first tried out this control, I was surprised at how difficult it was to use it with a simple collection such as an array, or a generic list.

Here is the basic markup you need in your page:

<ajaxToolkit:ReorderList ID="OrderList" runat="server" AllowReorder="true"
            LayoutType="Table" OnItemReorder="OrderList_ItemReorder" 
            PostBackOnReorder="true">
            <ItemTemplate>
                <span style="cursor: pointer;">
                    <asp:Label ID="ItemName" runat="server" 
                               Text='<%# Container.DataItem %>' />
                </span>
    </ItemTemplate>
</ajaxToolkit:ReorderList>

We're setting the text of the label control to the Container.DataItem directly rather than using an Eval or Bind method. That's because simple collections don't have properties that Eval and Bind can use, so you put the content of the data item directly in your output. This is the same with almost all bindable controls with simple collections, but is also one of those things the documentation just sort of "assumes" you already know... which is probably why I coded asp.net for about 4 years before I discovered this little trick.

On a postback or callback, there is no reliable way to get at the data items in their re-arranged state. If you put a button on the page to postback to the server and try to look at the items in the control, you will only be able to see them in the order they had been in when the control was first bound to the collection.

This means you can't just let the user make changes on the client and just submit the page back when they are done. The only reliable way to keep up with the user made reordering is to let the control postback each time the user makes a change, then manually keep track of the changes yourself.

Annoyingly, if you let the control post back to the server as the users makes their changes, the control will re-render itself with the original items in their original order. This will cause the list to reset after each postback effectively undoing the user's changes.

The only way around that seems to be to manually rebind the control yourself on each postback after you've updated the collection that you are using to manually track the changes.

To get all this to work together, the trick is to cache a copy of the items. When a user reorders the list you rearrange your copy of the items, recache it, and re-bind the ReorderList control to your updated copy.

How you choose to cache the items might depend on how much information is in the collection. For small collections you can just use ViewState, but for larger collections you'd probably want to cache them on the server with the Cache API or session variable.

This example will use ViewState since it assumes small and simple collection.

Here is the code that does all this:

protected void Page_PreRender(object sender, EventArgs e)
{
    // doing this at PreRender so we don't have to worry about when/if 
    // we should bind based on if it's a postback or callback and what not.
    OrderList.DataSource = OrderItems;
    OrderList.DataBind();
}

protected void SaveOrder_Click(object sender, EventArgs e)
{
    //normally you'd save to reordered list to the DB or whatever
    foreach(string s in OrderItems)
    {
        // output the re-ordered values to page
        Message.Text = Message.Text + s + "
"; } } protected void OrderList_ItemReorder(object sender, ReorderListItemReorderEventArgs e) { string[] items = OrderItems; List list = new List(OrderItems); //using a list for the reordering (convienience) string itemToMove = list[e.OldIndex]; list.Remove(itemToMove); list.Insert(e.NewIndex, itemToMove); OrderItems = list.ToArray(); //you could save this to the DB now, but this example uses a //save button to batch up changes }

As you can see, this is pretty straight forward. We maintain our own copy of the items collection putting it into ViewState between server trip. When the user makes a change the event handler just syncs our copy of the items collection. On PreRender we rebind the ReorderList to our collection, which should always be in sync with the client's copy. And finally, the save button can just commit the changes using our copy.

Tags: , , ,

Filed Under: Code

Powered by BlogEngine.NET 1.6.1.6
Theme by Stephen M. Redd