標籤:存在 參數 建立 value nbsp dex this return 中轉
在ASP.NET MVC應用程式中,如果使用Server.Transfer()方法希望將請求轉寄到其它路徑或者Http處理常式進行處理,都會引發“為xxx執行子請求時出錯”的HttpException異常。而在最終實現Server.Transfer()操作的方法內部,我看到這樣幾行代碼。
else if (!(handler is Page)){ error = new HttpException(0x194, string.Empty);}
很明顯,在方法內部,所有的IHttpHandler都將被當作Page類型來處理。如果傳入的處理常式不是Page類型則引發異常!即使是你傳入的Url或IHttpHandler對應一個物理的ashx檔案也不例外。而且這種做法在.NET 4.5架構下也未改變。
我們知道在ASP.NET程式中,除了WebService,所有對Http請求的處理都是從IHttpHandler的ProcessRequest()方法開始的,在MVC模式下也是如此;這樣就給我們自已實作類別似於Server.Transfer()這樣的提供了條件。我們只要得到用於處理新請求的IHttpHandler的執行個體,並對請求的上下文做適當的修改,這樣我們就可以調用ProcessRequest來強制把請求的處理切換到新的IHttpHandler中執行。
在ASP.NET MVC程式中,處理請求的IHttpHandler是從IRouteHandler的GetHttpHandler()方法擷取到的;所以為了能從這個方法得到新的處理常式,我們必須建立新的路由請求上下文資訊"RequestContext"。
第一步:得到目標請求處理常式所在的路由(Route)執行個體,建立新的路由資料(RouteData):
得到路由執行個體的方式有兩種,一種是根據路由名稱從路由表中擷取;一種是根據Url、和路由處理常式,建立新的路由執行個體。在以上兩種方式裡,都必須給出明確的路由參數的值,這些值用於決定路由的目標Url。
第一種方式:根據路由名稱和值產生路由資料
public void ToRoute(string routeName, object values) { //獲得路由執行個體 var route = RouteTable.Routes[routeName]; if (route == null) throw new Exception(string.Format("路由表中不存在名為 \"{0}\" 的路由", routeName)); //建立路由資料 var routeData = new RouteData(route, new MvcRouteHandler()); //添加路由參數/值 foreach (var pair in new RouteValueDictionary(values)) { routeData.Values[pair.Key] = pair.Value; } Route(routeData); }
第二種方式:根據給定的URL和值產生路由資料。(在ASP.NET MVC應用程式中,IRouteHandler的實現即是System.Web.Mvc.MvcRouteHandler)
public void ToUrl(string url, object values) { //建立路由處理常式執行個體 var routeHandler = new MvcRouteHandler(); //建立路由資料 var routeData = new RouteData(new Route(url, routeHandler), routeHandler); //添加路由參數/值 foreach (var pair in new RouteValueDictionary(values)) { routeData.Values[pair.Key] = pair.Value; } Route(routeData); }
第二步:建立新的路由請求上下文資訊(RequestContext),重寫內部路徑,擷取IHttpHandler並調用它的ProcessRequest方法
void Route(RouteData routeData) { var requestContext = new RequestContext(Context, routeData); //重寫內部請求路徑 var newPath = routeData.Route.GetVirtualPath(requestContext, null).VirtualPath; requestContext.HttpContext.RewritePath(newPath); //擷取處理常式,處理請求 IHttpHandler handler = routeData.RouteHandler.GetHttpHandler(requestContext); if (handler == null) throw new Exception("未能從指定路由中擷取到 IHttpHandler"); handler.ProcessRequest(HttpContext.Current); }
完整的實現代碼:
using System.Web.Mvc; using System.Web.Routing; public class RequestRouter { readonly HttpContextBase _Context; public HttpContextBase Context { get { return this._Context; } } public RequestRouter(HttpContextBase context) { this._Context = context; } void Route(RouteData routeData) { var requestContext = new RequestContext(Context, routeData); //重寫內部請求路徑 var newPath = routeData.Route.GetVirtualPath(requestContext, null).VirtualPath; requestContext.HttpContext.RewritePath(newPath); //擷取處理常式,處理請求 IHttpHandler handler = routeData.RouteHandler.GetHttpHandler(requestContext); if (handler == null) throw new Exception("未能從指定路由中擷取到 IHttpHandler"); handler.ProcessRequest(HttpContext.Current); } public void ToRoute(string routeName, object values) { //獲得路由執行個體 var route = RouteTable.Routes[routeName]; if (route == null) throw new Exception(string.Format("路由表中不存在名為 \"{0}\" 的路由", routeName)); //建立路由資料 var routeData = new RouteData(route, new MvcRouteHandler()); //添加路由參數/值 foreach (var pair in new RouteValueDictionary(values)) { routeData.Values[pair.Key] = pair.Value; } Route(routeData); } public void ToUrl(string url, object values) { //建立路由處理常式執行個體 var routeHandler = new MvcRouteHandler(); //建立路由資料 var routeData = new RouteData(new Route(url, routeHandler), routeHandler); //添加路由參數/值 foreach (var pair in new RouteValueDictionary(values)) { routeData.Values[pair.Key] = pair.Value; } Route(routeData); } }
第三步:在控制器的Action中轉寄請求
public ActionResult Index(){ var routeRequest = new RequestRouter(HttpContext); routeRequest.ToRoute("Default", new { controller = "Home", action = "About" }); return new EmptyResult();}
這樣一來,請求上面的控制器中的Index操作方法之後,請求被轉寄到 Home 控制器的 About 操作方法,而且所有請求相關的資料(Forms,QueryStrings)都被保留了下來。不過,在轉寄請求的Action中對ViewData和ViewBag做的修改都不能被保留,因為執行的是一個新的控制器執行個體。
在ASP.NET MVC應用程式中實現Server.Transfer()類似的功能