如何在 ASP.NET MVC 中模拟 Server.Transfer?
在 ASP.NET MVC 中,您可以非常轻松地返回重定向 ActionResult:
return RedirectToAction("Index");
or
return RedirectToRoute(new { controller = "home", version = Math.Random() * 10 });
这实际上会给出 HTTP 重定向,这通常很好。 然而,当使用 Google Analytics 时,这会导致大问题,因为原始引荐来源网址丢失了,因此 Google 不知道您来自哪里。 这会丢失有用的信息,例如任何搜索引擎术语。
顺便说一句,此方法的优点是删除可能来自活动的任何参数,但仍然允许我在服务器端捕获它们。 将它们留在查询字符串中会导致人们在不应该添加书签或 Twitter 或博客的链接上添加书签。 我已经多次看到这种情况,人们在 Twitter 上发布了包含活动 ID 的网站链接。
不管怎样,我正在为所有对该站点的传入访问编写一个“网关”控制器,我可以将其重定向到不同的位置或替代版本。
现在我更关心Google(而不是意外的书签),并且我希望能够将访问/
的人发送到他们访问/home时会得到的页面/7
,这是主页的版本 7。
就像我之前说过的,如果我这样做,我就会失去谷歌分析引荐来源网址的能力:
return RedirectToAction(new { controller = "home", version = 7 });
我真正想要的是一个
return ServerTransferAction(new { controller = "home", version = 7 });
无需客户端重定向即可获得该视图的方法。 但我认为这样的事情不存在。
目前我能想到的最好的办法是在我的 GatewayController.Index
操作中复制 HomeController.Index(..)
的整个控制器逻辑。 这意味着我必须将 'Views/Home'
移动到 'Shared'
中,以便可以访问它。 一定会有更好的办法。
In ASP.NET MVC you can return a redirect ActionResult quite easily:
return RedirectToAction("Index");
or
return RedirectToRoute(new { controller = "home", version = Math.Random() * 10 });
This will actually give an HTTP redirect, which is normally fine. However, when using Google Analytics this causes big issues because the original referrer is lost, so Google doesn't know where you came from. This loses useful information such as any search engine terms.
As a side note, this method has the advantage of removing any parameters that may have come from campaigns but still allows me to capture them server side. Leaving them in the query string leads to people bookmarking or twitter or blog a link that they shouldn't. I've seen this several times where people have twittered links to our site containing campaign IDs.
Anyway, I am writing a 'gateway' controller for all incoming visits to the site which I may redirect to different places or alternative versions.
For now I care more about Google for now (than accidental bookmarking), and I want to be able to send someone who visits /
to the page that they would get if they went to /home/7
, which is version 7 of a homepage.
Like I said before if I do this I lose the ability for google to analyse the referrer:
return RedirectToAction(new { controller = "home", version = 7 });
What I really want is a
return ServerTransferAction(new { controller = "home", version = 7 });
which will get me that view without a client side redirect.
I don't think such a thing exists, though.
Currently the best thing I can come up with is to duplicate the whole controller logic for HomeController.Index(..)
in my GatewayController.Index
Action. This means I had to move 'Views/Home'
into 'Shared'
so it was accessible. There must be a better way.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(14)
TransferResult 类怎么样? (基于斯坦的回答)
更新:现在可与 MVC3 配合使用(使用 西蒙的帖子)。 通过查看它是否在 IIS7+ 的集成管道中运行,它应该(尚未能够测试它)也可以在 MVC2 中工作。
实现完全透明; 在我们的生产环境中,我们从未直接使用 TransferResult。 我们使用 TransferToRouteResult,它又调用执行 TransferResult。 这是我的生产服务器上实际运行的内容。
如果您使用 T4MVC (如果没有......做!)这个扩展可能派上用场。
使用这个小宝石你可以做到
How about a TransferResult class? (based on Stans answer)
Updated: Now works with MVC3 (using code from Simon's post). It should (haven't been able to test it) also work in MVC2 by looking at whether or not it's running within the integrated pipeline of IIS7+.
For full transparency; In our production environment we've never use the TransferResult directly. We use a TransferToRouteResult which in turn calls executes the TransferResult. Here's what's actually running on my production servers.
And if you're using T4MVC (if not... do!) this extension might come in handy.
Using this little gem you can do
编辑:更新为与 ASP.NET MVC 3 兼容
如果您使用的是 IIS7,以下修改似乎适用于 ASP.NET MVC 3。
感谢@nitin 和@andy 指出原始代码不起作用。
编辑 2011 年 4 月 11 日:从 MVC 3 RTM 开始,TempData 与 Server.TransferRequest 发生冲突
修改了下面的代码以引发异常 - 但目前没有其他解决方案。
这是我根据马库斯对斯坦原始帖子的修改版本所做的修改。 我添加了一个额外的构造函数来获取 Route Value 字典 - 并将其重命名为 MVCTransferResult 以避免混淆,因为它可能只是一个重定向。
我现在可以对重定向执行以下操作:
我修改的类:
Edit: Updated to be compatible with ASP.NET MVC 3
Provided you are using IIS7 the following modification seems to work for ASP.NET MVC 3.
Thanks to @nitin and @andy for pointing out the original code didn't work.
Edit 4/11/2011: TempData breaks with Server.TransferRequest as of MVC 3 RTM
Modified the code below to throw an exception - but no other solution at this time.
Here's my modification based upon Markus's modifed version of Stan's original post. I added an additional constructor to take a Route Value dictionary - and renamed it MVCTransferResult to avoid confusion that it might just be a redirect.
I can now do the following for a redirect:
My modified class :
您可以在 IIS7+ 上使用 Server.TransferRequest。
You can use Server.TransferRequest on IIS7+ instead.
我最近发现 ASP.NET MVC 不支持 Server.Transfer(),因此我创建了一个存根方法(受到 Default.aspx.cs 的启发)。
I found out recently that ASP.NET MVC doesn't support Server.Transfer() so I've created a stub method (inspired by Default.aspx.cs).
难道您不能只创建一个要重定向到的控制器实例,调用您想要的操作方法,然后返回结果吗? 就像是:
Couldn't you just create an instance of the controller you would like to redirect to, invoke the action method you want, then return the result of that? Something like:
MVC 仍然能够实际执行 Server.TransferRequest:
Rather than simulate a server transfer, MVC is still capable of actually doing a Server.TransferRequest:
我想将当前请求重新路由到另一个控制器/操作,同时保持执行路径与请求第二个控制器/操作完全相同。 就我而言,Server.Request 不起作用,因为我想添加更多数据。 这实际上相当于当前处理程序执行另一个 HTTP GET/POST,然后将结果流式传输到客户端。 我确信会有更好的方法来实现这一目标,但这对我有用:
您的猜测是正确的:我放入此代码
,并使用它向开发人员显示错误,而它将使用常规在生产中重定向。 请注意,我不想使用 ASP.NET 会话、数据库或其他一些方法在请求之间传递异常数据。
I wanted to re-route the current request to another controller/action, while keeping the execution path exactly the same as if that second controller/action was requested. In my case, Server.Request wouldn't work because I wanted to add more data. This is actually equivalent the current handler executing another HTTP GET/POST, then streaming the results to the client. I'm sure there will be better ways to achieve this, but here's what works for me:
Your guess is right: I put this code in
and I'm using it to display errors to developers, while it'll be using a regular redirect in production. Note that I didn't want to use ASP.NET session, database, or some other ways to pass exception data between requests.
只需实例化另一个控制器并执行它的操作方法即可。
Just instance the other controller and execute it's action method.
您可以新建另一个控制器并调用返回结果的操作方法。 但是,这将要求您将视图放入共享文件夹中。
我不确定这是否是您所说的重复的意思,但是:
编辑
另一种选择可能是创建您自己的 ControllerFactory,这样您就可以确定要创建哪个控制器。
You could new up the other controller and invoke the action method returning the result. This will require you to place your view into the shared folder however.
I'm not sure if this is what you meant by duplicate but:
Edit
Another option might be to create your own ControllerFactory, this way you can determine which controller to create.
Server.TransferRequest
在 MVC 中完全没有必要。 这是一个过时的功能,仅在 ASP.NET 中才需要,因为请求直接到达页面,并且需要有一种方法将请求传输到另一个页面。 现代版本的 ASP.NET(包括 MVC)具有可自定义的路由基础结构,以直接路由到所需的资源。 当您可以简单地将请求直接发送到您想要的控制器和操作时,让请求到达控制器然后将其传输到另一个控制器是没有意义的。更重要的是,由于您正在响应原始请求,因此无需仅仅为了将请求路由到右侧而将任何内容放入
TempData
或其他存储中地方。 相反,您到达控制器操作时原始请求完好无损。 您也可以放心,Google 会批准这种方法,因为它完全发生在服务器端。虽然您可以通过
IRouteConstraint
和IRouteHandler
做很多事情,但最强大的路由扩展点是RouteBase
子类。 此类可以扩展以提供传入路由和传出 URL 生成,这使其成为与 URL 和 URL 执行的操作有关的所有内容的一站式商店。因此,按照第二个示例,从
/
到/home/7
,您只需要一条添加适当路由值的路由。但是回到原始示例,其中有一个随机页面,它更加复杂,因为路由参数无法在运行时更改。 因此,可以使用
RouteBase
子类来完成,如下所示。可以在路由中注册,如下所示:
请注意,在上面的示例中,还存储一个记录用户进入的主页版本的 cookie 可能是有意义的,这样当他们返回时,他们会收到相同的主页版本。
另请注意,使用此方法,您可以自定义路由以考虑查询字符串参数(默认情况下完全忽略它们)并相应地路由到适当的控制器操作。
其他示例
Server.TransferRequest
is completely unnecessary in MVC. This is an antiquated feature that was only necessary in ASP.NET because the request came directly to a page and there needed to be a way to transfer a request to another page. Modern versions of ASP.NET (including MVC) have a routing infrastructure that can be customized to route directly to the resource that is desired. There is no point of letting the request reach a controller only to transfer it to another controller when you can simply make the request go directly to the controller and action you want.What's more is that since you are responding to the original request, there is no need to tuck anything into
TempData
or other storage just for the sake of routing the request to the right place. Instead, you arrive at the controller action with the original request intact. You also can be rest assured that Google will approve of this approach as it happens entirely on the server side.While you can do quite a bit from both
IRouteConstraint
andIRouteHandler
, the most powerful extension point for routing is theRouteBase
subclass. This class can be extended to provide both incoming routes and outgoing URL generation, which makes it a one stop shop for everything having to do with the URL and the action that URL executes.So, to follow your second example, to get from
/
to/home/7
, you simply need a route that adds the appropriate route values.But going back to your original example where you have a random page, it is more complex because the route parameters cannot change at runtime. So, it could be done with a
RouteBase
subclass as follows.Which can be registered in routing like:
Note in the above example, it might make sense to also store a cookie recording the home page version the user came in on so when they return they receive the same home page version.
Note also that using this approach you can customize routing to take query string parameters into consideration (it completely ignores them by default) and route to an appropriate controller action accordingly.
Additional Examples
路由不就可以为您处理这种情况吗? 即对于上述场景,您可以创建一个实现此逻辑的路由处理程序。
Doesn't routing just take care of this scenario for you? i.e. for the scenario described above, you could just create a route handler that implemented this logic.
对于使用基于表达式的路由的任何人,仅使用上面的 TransferResult 类,这里有一个控制器扩展方法,可以实现这一点并保留 TempData。 不需要 TransferToRouteResult。
For anyone using expression-based routing, using only the TransferResult class above, here's a controller extension method that does the trick and preserves TempData. No need for TransferToRouteResult.
本身不是答案,但显然,要求不仅是实际导航“执行”Webforms Server.Transfer() 的等效功能,而且还要求所有这些在单元测试中得到完全支持。
因此,ServerTransferResult 应“看起来”像 RedirectToRouteResult,并且在类层次结构方面尽可能相似。
我正在考虑通过查看 Reflector 来做到这一点,并执行 RedirectToRouteResult 类以及各种 Controller 基类方法所做的任何操作,然后通过扩展方法将后者“添加”到控制器中。 也许这些可以是同一类中的静态方法,以便于下载?
如果我抽出时间来做这件事,我会把它发布出来,否则也许其他人可能会抢先一步!
Not an answer per se, but clearly the requirement would be not only for the actual navigation to "do" the equivalent functionality of Webforms Server.Transfer(), but also for all of this to be fully supported within unit testing.
Therefore the ServerTransferResult should "look" like a RedirectToRouteResult, and be as similar as possible in terms of the class hierarchy.
I'm thinking of doing this by looking at Reflector, and doing whatever RedirectToRouteResult class and also the various Controller base class methods do, and then "adding" the latter to the Controller via extension methods. Maybe these could be static methods within the same class, for ease/laziness of downloading?
If I get round to doing this I'll post it up, otherwise maybe somebody else might beat me to it!
我通过在视图中利用
Html.RenderAction
帮助器实现了这一点:在我的控制器中:
I achieved this by harnessing the
Html.RenderAction
helper in a View:And in my controller: