在ASP.NET MVC中我們很多時候都會在攔截器和Controller中直接使用Response.Redirect方法做跳轉,但是實際上Response.Redirect方法執行后ASP.NET並不會立即結束當前請求的執行,而是要過一段時間才會終止當前請求的執行,然后命令客戶端瀏覽器去訪問Response.Redirect方法中傳入的新的URL地址。這會導致一個問題,有時候我們希望Response.Redirect方法執行后后面的代碼就取消執行了,因為這並不是我們預期的行為,當代碼執行了Response.Redirect方法后,如果其后面的部分代碼還繼續在執行甚至有可能報錯。
比如在下面的代碼中我們演示了在MVC的IAuthorizationFilter攔截器中,如果用戶沒有登錄應該立刻停止當前頁面的請求,然后跳轉到登錄頁面。我們使用了Response.Redirect方法來做跳轉。
1 public class AuthenticationFilterAttribute:ActionFilterAttribute,IAuthorizationFilter,IActionFilter 2 { 3 #region IAuthorizationFilter Members 4 5 public void OnAuthorization(AuthorizationContext filterContext) 6 { 7 8 string curActionName = filterContext.ActionDescriptor.ActionName; 9 string curControllerName = filterContext.ActionDescriptor.ControllerDescriptor.ControllerName; 10 11 if (!filterContext.HttpContext.Request.IsAuthenticated && (curControllerName.ToLower() != "login" || curActionName.ToLower() != "index")) 12 { 13 filterContext.HttpContext.Response.Redirect(LoginHelper.LoginPageUrl, true); 14 return; 15 } 16 } 17 18 #endregion 19 }
結果我驚訝的發現即便代碼在上面13行Response.Redirect了,ASP.NET還是執行了當前請求URL對應Controller的Action中的代碼,甚至執行了Action返回的View的Razor引擎代碼。。。雖然最終這個view的內容沒有呈現給客戶端瀏覽器,瀏覽器最后還是正確跳轉到了登錄頁。但是根據調試我們發現即便是我們執行了Response.Redirect方法,ASP.NET之后還是執行了一大堆本不該執行的代碼。
所以這個時候我們需要用到EmptyResult這個對象,我們將上面的代碼改成如下所示:
1 public class AuthenticationFilterAttribute:ActionFilterAttribute,IAuthorizationFilter,IActionFilter 2 { 3 #region IAuthorizationFilter Members 4 5 public void OnAuthorization(AuthorizationContext filterContext) 6 { 7 8 string curActionName = filterContext.ActionDescriptor.ActionName; 9 string curControllerName = filterContext.ActionDescriptor.ControllerDescriptor.ControllerName; 10 11 if (!filterContext.HttpContext.Request.IsAuthenticated && (curControllerName.ToLower() != "login" || curActionName.ToLower() != "index")) 12 { 13 filterContext.HttpContext.Response.Redirect(LoginHelper.LoginPageUrl, true); 14 filterContext.Result = new EmptyResult();//加入EmptyResult就告訴ASP.NET MVC在本攔截器執行結束后,不必再為當前請求執行Controller中Action的代碼 15 return; 16 } 17 } 18 19 #endregion 20 }
那么ASP.NET MVC在執行完上面的IAuthorizationFilter攔截器后,就會發現EmptyResult被賦值在了參數filterContext的Result屬性上,就會終止執行Controller的Action,立即結束當前請求的執行,不會再去執行多余的代碼了。其實filterContext的Result屬性只要在IAuthorizationFilter攔截器中被賦值了不為null,就不會執行Controller的Action了,同時在該IAuthorizationFilter攔截器之后注冊的其它Filter攔截器也都不會被執行了。
比如在ASP.NET Core MVC(由於在ASP.NET MVC中JsonResult的構造函數不帶參數,所以在攔截器中使用JsonResult的意義不大,不如就用EmptyResult)中的IAuthorizationFilter攔截器中,我們也可以選擇給context的Result屬性賦值為JsonResult:
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using System; namespace WebApi.Filters { public class AuthorizationFilterAttribute : Attribute, IAuthorizationFilter { public AuthorizationFilterAttribute() { } public void OnAuthorization(AuthorizationFilterContext context) { context.Result = new JsonResult(new { message = "Http請求被AuthorizationFilter攔截" });//加入JsonResult來告訴ASP.NET Core MVC在本攔截器執行結束后,不必再為當前請求執行Controller中Action的代碼,同時取消執行在本攔截器之后注冊的其它Filter攔截器,此外會通過Http的Response返回一個{"message":"Http請求被AuthorizationFilter攔截"}的Json對象到客戶端瀏覽器 } } }
只不過這樣會同時返回一個Json對象給客戶端瀏覽器。
在攔截器中,還可以結合設置StatusCode和EmptyResult,來返回特定狀態碼的Http響應,例如下面我們就展示了如何在IAuthorizationFilter攔截器中,返回401狀態的Http響應
using System; using System.IO; using System.Text; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; namespace WebApi.Filters { public class AuthorizationFilterAttribute : Attribute, IAuthorizationFilter { public AuthorizationFilterAttribute() { } /// <summary> /// 設置StatusCode和EmptyResult,返回401狀態的Http響應,並發送一段html為Http響應體 /// </summary> public void OnAuthorization(AuthorizationFilterContext context) { context.HttpContext.Response.ContentType = "text/html; charset=utf-8";//設置Http響應類型為text/html,編碼為utf-8 context.HttpContext.Response.StatusCode = 401;//設置Http響應狀態碼為401 using (StreamWriter sw = new StreamWriter(context.HttpContext.Response.Body, Encoding.UTF8)) { sw.Write("<html><head></head><body><h1>Unauthorized!</h1></body></html>"); } context.Result = new EmptyResult();//加入EmptyResult就告訴ASP.NET Core MVC在本攔截器執行結束后,不必再為當前請求執行Controller中Action的代碼,同時取消執行在本攔截器之后注冊的其它Filter攔截器 } /// <summary> /// 設置UnauthorizedResult,返回401狀態的Http響應 /// </summary> //public void OnAuthorization(AuthorizationFilterContext context) //{ // context.Result = new UnauthorizedResult();//加入UnauthorizedResult就告訴ASP.NET Core MVC在本攔截器執行結束后,不必再為當前請求執行Controller中Action的代碼,同時取消執行在本攔截器之后注冊的其它Filter攔截器,並且返回401狀態的Http響應 //} } }
所以很多時候我們在ASP.NET中使用Response.Redirect方法時要相當小心,一定要知道Response.Redirect方法執行后並不等於代碼就結束執行了,還要考慮到后面的代碼一旦被誤執行會有什么后果,有什么辦法可以避免。在MVC中使用EmptyResult就是個不錯的選擇。
