[ASP.NET]ASP.NET AJAX網頁跨網域內嵌於iframe

ASP.NET AJAX網頁跨網域內嵌於iframe時發生指令碼錯誤的排除法。

摘要

ASP.NET AJAX是微軟近三年來所開發出來的非同部/部分刷新的AJAX框架,此框架在.net framework 2.0版屬於extensions,而在.net framework 3.5版則被納入framework之中,這對於使用ASP.NET做為基礎開發框架的程式設計師,實在是一大福音,看完ASP.NET AJAX的Showcase(http://www.asp.net/ajax/showcase/)後真的讓人迫不及待想用。

此框架在使用上非常簡單且直覺,因此推出沒多久就受到廣大迴響,因為從此以後我們再也不用自己撰寫AJAX功能,全交給微軟就搞定了,此框架看來似乎無懈可擊,但這個框架再好,使中有其限制,以下列舉幾個框架的缺點:

1. 使用一個簡單的AJAX功能便需額外下載80-100K大小的script resource

2. 預設的UpdatePanel刷新屬於Full Page postback,也就是說觸發部分刷新時,整個後端的code都會重頭到尾執行一次,與一般postback的差別只在,當回應到client端時,頁面不會全頁刷新,這其實對執行效率是一大問題(可透過PageMethods指呼叫單一function)。

3. 當網頁被跨網域嵌入iframe中,會出現存取被拒的錯誤。

1.2.兩點在本文中都會提到,但本文件的重點著重在如何解決3.。

 

問題背景

近期針對兩項產品進行整合,而其中UI的整合是產品A透過iframe的方式將產品B的頁面內嵌進來,而被嵌進來的頁面是有使用ASP.NET AJAX的功能,網頁的架構大致如下圖:

未命名

MainPage.aspx為產品A的外框網頁,AJAXPage.aspx為產品B的網頁,這是一個簡單的系統整合方式,照理說不會出現問題才對,但當實際運作時卻發生了異常現象。

 

案例說明

以下我們舉個例子來說明這個設計架構出了什麼問題,我們先在網站A建立一個網頁叫做AJAXPage.aspx,並在此網頁中放了以下控制項:ScriptManager、UpdatePanel、Label、Button、DropDownList、CheckBox,畫面如下:

clip_image004

我們先瀏覽一下,狀況一切正常,非常好!

clip_image006

接著我們在網站B建立一個網頁叫MainPage.aspx,並在此網頁中加入一個iframe,將src指定到網站A的AJAXPage.aspx,如下圖:

未命名2

這時候我開啟MainPage.aspx,一切看似十分正常,非常好,心理不禁開心,這個跨系統整合輕易的完成了,正打算要交差時想說測一下整合好的功能好了,不測還好,一測眼淚都不爭氣的掉了下來,當我點選畫面上的Button時,卻出現了下下圖的錯誤訊息:

clip_image010

clip_image012

幾經測試,發現不管點選畫面上哪個元件都會跳出這樣的錯誤訊息,心裡不禁想,一定是AJAXPage.aspx寫錯了,回去測試AJAXPage.aspx,卻發現完全沒問題,基於求知的精神,我們繼續回到MainPage.aspx的案發現場,當它再跳出這個訊息時,我們就毫不猶豫的給它點『是』。

clip_image012[1]

不點還好,點了真嚇人,我們看到下面這個畫面,讓人感覺十分的陌生,一般我們在發生script錯誤時,不是應該會停在script中嗎?這個ScriptResource.axd是什麼?重點是我們還找不到它,怎麼辦?下一章我們來介紹這些陌生的新東西。

未命名3

ASP.NET AJAX Resource

ScriptResource.axd

ScriptResource顧名思義就是含括Script的Resource檔,而ASP.NET AJAX的設計模式就是將所有框架所提供的js檔,以內嵌的形式嵌在ScriptResource.axd這個檔案中,所以我們將網頁另存新檔後可以看到網頁的內容物如下:

clip_image017

含有兩個ScriptResource跟一個WebResource,WebResource的用途我們暫不在此文件中說明,有興趣的可以參考(註一),ASP.NET AJAX所使用的js檔案在這邊完全都沒看到,因為這些檔案都被內嵌在ScriptResource中了,在解釋ScriptResource中的js檔之前,我們先看看,為什麼要使用ScriptResource的方式來內嵌js檔:

1. Automatically GZip/Compressing your scripts over HTTP for delivery.

2. Dynamically resolving Release/Debug scripts based on build parameters.This is useful, if you keep two types of the same script: one for debug, and one packed for release.

第一個原因是使用ScriptResource會自動進行檔案壓縮,可減少網路的傳輸量;第二個原因則是可將js檔分成debug版與非debug版本,透過參數的定義決定這次內嵌入ScriptResource的檔案是否為debug版本,至於為什麼這樣分,下個小節我們來做一些探討。

(註一:http://bchavez.bitarmory.com/archive/2008/07/28/understanding-scriptresource-and-webresource-in-asp.net.aspx)

 

ASP.NET AJAX的js檔

首先我們來到ASP.NET AJAX的安裝路徑,js的放置位置如下圖:

clip_image019

我們看到下面總共有6個js檔,每個js檔都有一個debug的版本與一個非debug的版本,這兩個版本的差異我們可先從檔案大小來看,MicrosoftAjax.debug.js與MicrosoftAjax.js的檔案大小差了170K左右,我們再來看看兩個檔案的內容:

首先是MicrosoftAjax.debug.js:

clip_image021

再來是MicrosoftAjax.js:

clip_image023

這兩個檔案的內容其實是一模一樣的,差別只在debug版的程式有進行排版,而非debug版的沒有排版,原因在於只有在debug模式下我們才會在意js的排版內容,因為方便除錯,而正式release的版本則無需排版,因為只要程式運作不會錯就好了。而只是單純的有無排版,兩者的檔案大小就差了170K左右,基於此,正式上線的系統我們一定要選擇release版本,怎麼選?只要把web.config中的debug設為false就好了。

 

解決方案

中間解釋了ASP.NET AJAX的Resource內容,我們還是要回到我們的原始問題:當有使用ASP.NET AJAX技術的網頁,以跨網域的形式被內嵌於iframe中時,出現下面這該死的錯誤時,我們該怎麼辦?

clip_image012[2]

 

方案說明

錯誤錯在MicrosoftAjax.debug.js中,但所有的js檔都被包裝在ScriptResource中了,而這些js檔又是由framework自動幫我們加入到ScriptResource.axd中,我們如何修改這個問題?

幸好微軟的工程師幫我們預留了一些後路,被包進ScriptResource.axd中的js檔是可以透過程式去動態修改的,我們只要修正該js檔的問題,並重新指定給ScriptResource.axd就可以了。

 

問題的根源

根據錯誤訊息,我們追蹤到MicrosoftAjax.debug.js的5959行,這行的內容如下:

clip_image025

這邊的寫法有個問題,主要問題在跨網域去取得top物件時,會因為權限不足而被檔掉,我們可以想像,如果我們隨便寫一個網頁將Yahoo的網頁嵌在裡頭,然後我們的網頁可以隨意的操作Yahoo網頁的物件,這會多麼可怕,因此類似這樣跨網域去存取網頁物件的動作是不被允許的。

 

修改方法

<<Step1>>

我們在MicrosoftAjax.js(不選擇debug版本的原因在於這個檔案較小)檔中將case Sys.Browser.InternetExplorer:跟case Sys.Browser.Safari:之間的程式碼換成以下內容:

 
 
01 Sys.UI.DomElement.getLocation = function(element) {
02     if (element.self || element.nodeType === 9) return new Sys.UI.Point(0,0);
03     var clientRect = element.getBoundingClientRect();
04     if (!clientRect) {
05         return new Sys.UI.Point(0,0);
06     }

07         var ownerDocument = element.document.documentElement;
08         var offsetX = clientRect.left - 2 + ownerDocument.scrollLeft,
09         offsetY = clientRect.top - 2 + ownerDocument.scrollTop;
10     
11     try {
12         var f = element.ownerDocument.parentWindow.frameElement || null;
13         if (f) {
14             var offset = 2 - (f.frameBorder || 1) * 2;
15             offsetX += offset;
16             offsetY += offset;
17         }

18     }

19     catch(ex) {
20     }
    
21     return new Sys.UI.Point(offsetX, offsetY);
22 }
23 break;

 

 

 

<<Step2>>

將MicrosoftAjax.js複製到站台的某個目錄下,如我的範例我放到站台下的AJAX_JS的目錄下,

未命名4

 

<<Step3>>

修改AJAXPage.aspx,找到網頁上的ScriptManager,並加入以下內容:

<asp:ScriptManager ID="ScriptManager1" runat="server">

1 <Scripts>  
2 <asp:ScriptReference  
3 Name="MicrosoftAjax.js" ScriptMode="Auto"  
4 Path="~/AJAX_JS /MicrosoftAjax.js"/>  
5 </Scripts>

 

 </asp:ScriptManager>

 

 

當我們加入以上內容後,ScriptResource中內嵌的的js檔便會改成我們所指定路徑的檔案了,快點開啟你的網頁測試看看吧,一測之下果然沒有錯誤了,太神奇了傑克。

不過如果因為這個問題我們就必須要調整所有的ASP.NET AJAX網頁的話,那也太耗費人力了,因此我們又想到另一個解決方案,就是在我們程式的Superclass的OnInit中下入以下程式碼,由後端自動幫我們加入這個ScriptReference:

 
 
1 ScriptReference tRef = new ScriptReference("~/AJAX_JS/MicrosoftAjax.js");
2 this.ScriptManager1.Scripts.Add(tRef);

一套用之下,果然所有的程式都沒有問題了。

後記

這個問題在微軟的開發者BLOG中是個已知的問題,但在.net framework 2.0版的AJAX extensions中並沒有被解決,但在.net framework 3.5中已被排除,若專案允許的話,是可以直接升級到3.5的版本進行排除。

游舒帆 (gipi)

探索原力Co-founder,曾任TutorABC協理與鼎新電腦總監,並曾獲選兩屆微軟最有價值專家 ( MVP ),離開職場後創辦探索原力,致力於協助青少年培養面對未來的能力。認為教育與組織育才其實息息相關,都是在為未來儲備能量,2018年起成立為期一年的專題課程《職涯躍升的關鍵24堂課》,為培養台灣未來的領袖而努力。