ASP.NET MVC 3.0:基於Ajax的表單提交,A頁面認證失敗後頁面被強轉至登入頁面,待登入成功將如何回到A頁面?

來源:互聯網
上載者:User

原文地址:http://www.cnblogs.com/luoxiaonet/archive/2011/12/13/2285326.html#commentform

 

    很多網站的首頁都提供資訊的輸入,而不論您是否有賬戶且已登入。比如我喜歡逛的42qu(我跟創始人無任何關係,僅是喜歡該網站且無意廣告,有興趣的可以瞧瞧去)。當我發表自己的"碎碎念"時,會被自動跳轉到登入頁,而問題是登入成功後能否再回到原來的頁面。聽起來這個問題略顯乏善可陳,然而它的實現架構是MVC 3.0,而且為了尋求其優雅的實現方式嘗試了很多天,現作為記要並分享一下。

    先來一張最終吧:

 

的頁面切分為兩個地區,一是碎碎念的發表區(上部分)、另一個是呈現區(下部分),以真分頁提取資料;資訊提交是基於Ajax.BeginForm()進行的,然後即時無重新整理於呈現區。

    看看這個基於非同步提交的表單是如何陳述的:

值得關注的屬性有UpdateTargetId,它預示著非同步載入的資料將呈現於哪個Div;另外OnSuccess事件也尤為重要,它擔當著回調的處理重任。最後,非同步請求的資料想成功載入於指定的Div中,得引用 jquery.validate.unobtrusive.min.js指令檔(第3行):

這個js檔案的功能類似於ASP.NET AJAX中的"萬能"控制項UpdatePanel。

    現在我想發表一條碎碎念,試圖點擊"碎碎念"進行發送,由於近一個星期沒有登入,其相關Cookie已然到期,頁面將被強轉至登入頁(Login.cshtml),跳轉本身倒是我的設計意圖,但頁面呈現出的結果卻很糟糕,因為整個登入頁面全部呈現在id名為"ChatIntegrationContent"的Div內,其頁面大家可以想像得出是很荒唐的,於是我要解決兩個問題:

1.預設的認證屬性AuthorizeAttribute已經不能滿足我的認證需求了,我得自訂認證類LoginAuthorizeAttribute並繼承它,且重寫(override)相關受保護的虛方法(protected virtual method);

2.伺服器端的任何跳轉顯然已是徒勞:無論是Redirect還是變向的類比Server.Transfer模式來曲線跳轉,都會乖乖地落入原UpdateTargetId屬性指定的Div中。一開始我想,在服務端我能取消Ajax調用嗎?使其轉變成普通調用,即形同@Html.BeginForm()這種普通表單的Post請求。答案是NO!這個請求是Ajax非同步產生的,其命運已然註定,故唯一的途徑便是通知用戶端指令碼作window.location跳轉!

    禍源於Ajax非同步呼叫,解決之道自然還要從它入手,且看看自訂認證類有如何了不得:

繼承AuthorizeAttribute後,僅重寫了虛函數:protected virtual void HandleUnauthorizedRequest(AuthorizationContext filterContext);這意味著形如:[Authorize(Roles = "Member")]這樣的認證邏輯依舊採用MVC 3.0自己的演算法,看了一下它的源碼,確實比我自己另起灶爐寫得全面而優雅得多,我羞愧的將自己實現的百來行代碼統統刪除,物件導向的可重用性真是棒啊。可以看到,內容物件filterContext的屬性Result已被設定成為JsonResult。另外有個內在原因要注意,Authorize屬性(Attribute)認證失敗後,是因為拋出了401異常,才導致MVC架構跳轉到登入頁(loginUrl屬性所指定):

loginUrl屬性顯式指定了登入頁,按照dudu老大的建議,cookieless也顯式指定為 UseCookies,為測試方便Cookie和認證票據(Ticket)都只設定一分鐘(timeout="1")。

    上述代碼最重要的一句是:filterContext.HttpContext.Request.IsAjaxRequest()

它拷問當前請求是否為非同步Ajax請求,之所以這麼判斷是因為用戶端發來的假如是基於@Html.BeginForm()的普通請求,那服務端可以輕鬆跳轉而重新整理整個瀏覽器,不用擔心登入頁落入相關Div之內;相反Ajax請求則要加倍呵護,它得通知JS指令碼作用戶端跳轉(window.location),方能保證登入頁是重新整理整個瀏覽器。以JSON格式封裝一個索引值對傳回用戶端:

繼承自ActionResult的"JSON格式內容"類JsonResult,有一個專門用來裝載資料的Object型屬性Data,目前只需要一個索引值對足夠。另外JsonRequestBehavior = JsonRequestBehavior.AllowGet也舉足輕重:為了避免JSON Hijacking攻擊,自MVC2.0起任何以JsonResult類返回的請求都不允許Get型的Ajax請求擷取資料,即便資料對象內才一個索引值對,而妥協地採用Post請求也將使資料無法被瀏覽器緩衝,其實這些資料的敏感度又不高,所以採用Get請求是個很好的開發體驗!到目前為止,對用戶端發來的Ajax請求,服務端在認證失敗情況下給出了應答,即通知用戶端指令碼"我對你驗證失敗"----Ajax_UnAuthorized。

    還記得Ajax.BeginForm()裡的屬性OnSuccess吧?現在看看它的JS函數:

用戶端的接收也很乾脆,如果通知資訊兩相統一,那麼直接賦值給window.location作頁面跳轉,重新整理整個瀏覽器。採用Request.Url擷取到當前頁面的路由網址作為跳轉路徑的參數一併攜帶過去,結果形如:

http://localhost:1650/Account/LogOn?returnUrl=http%3A%2F%2Flocalhost%3A1650%2F

    強轉到登入頁時,得先讓使用者輸入表單,待提交表單且登入驗證通過後,才利用returnUrl參數值跳回原來的頁面,所以我們需要把returnUrl參數值暫存起來,看看LogOn.cshtml的登入表單頭:

BeginForm()的第三位參數叫路由參數:object routeValues,我們把"返回頁"暫存於此,待將來登入表單提交時,該參數將一併提交到伺服器,交由標記了HttpPost特性的Action處理:

路由網址中的參數returnUrl可以在 HttpPost限定的Action作為參數擷取:顯然第一個是登入表單實體,第二個便是其參數。

只要判斷傳入參數returnUrl是否為空白,便可作出恰當的跳轉選擇,所以只要returnUrl是一個具體的網址就可以順利返回,這就是MVC3.0中對返回原頁面的較為簡潔而優雅的處理方案。

分類: ASP.NET綠色通道:好文要頂關注我收藏該文與我聯絡不覺流年似水
關注 - 39
粉絲 - 1+加關注20(請您對文章做出評價)« 博主前一篇:ASP.NET MVC 3.0:Razor視圖引擎如何展示多實體

posted @ 2011-12-13 19:44 不覺流年似水 閱讀(1020) 評論(8)編輯 收藏

發表評論2269913 回複 引用 查看  

#1樓 2011-12-13 21:08 Chairo      42qu不是python做的?怎麼變成.net mvc3.0了? 回複 引用 查看  

#2樓[樓主] 2011-12-14 07:23 不覺流年似水     

引用

Chairo:42qu不是python做的?怎麼變成.net mvc3.0了?

嗯,是Python.
MVC3是我自己玩的方式。查看 刪除  修改

#3樓 2011-12-14 10:04 黑子範      returnUrl = Request.Url,如果在post的時候驗證登入失效,這個時候,取出的Request.Url的地址是這個post的路徑地址,登入後跳轉這個地址顯然不對,這種情況怎麼處理? 回複 引用 查看  

#4樓 2011-12-14 10:09 C#通用許可權管理系統組件      代碼看上去很規範,排版也很不錯,頂一下。
繼續好好寫部落格。 回複 引用 查看  

#5樓 2011-12-14 10:22 V.Enjoy      非常好!辛苦了!多謝分享! 回複 引用 查看  

#6樓[樓主] 2011-12-14 10:44 不覺流年似水     

引用

黑子範:returnUrl = Request.Url,如果在post的時候驗證登入失效,這個時候,取出的Request.Url的地址是這個post的路徑地址,登入後跳轉這個地址顯然不對,這種情況怎麼處理?

引用

黑子範:returnUrl = Request.Url,如果在post的時候驗證登入失效,這個時候,取出的Request.Url的地址是這個post的路徑地址,登入後跳轉這個地址顯然不對,這種情況怎麼處理?

謝謝您的提問。
比如現在有一個功能頁面A,它的路由網址是:http://localhost:1066/A
在文字框輸入一些文本,預期我們會把文本資訊非同步提交給Controller層的Action處理(取名叫CreateChat),但實際上Action執行前還有一個攔截過濾器(自訂名稱是LoginAuthorize),當它發現你沒有登入或沒有相應許可權時,會針對你的非同步請求返回一個JSON對象,並將控制權迅速移交給用戶端(這樣顯式屏蔽了伺服器拋401異常),意思便是“您認證失敗,請看著辦吧”,用戶端指令碼獲得控制權後不會無動於衷的,它立馬在回呼函數中判斷JSON的索引值如果是"Ajax_UnAuthorized",便意識到“喔,我錯了,我應該登入一下”,於是A頁面指令碼中的Request.Url擷取到自己的路由網址(http://localhost:1066/A),作為網址參數,藉助window.location的瀏覽器重新導向至LogOn頁面,所以“跳轉這個地址顯然不對”這種情況是不會發生的。
    接下來,在登入頁中,如果登入驗證不通過(帳號不存在或密碼不正確),你將一直停留在登入頁;如果驗證成功,即 LogOn這個Actoin的參數returnUrl不為空白時,將由“return Redirect(returnUrl)”前往A頁面。
可以建個MVC項目玩一下,呵呵,有疑問再溝通。查看 刪除  修改

#7樓 2011-12-14 11:37 黑子範     

引用

不覺流年似水:

引用

黑子範:returnUrl = Request.Url,如果在post的時候驗證登入失效,這個時候,取出的Request.Url的地址是這個post的路徑地址,登入後跳轉這個地址顯然不對,這種情況怎麼處理?

引用

黑子範:returnUrl = Request.Url,如果在post的時候驗證登入失效,這個時候,取出的Request.Url的地址是這個post的路徑地址,登入後跳轉這個地址顯然不對,這種情況怎麼處理?

謝謝您的提問。
比如現在有一個功能頁面A,它的路由網址是:http://localhost:1066/A
在...

懂了,謝謝你的回答,可是如果不是ajax的提交呢,returnUrl在什麼地方取?攔截器LoginAuthorize(顯然不對)?如果是在提交前就擷取一下再post,這樣又挺繁瑣,有木有好的辦法 回複 引用 查看  

#8樓[樓主] 2011-12-14 13:48 不覺流年似水     

引用

黑子範:

如果不是Ajax請求,同時以後還希望回到該頁面,則意味著在發表文本資訊時,就得把A頁面的路由網址作為參數一併提交給預期執行的Action(名叫CreateChat),當自訂過濾器LoginAuthorize攔截時,發現閣下沒有登入,於是處理手段分兩情況:
1.也就是本文的處理情況。詢問用戶端的請求是不是Ajax請求,在自訂過濾器LoginAuthorize中有IsAjaxReuquest()的判斷,如果是就立即把控制權移交給用戶端;
2.如果不是Ajax請求,即用戶端的表單是形同@Html.BeginForm(),那麼表單頭只需要多加一個傳遞參數:Request.Url,完整寫法大概是這樣:
@using(Html.BeginForm("CreateChat", "Chat", new{ returnUrl = Request.Url }, FormMethod.Post))
{
//提示:該代碼未測試
}
可以發現,Request.Url是第一次提交時就主動發給伺服器,而不像Ajax請求那樣要等到伺服器下達一個通知後以window.location強行跳轉再以參數攜入。
這便是你希望的“在提交前就擷取一下再post”的願望(即改成Html表單後,多填入一個路由參數)。

Request.Url傳到伺服器,當我們預設不作任何處理,即試圖以Authorize[]作為登入驗證時,一旦驗證失敗MVC架構會拋出401異常,進而根據Web.config中的配置資訊(本文有)重新導向到登入頁,且不帶任何參數,而不帶參數,又如何將returnUrl傳遞出去呢,問題就在這裡!所以在自訂過濾器LoginAuthorize運行階段,MVC架構認證失敗且判定不是Ajax請求情況下,可以顯式重新導向到登入頁,並將這個Url攜帶過去:
return RedirectToAction("LogOn", "Account", new { returnUrl = Request.QueryString["returnUrl"].ToString });
登入頁LogOn代碼和原來一樣,不用更改,即LogOn根本不關心你原來是否是Ajax。

    很多網站的首頁都提供資訊的輸入,而不論您是否有賬戶且已登入。比如我喜歡逛的42qu(我跟創始人無任何關係,僅是喜歡該網站且無意廣告,有興趣的可以瞧瞧去)。當我發表自己的"碎碎念"時,會被自動跳轉到登入頁,而問題是登入成功後能否再回到原來的頁面。聽起來這個問題略顯乏善可陳,然而它的實現架構是MVC 3.0,而且為了尋求其優雅的實現方式嘗試了很多天,現作為記要並分享一下。

    先來一張最終吧:

 

的頁面切分為兩個地區,一是碎碎念的發表區(上部分)、另一個是呈現區(下部分),以真分頁提取資料;資訊提交是基於Ajax.BeginForm()進行的,然後即時無重新整理於呈現區。

    看看這個基於非同步提交的表單是如何陳述的:

值得關注的屬性有UpdateTargetId,它預示著非同步載入的資料將呈現於哪個Div;另外OnSuccess事件也尤為重要,它擔當著回調的處理重任。最後,非同步請求的資料想成功載入於指定的Div中,得引用 jquery.validate.unobtrusive.min.js指令檔(第3行):

這個js檔案的功能類似於ASP.NET AJAX中的"萬能"控制項UpdatePanel。

    現在我想發表一條碎碎念,試圖點擊"碎碎念"進行發送,由於近一個星期沒有登入,其相關Cookie已然到期,頁面將被強轉至登入頁(Login.cshtml),跳轉本身倒是我的設計意圖,但頁面呈現出的結果卻很糟糕,因為整個登入頁面全部呈現在id名為"ChatIntegrationContent"的Div內,其頁面大家可以想像得出是很荒唐的,於是我要解決兩個問題:

1.預設的認證屬性AuthorizeAttribute已經不能滿足我的認證需求了,我得自訂認證類LoginAuthorizeAttribute並繼承它,且重寫(override)相關受保護的虛方法(protected virtual method);

2.伺服器端的任何跳轉顯然已是徒勞:無論是Redirect還是變向的類比Server.Transfer模式來曲線跳轉,都會乖乖地落入原UpdateTargetId屬性指定的Div中。一開始我想,在服務端我能取消Ajax調用嗎?使其轉變成普通調用,即形同@Html.BeginForm()這種普通表單的Post請求。答案是NO!這個請求是Ajax非同步產生的,其命運已然註定,故唯一的途徑便是通知用戶端指令碼作window.location跳轉!

    禍源於Ajax非同步呼叫,解決之道自然還要從它入手,且看看自訂認證類有如何了不得:

繼承AuthorizeAttribute後,僅重寫了虛函數:protected virtual void HandleUnauthorizedRequest(AuthorizationContext filterContext);這意味著形如:[Authorize(Roles = "Member")]這樣的認證邏輯依舊採用MVC 3.0自己的演算法,看了一下它的源碼,確實比我自己另起灶爐寫得全面而優雅得多,我羞愧的將自己實現的百來行代碼統統刪除,物件導向的可重用性真是棒啊。可以看到,內容物件filterContext的屬性Result已被設定成為JsonResult。另外有個內在原因要注意,Authorize屬性(Attribute)認證失敗後,是因為拋出了401異常,才導致MVC架構跳轉到登入頁(loginUrl屬性所指定):

loginUrl屬性顯式指定了登入頁,按照dudu老大的建議,cookieless也顯式指定為 UseCookies,為測試方便Cookie和認證票據(Ticket)都只設定一分鐘(timeout="1")。

    上述代碼最重要的一句是:filterContext.HttpContext.Request.IsAjaxRequest()

它拷問當前請求是否為非同步Ajax請求,之所以這麼判斷是因為用戶端發來的假如是基於@Html.BeginForm()的普通請求,那服務端可以輕鬆跳轉而重新整理整個瀏覽器,不用擔心登入頁落入相關Div之內;相反Ajax請求則要加倍呵護,它得通知JS指令碼作用戶端跳轉(window.location),方能保證登入頁是重新整理整個瀏覽器。以JSON格式封裝一個索引值對傳回用戶端:

繼承自ActionResult的"JSON格式內容"類JsonResult,有一個專門用來裝載資料的Object型屬性Data,目前只需要一個索引值對足夠。另外JsonRequestBehavior = JsonRequestBehavior.AllowGet也舉足輕重:為了避免JSON Hijacking攻擊,自MVC2.0起任何以JsonResult類返回的請求都不允許Get型的Ajax請求擷取資料,即便資料對象內才一個索引值對,而妥協地採用Post請求也將使資料無法被瀏覽器緩衝,其實這些資料的敏感度又不高,所以採用Get請求是個很好的開發體驗!到目前為止,對用戶端發來的Ajax請求,服務端在認證失敗情況下給出了應答,即通知用戶端指令碼"我對你驗證失敗"----Ajax_UnAuthorized。

    還記得Ajax.BeginForm()裡的屬性OnSuccess吧?現在看看它的JS函數:

用戶端的接收也很乾脆,如果通知資訊兩相統一,那麼直接賦值給window.location作頁面跳轉,重新整理整個瀏覽器。採用Request.Url擷取到當前頁面的路由網址作為跳轉路徑的參數一併攜帶過去,結果形如:

http://localhost:1650/Account/LogOn?returnUrl=http%3A%2F%2Flocalhost%3A1650%2F

    強轉到登入頁時,得先讓使用者輸入表單,待提交表單且登入驗證通過後,才利用returnUrl參數值跳回原來的頁面,所以我們需要把returnUrl參數值暫存起來,看看LogOn.cshtml的登入表單頭:

BeginForm()的第三位參數叫路由參數:object routeValues,我們把"返回頁"暫存於此,待將來登入表單提交時,該參數將一併提交到伺服器,交由標記了HttpPost特性的Action處理:

路由網址中的參數returnUrl可以在 HttpPost限定的Action作為參數擷取:顯然第一個是登入表單實體,第二個便是其參數。

只要判斷傳入參數returnUrl是否為空白,便可作出恰當的跳轉選擇,所以只要returnUrl是一個具體的網址就可以順利返回,這就是MVC3.0中對返回原頁面的較為簡潔而優雅的處理方案。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.