【问题标题】:Faking MVC Server.Transfer: Response.End() does not end my thread伪造 MVC Server.Transfer:Response.End() 不会结束我的线程
【发布时间】:2012-09-27 10:12:05
【问题描述】:

我在这里有两个问题,如果第一个问题得到回答,第二个问题无关紧要,但我认为在技术上仍然很有趣......我会尽量说清楚:

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

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

这听起来微不足道和简单,在大多数情况下,除了...相同的应用程序池,相同的应用程序)。

因为桌面版本代表了我们大约 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 页面、编写标头等时,显然还会产生大量错误)。

所以任何新的线索都会受到欢迎!

总结一下: - 有没有人有一种不那么老套的方法来实现在同一个 url 上共享 ASPX 页面和 MVC 视图? - 如果没有,有没有人知道我如何确保我的响应真的被结束了?

非常感谢!

【问题讨论】:

  • “SEO 专家”说,最常见和全球公认的合作伙伴来运行网站的移动版本不是一件好事吗?太棒了!
  • 内容+1;讽刺+1
  • @Pluc:确实,在这里给出了我的意见,SEO专家更贵,他赢了:)。但无论如何,不​​幸的是,在我的场景中并不是每一个都有帮助(当然,除非您作为 SEO 专家为您的服务收取更多费用)。谢谢你说的很明显:)。

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


【解决方案1】:

嗯,

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

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

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

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

看到我的解决方案显然不是最优的,我不得不回到 IIS 解决方案,这似乎是最适合生产环境的解决方案。

此解决方案适用于一个页面并在另一个页面上触发了无限循环...

那时我被指出我懒惰地丢弃的不是原因:我们的 url 重定向模块。它使用 Request.RawUrl 来匹配规则,哦,令人惊讶的是,Server.TransferRequest 保留了原始的 Request.RawUrl,而 app.Request.Url.AbsolutePath 将包含转移到的 url。 所以基本上我们的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);
}

【讨论】:

    【解决方案2】:

    我没有做太多研究,但这就是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 反编译器进行跟踪。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-02-06
      • 2010-12-14
      • 1970-01-01
      • 2022-01-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多