在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框架下也未改變。this
咱們知道在ASP.NET程序中,除了WebService,全部對Http請求的處理都是從IHttpHandler的ProcessRequest()方法開始的,在MVC模式下也是如此;這樣就給咱們自已實現相似於Server.Transfer()這樣的提供了條件。咱們只要獲得用於處理新請求的IHttpHandler的實例,並對請求的上下文作適當的修改,這樣咱們就能夠調用ProcessRequest來強制把請求的處理切換到新的IHttpHandler中執行。url
在ASP.NET MVC程序中,處理請求的IHttpHandler是從IRouteHandler的GetHttpHandler()方法獲取到的;因此爲了能從這個方法獲得新的處理程序,咱們必須建立新的路由請求上下文信息"RequestContext"。spa
第一步:獲得目標請求處理程序所在的路由(Route)實例,建立新的路由數據(RouteData):code
獲得路由實例的方式有兩種,一種是根據路由名稱從路由表中獲取;一種是根據Url、和路由處理程序,建立新的路由實例。在以上兩種方式裏,都必須給出明確的路由參數的值,這些值用於決定路由的目標Url。orm
第一種方式:根據路由名稱和值生成路由數據blog
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方法get
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); }
完整的實現代碼:string
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作的修改都不能被保留,由於執行的是一個新的控制器實例。