I am not a big fan of frame/iframe solutions but iframes are sometimes necessary if you need to incorporate a legacy application within a new portal/application.

One of my main problems with iframes is that the address bar does not change when you navigate to a sub page within the iframe. This means that you cannot copy the current url from the address bar or bookmark the page and expect to see the same page when you revisit the bookmark.

I thought I would try to work around these problems using the url routing engine in ASP.NET MVC. I started with this url routing rule:

routes.MapRoute("FramedApp",
                "framedApp/{*page}",
                 new { controller = "FramedApp", action = "ViewPage", page = "" }); 

Here I use a wildcard rule to capture the complete url after "framedApp/" and pass in that url to the ViewPage method that looks like this:

public ActionResult ViewPage(string page)
{
    ViewData["page"] = page;
    return View();
}

Not much going on here, I just simply pass the captured page url to the view. The view can then use this url to generate the src attribute for the iframe:

<viewdata page="string" />

<div class="framed-application">
    <iframe src="http://localhost/legacyApp/${page}" frameborder="0"></iframe>    
</div>

So what has this accomplished? Well now we can directly navigate to a sub page within the framed legacy app by a simple url syntax, for example: /framedApp/urlFor/legacyPage.aspx. This will make linking to specific pages in the legacy app a lot easier but there is still one big issue left. The url is not going to change when you move from legacyPage.aspx to legacyPage2.aspx, so you still cannot bookmark some pages.

This problem is a little problematic to work around, my first thought was to change the url by javascript everytime you load a page in the iframe, so if you click on a link to go to legacyPage2.aspx (from within the iframe) you could update the address bar to reflect the new page. However you cannot from javascript change the browser url without causing the browser to reload the page, which is not something we want to do. What you can do from javascript is to set the window location hash, the hash is the text after the "#" . The text after the hash sign is normally used to make the browser jump to a specific named element.

The fact that the text after the hash sign can be set by javascript is used to by many ajax applications to support bookmarking and browser history (back button). For example if you view a label in gmail the browser url looks like this:

image

The problem though is that the text after the hash sign is not sent to the server so the url routing solution I tested above will not work. I need to change it into a javascript solution.

Here is what I ended up with (using JQuery):

$(document).ready(function() {
  if (window.location.hash.length > 0)
  {
    var iframe = $('iframe').get(0);        
    iframe.src = "http://localhost/legacyApp/" + window.location.hash.substring(1);    
  }
        
  $('iframe').load(function() {
    var page = this.contentDocument.baseURI.substring("http://localhost/legacyApp/".length);
    window.location.hash = page;            
  });
});

What I do here is on document load I check if the browser url contains any hash argument, if it does I take the value and append it to the legacyApp url. I also hookup the iframe load event to update the hash when a new iframe page has loaded, for example after a you click a link to go to another page in the iframe. 

image

As you can see from the above screenshot the url now contains the url for the framed application, and it will be updated as you navigate the framed application. So bookmarking and copying the url to send to a friend will work. This makes working with web applications that use frames, at least for me a lot less frustrating as you can clearly see the url for the main page and the framed page. No need to right click This Frame->View Frame Info to find out what page you are currently viewing!

16 comments:

Sagamore said...

this is a great finding.

Thanks for sharing.

Anonymous said...

Thanks for this method, what if there is no hash mark? Any thoughts?

Torkel Ödegaard said...

What do you mean, no hash mark?

Karthik said...

I am not able to go back and forward using this approach. The history behaves different for firefox and IE. Do you have any suggestions

Anonymous said...

Sorry, I meant hash arguement, we are just using "/" so I would want to append everything after the domain slash. Thanks in advance Torkel.

Torkel Ödegaard said...

It should work fine if the application is in the root ("/") folder.

Rajan said...

$(document).ready(function() {
if (window.location.hash.length > 0)
{
var iframe = $('iframe').get(0);
iframe.src = "http://localhost/legacyApp/" + window.location.hash.substring(1);
}

$('iframe').load(function() {
var page = this.contentDocument.baseURI.substring("http://localhost/legacyApp/".length);
window.location.hash = page;
});
});


this code is not working with me in my mvc project
it show "this.contentDocument.baseURI" is null or not an object

Torkel Ödegaard said...

Yes I have had similar problems. I think baseURI is not working in some cases and on all browsers.

Not sure how I solved it, don't have the code for that now.

Rajan said...

thanks for your reply it is not working with me in any browser do i need some extra file for this...

Anonymous said...

my firefox 3 can't access contentDocument or contentWindow at all. so maybe it's rather there the issue, instead of baseURI. i wonder if we will get the ability to catch the DOM tree there again somewhen.

rick said...

This implementation is adding the token to the URL as expected. But I'm finding that it's also adding "dead" entries in the history so that when I click the back button, I have to click two or three times in order to reach the prior URL.

This happens in Firefox (version 3.5) and Safari (version 4.0.2). Any idea how avoid these inactive entries in the history? I've tried executing window.location.replace=page immediately after setting the hash, but that simply breaks the back button.

Michelle Sollicito said...

I really need this code - I think this will solve my problem but I cant get it to work quite right on my code... wish you would provide the full code for this example - I am left guessing what the controller is called and what the view is called etc.. I am not sure if you made a typo or whether it is intentional that in the routing the capitalization is different to the Controller name for framedApp/

Michelle Sollicito said...

The above doesnt work for me - what should it be instead?

Pravesh Singh said...

Hi,

I was reading your article and I would like to appreciate you for making it very simple and understandable. This article gives me a basic idea of URL Routing in ASP.Net 3.5(IIS7) and it helped me a lot. Thanks for sharing with us. Check out this link too its also having nice post with wonderful explanation on URL Routing in ASP.Net 3.5(IIS7), for more details check this....

URL Routing in ASP.Net 3.5(IIS7)

Thank you very much!

ibsukru said...

thank you !

Hrithik said...

Thanks for your valuable information on ASP.NET MVC and IFrame Url Routing


Online MVC Training