Ajax and Iframes
Even with AJAX capabilities - there are times that a multi-frame page must be generated. Some reasons would be:
- Your application uses a IHTTP Handler (no master pages) to generate content and requires passing in query string parameters.
- Lack of desire to create all of the CSS to make a control's skin work (and sometimes IFRAME designs are actually easier to work with than all of the CSS design considerations).
- Use of a custom control that you want to use a totally different layout than what your main page uses and perhaps is shared in different ways across your application.
- Use of multiple Update Panels results in more traffic than what a normal postback scenario would create.
Myth: You can not use IFRAMES with AJAX because of issues with ScriptManager and ScriptManagerProxies.
DEBUNKED: While I can not speak for the "Master Page" programming model, I do know that when using a IHTTP handler that there is no such issue. In fact both the 'main page' and the IFRAME have their own ScriptManagers loaded in my application. To date I have not seen an issue or ScriptManager errors. Just be sure you do not set the IFRAME border=yes or no and instead use numeric values (0 ..1..so on..). Unlike in RC1 - it appears at least that RTM ignores the IFRAME to a degree. You can still access from the parent page the iframe in javascript - but you have to call it explicitly:
In designing the Administrative portion of my project I opted to go the IFRAME approach. Reasons listed above all compelled the decision as I wanted to create re-useable controls that could be stand-alone such that they can be integrated into other areas of the site. You're probably saying but you could do that without IFRAMES but these controls are also used such that they can be the actual page and consideration has to be given to what happens when these controls are used in non-ajax browsers. However - one of the biggest issues was this:
Using Collapsible Extenders for a menu control, I found that in order to update the main working form portion of the application one of two things had to happen:
1. Both the series of Collapsible Extenders and the dynamic loaded "Form" content had to reside in the same update panel. This bloats unnecessarily the amount of state information and postback data on updates within the 'Form" controls. If you haven't noticed already - it is very easy to 'overload' a web application with improper use of Ajax controls.
2. If both the menu and the form controls were in seperate update panels then in code-behind I would have to actually call a updatepanel.update on the form controls when a change was made in the menu selection. That initial update would result in both update panels refreshing ; however, on subsequent calls the form controls would be the only asynch postback (the latter being desirble naturally).
Lastly, some of my custom controls have tons of nested controls. I find that more I try to remove and add in new controls that the more headaches I incurred trying to troubleshoot what is not working. The more seperation I can give - such as using a menu in a update panel and a IFRAME that simply just loads a new 'page' - the application is quicker and the least amount of code required for tracking what needs to be done in a nested update panel of that nature. Additionally, tracking views is easier because my tracking code for statistics only does it on a actual page load (which the IFRAME is designed for).
An example of dynamically creating IFRAMES is listed here:
/**
*
* AJAX IFRAME METHOD (AIM)
* http://www.webtoolkit.info/
*
**/
AIM = {
frame : function(c) {
var n = 'f' + Math.floor(Math.random() * 99999);
var d = document.createElement('DIV');
d.innerHTML = '<iframe style="display:none" src="about:blank" mce_src="about:blank" id="'+n+'" name="'+n+'" onload="AIM.loaded(\''+n+'\')"></iframe>';
document.body.appendChild(d);
var i = document.getElementById(n);
if (c && typeof(c.onComplete) == 'function') {
i.onComplete = c.onComplete;
}
return n;
},
form : function(f, name) {
f.setAttribute('target', name);
},
submit : function(f, c) {
AIM.form(f, AIM.frame(c));
if (c && typeof(c.onStart) == 'function') {
return c.onStart();
} else {
return true;
}
},
loaded : function(id) {
var i = document.getElementById(id);
if (i.contentDocument) {
var d = i.contentDocument;
} else if (i.contentWindow) {
var d = i.contentWindow.document;
} else {
var d = window.frames[id].document;
}
if (d.location.href == "about:blank") {
return;
}
if (typeof(i.onComplete) == 'function') {
i.onComplete(d.body.innerHTML);
}
}
}
}
if( Sys && Sys.Application ){
Sys.Application.notifyScriptloaded();
(required for Microsoft's AJAX)
The code above is used for creating a hidden iframe for doing file uploads in a ajax enviroment. Two code articles / examples are here:
-
-
ScriptManagerProxies: Do you really need them?
Again - I do not use the Master Pages and instead generate all of my content dynamically through a custom IHTTP module. I have yet to see a purpose for using the ScriptManagerProxies anywhere as of yet. However, the following must be done on each control that you want to use AJAX controls or insert script. Remember - yes it is possible to inject script without loading them through the ScriptManager - BUT - you'll face a myriad of errors in the valiant attempt.
Here is how I do it:
On the Page's OnInit for your control:
SM = ScriptManager.GetCurrent(Page);
if (SM != null)
{
SM.AsyncPostBackError += new EventHandler<AsyncPostBackErrorEventArgs>(SM_AsyncPostBackError);
}
Next, make sure you add a event for doing something with the AsynchPostBackError by adding the event handler code:
void SM_AsyncPostBackError(object sender, AsyncPostBackErrorEventArgs e)
{
SM.AsyncPostBackErrorMessage = e.Exception.Message;
// I log it - but since most cases may not break the page - So I do not throw it again
bllLogging.RecordError(LoggingID+" ScriptManager Error", e.Exception, Severity.Severe, "", "");
}
Depending on the nature of your control - you can opt to handle the error or just throw a new exception. Personally, I find 9 times out 10 that the error is not necessarily accurate(if the SM throws one) and that it is a by-product of a error not caught in the actual .Net (non ajax) code. For instance a dropdownlist is set to a selected index that doesn't exist. It may be possible that the exception is not handled by .Net or the SCM - however on the partial page update the DOM will get out of synch and controls will not work (such as modal will not pop back up after a modal is cancelled and re-invoked). You will find this especially true if you are in Release compilation mode and testing your design (I speak from my own experience on this - naturally it's not supposed to be that way).
One final note on use of IFRAMES. Make sure the default page the IFRAME is set to - actually loads an actual page. If you do not - you will most certainly find yourself trying to troubleshoot a ghost (Fiddler comes in handy here to look for the 404 errors). The page not found will upset the DOM when scripts are loaded.
Secondly, if you are dynamically creating the IFRAME and using the pre-render event to load the script - you may want to change the mode that the ScriptManager loads the scripts. Introduced in the RTM (and was never part of the RC builds)
SM.LoadScriptsBeforeUI = true; (or do it in the actual aspx page where your dropped the scriptmanager control)
The reason being is that the iframe gets created late and may not load the default page. This is especially true if the IFRAME is being created in a IFRAME. There is nothing that prevents one however from loading scripts on a pages OnInit or Page load event in which case any controls requiring the script (such to invoke the function in a script) - less that it is not a recommended practice to do so.
Lastly:
If you are experiencing issues with Iframes and "Access is Denied" with Ajax the odds are you are doing cross domain scripting without knowing it. Say a main page is Domain A and the Iframe pulls in Domain B or perhaps even you are doing main page Domain A and in the Iframe subdomain of Domain A. You are cross scripting ... here is a fix that will take care of that. http://weblogs.asp.net/bleroy/archive/2007/01/31/how-to-work-around-the-quot-access-denied-quot-cross-domain-frame-issue-in-asp-net-ajax-1-0.aspx