124

在 ASP.NET MVC 中,您可以很容易地返回重定向 ActionResult:

 return RedirectToAction("Index");

 or

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

这实际上会给出一个 HTTP 重定向,这通常很好。然而,当使用谷歌分析时,这会导致很大的问题,因为原始推荐人丢失了,所以谷歌不知道你来自哪里。这会丢失有用的信息,例如任何搜索引擎术语。

附带说明一下,这种方法的优点是可以删除可能来自活动的任何参数,但仍然允许我在服务器端捕获它们。将它们留在查询字符串中会导致人们在书签、推特或博客中添加他们不应该的链接。我已经多次看到这种情况,人们在 Twitter 上发布了指向我们网站的包含活动 ID 的链接。

无论如何,我正在为网站的所有传入访问编写一个“网关”控制器,我可能会将其重定向到不同的地方或替代版本。

现在我现在更关心谷歌(而不是偶然的书签),我希望能够发送访问/页面的人,如果他们去的话,他们会得到的页面/home/7,这是主页的第 7 版。

就像我之前说的那样,如果我这样做,我将失去谷歌分析推荐人的能力:

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

我真正想要的是一个

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

这将使我在没有客户端重定向的情况下获得该视图。不过,我不认为这样的事情存在。

目前我能想到的最好的办法是HomeController.Index(..)在我的GatewayController.IndexAction 中复制整个控制器逻辑。这意味着我必须搬进'Views/Home''Shared',这样它就可以使用了。一定会有更好的办法。

4

14 回答 14

131

TransferResult 类怎么样?(基于斯坦斯的回答

/// <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 一起使用(使用Simon 帖子中的代码)。通过查看它是否在 IIS7+ 的集成管道中运行,它应该(尚未能够对其进行测试)也可以在 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());
    }
}

使用这个小宝石你可以做到

// in an action method
TransferToAction(MVC.Error.Index());
于 2009-07-25T00:03:38.447 回答
48

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

如果您使用的是 IIS7,则以下修改似乎适用于 ASP.NET MVC 3。感谢 @nitin 和 @andy 指出原始代码不起作用。

编辑 4/11/2011:TempData 从 MVC 3 RTM 开始与 Server.TransferRequest 中断

修改了下面的代码以引发异常 - 但目前没有其他解决方案。


这是我根据 Markus 对 Stan 原帖的修改版本所做的修改。我添加了一个额外的构造函数来获取一个路由值字典 - 并将其重命名为 MVCTransferResult 以避免混淆它可能只是一个重定向。

我现在可以为重定向执行以下操作:

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);
    }
}
于 2009-08-07T01:57:33.317 回答
15

You can use Server.TransferRequest on IIS7+ instead.

于 2010-09-17T21:09:29.597 回答
12

我最近发现 ASP.NET MVC 不支持 Server.Transfer() 所以我创建了一个存根方法(受 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);
    }
于 2009-05-21T11:50:20.143 回答
9

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

 HomeController controller = new HomeController();
 return controller.Index();
于 2009-04-28T19:42:17.317 回答
9

MVC 仍然能够实际执行Server.TransferRequest ,而不是模拟服务器传输:

public ActionResult Whatever()
{
    string url = //...
    Request.RequestContext.HttpContext.Server.TransferRequest(url);
    return Content("success");//Doesn't actually get returned
}
于 2014-01-03T21:49:04.100 回答
7

我想将当前请求重新路由到另一个控制器/动作,同时保持执行路径与请求第二个控制器/动作完全相同。就我而言, 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

我正在使用它向开发人员显示错误,同时它将在生产中使用常规重定向。请注意,我不想使用 ASP.NET 会话、数据库或其他方式在请求之间传递异常数据。

于 2009-10-18T10:43:02.207 回答
5

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

于 2009-04-28T19:42:23.523 回答
2

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

我不确定这是否是您所说的重复,但是:

return new HomeController().Index();

编辑

另一种选择可能是创建自己的 ControllerFactory,这样您就可以确定要创建的控制器。

于 2009-04-28T19:47:48.287 回答
2

Server.TransferRequest在 MVC中完全没有必要。这是一个过时的功能,仅在 ASP.NET 中才需要,因为请求直接到达一个页面,并且需要有一种方法将请求传输到另一个页面。现代版本的 ASP.NET(包括 MVC)有一个路由基础结构,可以自定义以直接路由到所需的资源。当您可以简单地将请求直接发送到您想要的控制器和操作时,让请求到达控制器只是将其传输到另一个控制器是没有意义的。

更重要的是,由于您正在响应原始请求,因此无需TempData为了将请求路由到正确的位置而将任何东西放入或其他存储中。相反,您会在原始请求完好无损的情况下到达控制器操作。您也可以放心,Google 会批准这种方法,因为它完全发生在服务器端。

虽然您可以从IRouteConstraint和做很多事情IRouteHandler,但最强大的路由扩展点是RouteBase子类。可以扩展此类以提供传入路由和传出 URL 生成,这使其成为与 URL 和 URL 执行的操作有关的所有事情的一站式商店。

因此,按照您的第二个示例,要从/to获取/home/7,您只需要一个添加适当路由值的路由。

public class RouteConfig
{
    public static void RegisterRoutes(RouteCollection routes)
    {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

        // Routes directy to `/home/7`
        routes.MapRoute(
            name: "Home7",
            url: "",
            defaults: new { controller = "Home", action = "Index", version = 7 }
        );

        routes.MapRoute(
            name: "Default",
            url: "{controller}/{action}/{id}",
            defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
        );
    }
}

但是回到你有一个随机页面的原始示例,它更复杂,因为路由参数在运行时不能改变。所以,它可以用一个RouteBase子类来完成,如下所示。

public class RandomHomePageRoute : RouteBase
{
    private Random random = new Random();

    public override RouteData GetRouteData(HttpContextBase httpContext)
    {
        RouteData result = null;

        // Only handle the home page route
        if (httpContext.Request.Path == "/")
        {
            result = new RouteData(this, new MvcRouteHandler());

            result.Values["controller"] = "Home";
            result.Values["action"] = "Index";
            result.Values["version"] = random.Next(10) + 1; // Picks a random number from 1 to 10
        }

        // If this isn't the home page route, this should return null
        // which instructs routing to try the next route in the route table.
        return result;
    }

    public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
    {
        var controller = Convert.ToString(values["controller"]);
        var action = Convert.ToString(values["action"]);

        if (controller.Equals("Home", StringComparison.OrdinalIgnoreCase) &&
            action.Equals("Index", StringComparison.OrdinalIgnoreCase))
        {
            // Route to the Home page URL
            return new VirtualPathData(this, "");
        }

        return null;
    }
}

可以在路由中注册,例如:

public class RouteConfig
{
    public static void RegisterRoutes(RouteCollection routes)
    {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

        // Routes to /home/{version} where version is randomly from 1-10
        routes.Add(new RandomHomePageRoute());

        routes.MapRoute(
            name: "Default",
            url: "{controller}/{action}/{id}",
            defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
        );
    }
}

请注意,在上面的示例中,还可以存储一个 cookie 记录用户进入的主页版本,这样当他们返回时,他们会收到相同的主页版本。

另请注意,使用这种方法,您可以自定义路由以考虑查询字符串参数(默认情况下完全忽略它们)并相应地路由到适当的控制器操作。

其他示例

于 2018-01-20T14:59:28.613 回答
1

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

于 2009-04-28T19:40:20.473 回答
1

对于使用基于表达式的路由的任何人,只使用上面的 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);
}
于 2012-10-18T17:43:02.637 回答
0

本身不是一个答案,但很明显,不仅要求实际导航“执行”Webforms Server.Transfer() 的等效功能,而且还要求在单元测试中完全支持所有这些。

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

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

如果我能做到这一点,我会把它贴出来,否则也许其他人可能会打败我!

于 2009-12-29T17:15:58.143 回答
0

我通过在视图中利用Html.RenderAction助手实现了这一点:

@{
    string action = ViewBag.ActionName;
    string controller = ViewBag.ControllerName;
    object routeValues = ViewBag.RouteValues;
    Html.RenderAction(action, controller, routeValues);
}

在我的控制器中:

public ActionResult MyAction(....)
{
    var routeValues = HttpContext.Request.RequestContext.RouteData.Values;    
    ViewBag.ActionName = "myaction";
    ViewBag.ControllerName = "mycontroller";
    ViewBag.RouteValues = routeValues;    
    return PartialView("_AjaxRedirect");
}
于 2018-03-06T17:56:43.177 回答