伪造MVC Server.Transfer:Response.End()不会结束我的线程

时间:2012-09-27 10:12:05

标签: asp.net asp.net-mvc server.transfer

我在这里有两个问题,第二个是无关紧要的,如果第一个问题得到解答,但在我看来技术上仍然很有意思......我会尽量明确:

  • 第一个问题:我的目标是在MVC中伪造一个Server.Transfer,有没有任何下降的方法来做到这一点,我发现了很多关于它的文章,但大多数关于重定向/重新路由,这在我的案例(不是我至少可以想到的)。

以下是我们网站的两个版本,一个是“桌面”版本,另一个是移动版本。我们的营销人员希望两个版本的主页都在同一个网址上提供(因为SEO专家这么说)。

这听起来很简单,在大多数情况下都是这样,除了......我们的桌面站点是.NET 4.0 ASPX站点,我们的移动站点是MVC,两者都运行在同一站点(同一个项目,相同的apppool,相同的应用程序)。

因为桌面版本占我们流量的大约95%,这应该是默认值,并且我们希望仅当用户在移动设备上时才从ASPX代码“转移”(因此相同的URL)到MVC视图或者真的想看看手机版。据我所知,到目前为止,没有简单的方法可以做到这一点(Server.Transfer只执行一个新的处理程序 - 因此页面 - 如果有一个物理文件)。因此到目前为止,有任何人以适当的方式完成了这个问题吗?

这让我想到:

  • 第二个问题:我确实构建了自己的转移到MVC机制,但后来发现Response.End()实际上不再结束正在运行的线程,有没有人知道为什么?

显然,我不期待任何答案,所以这就是我在做的事情:

在需要转移到移动设备的页面中,我执行以下操作:

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视图? - 如果没有,有没有人知道如何确保我的回复真的结束了?

非常感谢提前!

2 个答案:

答案 0 :(得分:2)

那么,

对于任何有兴趣的人,我至少可以回答问题1 :)。 当我第一次使用该功能时,我查看了以下(非常接近)的问题:

How to simulate Server.Transfer in ASP.NET MVC?

并尝试了Stan创建的传输方法(使用 httpHandler.ProcessRequest )和 Server.TransferRequest 方法。两者都有我的缺点:

  • 第一个在IIS中不起作用(因为我需要在页面中调用它,而这似乎已经太晚了)。
  • 第二个对于那些需要在IIS中运行其网站的开发人员来说非常烦人(没有biggy,但仍然......)。

看到我的解决方案显然不是最优的,我不得不回到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反编译器来跟踪它。