我在这里有两个问题,第二个是无关紧要的,如果第一个问题得到解答,但在我看来技术上仍然很有意思......我会尽量明确:
以下是我们网站的两个版本,一个是“桌面”版本,另一个是移动版本。我们的营销人员希望两个版本的主页都在同一个网址上提供(因为SEO专家这么说)。
这听起来很简单,在大多数情况下都是这样,除了......我们的桌面站点是.NET 4.0 ASPX站点,我们的移动站点是MVC,两者都运行在同一站点(同一个项目,相同的apppool,相同的应用程序)。
因为桌面版本占我们流量的大约95%,这应该是默认值,并且我们希望仅当用户在移动设备上时才从ASPX代码“转移”(因此相同的URL)到MVC视图或者真的想看看手机版。据我所知,到目前为止,没有简单的方法可以做到这一点(Server.Transfer只执行一个新的处理程序 - 因此页面 - 如果有一个物理文件)。因此到目前为止,有任何人以适当的方式完成了这个问题吗?
这让我想到:
显然,我不期待任何答案,所以这就是我在做的事情:
在需要转移到移动设备的页面中,我执行以下操作:
protected override void OnPreInit(EventArgs e) {
base.OnPreInit(e);
MobileUri = "/auto/intro/index"; // the MVC url to transfer to
//Identifies correct flow based on certain conditions 1-Desktop 2-Mobile
BrowserCheck.RedirectToMobileIfRequired(MobileUri);
}
和RedirectToMobileIfRequired调用的实际TransferToMobile方法(我跳过检测部分,因为它完全不相关)看起来像:
/// <summary>
/// Does a transfer to the mobile (MVC) action. While keeping the same url.
/// </summary>
private static void TransferToMobile(string uri) {
var cUrl = HttpContext.Current.Request.Url;
// build an absolute url from relative uri passed as parameter
string url = String.Format("{0}://{1}/{2}", cUrl.Scheme, cUrl.Authority, uri.TrimStart('/'));
// fake a context for the mvc redirect (in order to read the routeData).
var fakeContext = new HttpContextWrapper(new HttpContext(new HttpRequest("", url, ""), HttpContext.Current.Response));
var routeData = RouteTable.Routes.GetRouteData(fakeContext);
// get the proper controller
IController ctrl = ControllerBuilder.Current.GetControllerFactory().CreateController(fakeContext.Request.RequestContext, (string)routeData.Values["controller"]);
// We still need to set routeData in the request context, as execute does not seem to use the passed route data.
HttpContext.Current.Request.RequestContext.RouteData.DataTokens["Area"] = routeData.DataTokens["Area"];
HttpContext.Current.Request.RequestContext.RouteData.Values["controller"] = routeData.Values["controller"];
HttpContext.Current.Request.RequestContext.RouteData.Values["action"] = routeData.Values["action"];
// Execute the MVC controller action
ctrl.Execute(new RequestContext(new HttpContextWrapper(HttpContext.Current), routeData));
if (ctrl is IDisposable) {
((IDisposable)ctrl).Dispose(); // does not help
}
// end the request.
HttpContext.Current.Response.End();
// fakeContext.Response.End(); // does not add anything
// HttpContext.Current.Response.Close(); // does not help
// fakeContext.Response.Close(); // does not help
// Thread.CurrentThread.Abort(); // causes infinite loading in FF
}
此时,我希望Response.End()调用也可以结束线程(如果我跳过整个伪造控制器执行位的话,它会这样做),但事实并非如此。
因此我怀疑我的伪造上下文(我发现能够通过新url传递当前上下文的唯一方法)或控制器可以防止线程被杀死。
fakeContext.Response与CurrentContext.Response相同,并且结束虚假上下文响应或杀死线程的几次尝试并没有真正帮助我。
在Response.End()之后运行的任何代码都不会实际呈现给客户端(这是一个小小的胜利),因为响应流(和连接,客户端没有“无限加载”)正在关闭。但是代码仍在运行,这并不好(在尝试编写ASPX页面,写入标题等时,显然会产生大量错误。)
所以任何新的领导都会受到欢迎!
总结一下: - 有没有人有一个不那么hacky的方式来实现在同一个URL上共享ASPX页面和MVC视图? - 如果没有,有没有人知道如何确保我的回复真的结束了?
非常感谢提前!
答案 0 :(得分:2)
那么,
对于任何有兴趣的人,我至少可以回答问题1 :)。 当我第一次使用该功能时,我查看了以下(非常接近)的问题:
How to simulate Server.Transfer in ASP.NET MVC?
并尝试了Stan创建的传输方法(使用 httpHandler.ProcessRequest )和 Server.TransferRequest 方法。两者都有我的缺点:
看到我的解决方案显然不是最优的,我不得不回到IIS解决方案,这似乎是生产环境中最好的。
此解决方案适用于页面并在另一个页面上触发无限循环...
当我指出我懒得放弃的东西时,我的url重定向模块。它使用Request.RawUrl来匹配规则,哦,意外, Server.TransferRequest保留原始的Request.RawUrl,而app.Request.Url.AbsolutePath将包含转移到的URL。所以基本上我们的url重写模块总是重定向到原来请求的,试图转移到新的等等。
在网址重写模块中进行了更改,并希望一切仍然像魅力一样(显然很多测试都会遵循这样的改变)......
为了解决开发人员问题,我选择将这两种解决方案结合起来,这可能会使开发和生产之间的不同行为风险更大,但这就是我们为测试服务器提供的...
所以这是我的传输方法最终看起来像:
再一次这是为了从ASPX页面转移到MVC动作,从MVC转移到MVC你可能不需要任何复杂的东西,因为你可以使用TransferResult或只返回一个不同的视图,调用另一个动作等
private static void Transfer(string url) {
if (HttpRuntime.UsingIntegratedPipeline) {
// IIS 7 integrated pipeline, does not work in VS dev server.
HttpContext.Current.Server.TransferRequest(url, true);
}
// for VS dev server, does not work in IIS
var cUrl = HttpContext.Current.Request.Url;
// Create URI builder
var uriBuilder = new UriBuilder(cUrl.Scheme, cUrl.Host, cUrl.Port, HttpContext.Current.Request.ApplicationPath);
// Add destination URI
uriBuilder.Path += url;
// Because UriBuilder escapes URI decode before passing as an argument
string path = HttpContext.Current.Server.UrlDecode(uriBuilder.Uri.PathAndQuery);
// Rewrite path
HttpContext.Current.RewritePath(path, true);
IHttpHandler httpHandler = new MvcHttpHandler();
// Process request
httpHandler.ProcessRequest(HttpContext.Current);
}
答案 1 :(得分:0)
我没有做太多研究,但这里似乎发生在Response.End()
上的事情:
public void End()
{
if (this._context.IsInCancellablePeriod)
{
InternalSecurityPermissions.ControlThread.Assert();
Thread.CurrentThread.Abort(new HttpApplication.CancelModuleException(false));
}
else if (!this._flushing)
{
this.Flush();
this._ended = true;
if (this._context.ApplicationInstance != null)
{
this._context.ApplicationInstance.CompleteRequest();
}
}
}
这至少可以提供“为什么”(_context.IsInCancellablePeriod
)。您可以尝试使用您最喜欢的CLR反编译器来跟踪它。