3

我正在尝试在 AppHarbor 上托管 MVC 3 应用程序(FunnelWeb)。由于我仍然不清楚的原因,当我的路线只是一个控制器 + 操作(例如 mysite/admin 是 Admin+Index 并且 mysite/login 是 Admin+login)时,一切正常,但如果我在路线中有其他东西(例如像 {*page} 这样的变量)我的 URL 将是 mysite:12345/mypage(其中 12345 是 AppHarbor 分配的端口号,mypage 是我请求的页面的名称)。这使得请求失败,因为端口 12345 未公开。

AppHarbor 使用负载平衡在多个 IIS 之间分配请求。这是他们做事的方式,这就是为什么在内部将请求路由到一些非标准端口的原因。我对此没有问题,但我对试图将我路由到该内部 URL 的 MVC 有问题。

我不是在这里指指点点;这不是任何人的错 :) 所以让我们转向这个问题:

  1. 为什么仅使用 Controller+Action 请求路由与使用 {*page} 等变量请求路由之间存在差异?请技术性好:)
  2. 是如何在 AppHarbor 中处理请求的示例,但是,它似乎需要我修改所有控制器(OMG)。有没有办法在不修改我的控制器的情况下实现这一点?
  3. 欢迎任何其他建议:)

提前致谢。

更新:巧合的是,我观察到的行为与我得出的结论相符。但是,该问题与 ASP.Net MVC 路由无关。简短的故事是,FunnelWeb 强制使用小写 URL,因此,每当它收到对资源的​​请求时,如果需要,它会将其转换为小写,并发出 301 响应。问题是,在为 301 响应创建 URL 时,请求 URL(绝对 URL)现在是从负载均衡器向 IIS 发出请求时使用的 URL,而不是从客户端发出的 URL;因此,请求失败。

4

3 回答 3

3

这是 AppHarbor 上的 FunnelWeb url 生成的已知问题。当使用标准 MVC 方法生成相对 URL 时,这不是问题。AppHarbor 有一个关于如何在知识库中生成公共 URL 的简短指南和示例。

于 2011-08-19T22:01:17.627 回答
2

有一种方法,但它需要几个类。

当 ASP.NET MVC 注册一个路由时,它定义了一个路由处理程序。此路由处理程序返回处理请求的 HTTP 处理程序。如果您使用返回自定义 HTTP 处理程序的自定义路由处理程序,则可以使用几个装饰器类重写 HTTP 上下文。

首先创建一个从基类派生的 and 并将所有方法和属性包装到一个内部实例HttpContextProxyHttpRequestProxy我已经完成了艰苦的工作

接下来创建装饰器,首先是 HTTP 上下文装饰器:

using System.Web;

public class HttpContextDecorator : HttpContextProxy
{
    public HttpContextDecorator(HttpContextBase innerHttpContext)
        : base(innerHttpContext)
    {
    }

    public override HttpRequestBase Request
    {
        get
        {
            return new HttpRequestDecorator(base.Request);
        }
    }
}

HTTP 请求装饰器:

using System;
using System.Web;

public class HttpRequestDecorator : HttpRequestProxy
{
    public HttpRequestDecorator(HttpRequestBase innerHttpRequest)
        : base(innerHttpRequest)
    {
    }

    public override bool IsSecureConnection
    {
        get
        {
            return string.Equals(Headers["X-Forwarded-Proto"], "https", StringComparison.OrdinalIgnoreCase);
        }
    }

    public override Uri Url
    {
        get
        {
            var url = base.Url;
            var urlBuilder = new UriBuilder(url);

            if (IsSecureConnection)
            {
                urlBuilder.Port = 443;
                urlBuilder.Scheme = "https";
            }
            else
            {
                urlBuilder.Port = 80;
            }

            return urlBuilder.Uri;
        }
    }

    public override string UserHostAddress
    {
        get
        {
            const string forwardedForHeader = "HTTP_X_FORWARDED_FOR";
            var forwardedFor = ServerVariables[forwardedForHeader];
            if (forwardedFor != null)
            {
                return forwardedFor;
            }

            return base.UserHostAddress;
        }
    }
}

如前所述,您还需要覆盖 MVC 类 - 这里是 HTTP 处理程序:

using System;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;

public class CustomMvcHandler : MvcHandler
{
    public CustomMvcHandler(RequestContext requestContext)
        : base(requestContext)
    {
        requestContext.HttpContext = new HttpContextDecorator(requestContext.HttpContext);
    }

    protected override IAsyncResult BeginProcessRequest(HttpContextBase httpContext, AsyncCallback callback, object state)
    {
        httpContext = new HttpContextDecorator(httpContext);
        return base.BeginProcessRequest(httpContext, callback, state);
    }

    protected override void ProcessRequest(HttpContextBase httpContext)
    {
        httpContext = new HttpContextDecorator(httpContext);
        base.ProcessRequest(httpContext);
    }
}

然后路由处理程序:

using System.Web;
using System.Web.Mvc;
using System.Web.Routing;

public class CustomMvcRouteHandler : MvcRouteHandler
{
    protected override IHttpHandler GetHttpHandler(RequestContext requestContext)
    {
        return new CustomMvcHandler(requestContext);
    }
}

最后,您需要替换所有已注册路由的关联处理程序(或从头开始正确映射它们):

var routes = RouteTable.Routes.OfType<Route>().Where(x => x.RouteHandler is MvcRouteHandler);
foreach (var route in routes)
{
    route.RouteHandler = new CustomMvcRouteHandler();
}
于 2011-08-19T23:23:57.870 回答
2

您现在可能只需要以下内容:

<appSettings>
  <!-- AppHarbor Setting to stop AppHb load balancer internal port numbers from showing up in URLs-->
  <add key="aspnet:UseHostHeaderForRequestUrl" value="true" />
</appSettings>

这是 AppHarbor 支持页面上的更新,网址为http://support.appharbor.com/kb/getting-started/workaround-for-generating-absolute-urls-without-port-number

MSDN对 UseHostHeaderForRequestUrl说了以下内容:

aspnet:UseHostHeaderForRequestUrl - 如果此值属性为 false [默认],则 Url 属性是根据 Web 服务器提供的主机、端口和路径动态构建的。如果此 value 属性为 true,则使用传入的“Host”标头提供的主机和端口以及 Web 服务器提供的路径动态构建 Url 属性。

于 2014-08-12T14:15:38.107 回答