如何模拟ASP.NET MVC中的Server.Transfer?

在ASP.NET MVC中,你可以很容易地返回一个redirect的ActionResult:

return RedirectToAction("Index"); or return RedirectToRoute(new { controller = "home", version = Math.Random() * 10 }); 

这实际上会给HTTPredirect,通常很好。 然而,当使用谷歌分析这会引起大问题,因为原来的引用丢失,所以谷歌不知道你来自哪里。 这会丢失有用的信息,例如任何search引擎条款。

作为一个方面说明,这种方法的优点是消除可能来自广告系列的任何参数,但仍然允许我捕获它们的服务器端。 将它们留在查询string中会导致用户collections书签或推特或博客链接。 我已经多次看到人们纷纷指向包含广告系列ID的网站的链接。

无论如何,我正在写一个“网关”控制器的所有来访的网站,我可能会redirect到不同的地方或替代版本。

现在,我现在更关心Google(而不是偶然的书签),我希望能够发送访问/到他们将得到的页面,如果他们去/home/7 ,这是一个主页的版本7 。

就像我以前说过的如果我这样做,我失去了谷歌分析引荐的能力:

  return RedirectToAction(new { controller = "home", version = 7 }); 

我真正想要的是一个

  return ServerTransferAction(new { controller = "home", version = 7 }); 

这将使我看到没有客户端redirect的观点。 我不认为这样的事情存在。

目前我能想到的最好的事情是在我的GatewayController.Index操作中复制HomeController.Index(..)的整个控制器逻辑。 这意味着我必须将'Views/Home'移动到'Shared'以便访问。 一定会有更好的办法??..

TransferResult类怎么样? (根据Stans的回答 )

 /// <summary> /// Transfers execution to the supplied url. /// </summary> public class TransferResult : ActionResult { public string Url { get; private set; } public TransferResult(string url) { this.Url = url; } public override void ExecuteResult(ControllerContext context) { if (context == null) throw new ArgumentNullException("context"); var httpContext = HttpContext.Current; // MVC 3 running on IIS 7+ if (HttpRuntime.UsingIntegratedPipeline) { httpContext.Server.TransferRequest(this.Url, true); } else { // Pre MVC 3 httpContext.RewritePath(this.Url, false); IHttpHandler httpHandler = new MvcHttpHandler(); httpHandler.ProcessRequest(httpContext); } } } 

更新:现在适用于MVC3(使用西蒙的post代码)。 它应该 (还没有能够testing)通过查看是否在IIS7 +的集成pipe道中运行,也可以在MVC2中工作。

为了完全透明; 在我们的生产环境中,我们从来没有直接使用TransferResult。 我们使用TransferToRouteResult,然后调用执行TransferResult。 以下是我的生产服务器上实际运行的内容。

 public class TransferToRouteResult : ActionResult { public string RouteName { get;set; } public RouteValueDictionary RouteValues { get; set; } public TransferToRouteResult(RouteValueDictionary routeValues) : this(null, routeValues) { } public TransferToRouteResult(string routeName, RouteValueDictionary routeValues) { this.RouteName = routeName ?? string.Empty; this.RouteValues = routeValues ?? new RouteValueDictionary(); } public override void ExecuteResult(ControllerContext context) { if (context == null) throw new ArgumentNullException("context"); var urlHelper = new UrlHelper(context.RequestContext); var url = urlHelper.RouteUrl(this.RouteName, this.RouteValues); var actualResult = new TransferResult(url); actualResult.ExecuteResult(context); } } 

如果你使用T4MVC (如果不行…),这个扩展可能会派上用场。

 public static class ControllerExtensions { public static TransferToRouteResult TransferToAction(this Controller controller, ActionResult result) { return new TransferToRouteResult(result.GetRouteValueDictionary()); } } 

使用这个小gem,你可以做

 // in an action method TransferToAction(MVC.Error.Index()); 

编辑:更新为与ASP.NET MVC 3兼容

假设您使用的是IIS7,下面的修改似乎适用于ASP.NET MVC 3.感谢@nitin和@andy指出原始代码无法正常工作。

编辑2011年4月11日:TempData从MVC 3 RTM的Server.TransferRequest中断

修改下面的代码来抛出一个exception – 但目前没有其他的解决scheme。


这里是我根据马修斯改编的斯坦原始版本的修改。 我添加了一个额外的构造函数来获取Route Value字典 – 并将其重命名为MVCTransferResult,以避免混淆它可能只是一个redirect。

我现在可以做以下redirect:

 return new MVCTransferResult(new {controller = "home", action = "something" }); 

我修改的类:

 public class MVCTransferResult : RedirectResult { public MVCTransferResult(string url) : base(url) { } public MVCTransferResult(object routeValues):base(GetRouteURL(routeValues)) { } private static string GetRouteURL(object routeValues) { UrlHelper url = new UrlHelper(new RequestContext(new HttpContextWrapper(HttpContext.Current), new RouteData()), RouteTable.Routes); return url.RouteUrl(routeValues); } public override void ExecuteResult(ControllerContext context) { var httpContext = HttpContext.Current; // ASP.NET MVC 3.0 if (context.Controller.TempData != null && context.Controller.TempData.Count() > 0) { throw new ApplicationException("TempData won't work with Server.TransferRequest!"); } httpContext.Server.TransferRequest(Url, true); // change to false to pass query string parameters if you have already processed them // ASP.NET MVC 2.0 //httpContext.RewritePath(Url, false); //IHttpHandler httpHandler = new MvcHttpHandler(); //httpHandler.ProcessRequest(HttpContext.Current); } } 

您可以在IIS7 +上使用Server.TransferRequest

我最近发现,ASP.NET MVC不支持Server.Transfer(),所以我创build了一个存根方法(受Default.aspx.cs启发)。

  private void Transfer(string url) { // Create URI builder var uriBuilder = new UriBuilder(Request.Url.Scheme, Request.Url.Host, Request.Url.Port, Request.ApplicationPath); // Add destination URI uriBuilder.Path += url; // Because UriBuilder escapes URI decode before passing as an argument string path = Server.UrlDecode(uriBuilder.Uri.PathAndQuery); // Rewrite path HttpContext.Current.RewritePath(path, false); IHttpHandler httpHandler = new MvcHttpHandler(); // Process request httpHandler.ProcessRequest(HttpContext.Current); } 

难道你不能只是创build一个你想redirect到的控制器实例,调用你想要的操作方法,然后返回结果呢? 就像是:

  HomeController controller = new HomeController(); return controller.Index(); 

我想将当前请求重新路由到另一个控制器/动作,同时保持执行path与请求第二个控制器/动作完全相同。 在我的情况下,Server.Request将无法正常工作,因为我想添加更多的数据。 这实际上等价于执行另一个HTTP GET / POST的当前处理程序,然后将结果传输到客户端。 我相信会有更好的方法来达到这个目的,但是这对我有用:

 RouteData routeData = new RouteData(); routeData.Values.Add("controller", "Public"); routeData.Values.Add("action", "ErrorInternal"); routeData.Values.Add("Exception", filterContext.Exception); var context = new HttpContextWrapper(System.Web.HttpContext.Current); var request = new RequestContext(context, routeData); IController controller = ControllerBuilder.Current.GetControllerFactory().CreateController(filterContext.RequestContext, "Public"); controller.Execute(request); 

你的猜测是正确的:我把这个代码放进去

 public class RedirectOnErrorAttribute : ActionFilterAttribute, IExceptionFilter 

我用它来向开发人员显示错误,而在生产中它将使用常规的redirect。 请注意,我不想使用ASP.NET会话,数据库或其他方式在请求之间传递exception数据。

只是实例另一个控制器并执行它的操作方法。

MVC不是模拟服务器传输,而是实际上可以做一个Server.TransferRequest :

 public ActionResult Whatever() { string url = //... Request.RequestContext.HttpContext.Server.TransferRequest(url); return Content("success");//Doesn't actually get returned } 

您可以新build另一个控制器并调用返回结果的操作方法。 这将要求您将视图放入共享文件夹。

我不知道这是不是你的意思重复,但:

 return new HomeController().Index(); 

编辑

另一个选项可能是创build自己的ControllerFactory,这样您可以确定要创build哪个控制器。

路由不只是照顾你的这种情况? 即对于上述场景,您可以创build一个实现此逻辑的路由处理程序。

对于任何使用基于expression式的路由,只使用上面的TransferResult类,这里有一个控制器扩展方法,它可以完成这个技巧并保留TempData。 不需要TransferToRouteResult。

 public static ActionResult TransferRequest<T>(this Controller controller, Expression<Action<T>> action) where T : Controller { controller.TempData.Keep(); controller.TempData.Save(controller.ControllerContext, controller.TempDataProvider); var url = LinkBuilder.BuildUrlFromExpression(controller.Request.RequestContext, RouteTable.Routes, action); return new TransferResult(url); } 

本身并不是一个答案,但显然这个要求不仅仅是实际的导航“做”了Webforms Server.Transfer()的等价function,而且也是为了在unit testing中完全支持所有这些。

因此,ServerTransferResult应该像“RedirectToRouteResult”一样“看起来”,并且在类层次结构方面尽可能地相似。

我正在考虑通过查看Reflector来做到这一点,并且执行RedirectToRouteResult类以及各种Controller基类方法,然后通过扩展方法将后者“添加”到Controller中。 也许这些可能是同一类中的静态方法,为了方便/懒惰的下载?

如果我这样做,我会把它张贴起来,否则也许别人可能会打败我!