在ASP.NET MVC應用程序中實現Server.Transfer()相似的功能

在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作的修改都不能被保留,由於執行的是一個新的控制器實例。

相關文章
相關標籤/搜索