15

I'm creating a website using MVC3, I'm using the razor syntax to create the views and it's all running under azure.

Currently I'm running under the azure emulator locally.

I have a view at the url: 'http://localhost:81/Blah/Foo'.

In that view I want to get the Url for another action.

To achieve this I use: Url.Action("SomeAction", "SomeController", null, this.Request.Url.Scheme)

However because of the load balancing the azure emulator does the port number the request is made on changes.

i.e. whilst it's running on port 81, the request might come from port 82.

This leads to to create an incorrect url 'http://localhost:82/Blah/Bar' and I get a 400, bad hostname error.

Following the info in this post http://social.msdn.microsoft.com/Forums/en-US/windowsazure/thread/9142db8d-0f85-47a2-91f7-418bb5a0c675/ I found that I could get the correct host and port number using HttpContext.Request.Headers["Host"].

But I can only pass a host-name to Url.Action, if I try passing the host-name and port then it still appends what it thinks is the right port so I end up with localhost:81:82.

EDIT: I found someone with the same problem. They seem to have gathered the same information I have (except they've included a reproduction too) but they don't have a useful fix as I can't specify the port number manually.

http://social.msdn.microsoft.com/Forums/en-US/windowsazuredevelopment/thread/87c729e8-094c-4578-b9d1-9c8ff7311577/

I suppose one fix would be to make my own Url.Action overload that lets me specify the port.

  • Curious but you're using load balancing so your trying to force all users to connect to the same machine they started with? – Buildstarted Oct 17 '11 at 15:30
  • The azure emulator is what's messing with the port numbers. I'd be happy to disable that if it would allow me to carry on working an debug locally :) The core of the issue is that the azure emulator is using different ports, I start on port 81 which works, it then switches to port 82 and and that gives me a 400 bad host http error. –  Oct 17 '11 at 15:32
  • @BuildStarted - You raise a good point here. I'm probably looking at this the wrong way. The issue isn't that it's using the wrong port number, the issue is that whilst the Azure Emulator is load balancing between port 81 and 82 the mvc app (running on IIS, behind the azure emulator) is only accepting on port 81. I should be trying to find out how to correct that. –  Oct 17 '11 at 15:43
  • Interestingly, our load balancer has all the machines on the same ports but since they're different machines there's no conflict. It sounds like a limitation of the Azure emulator and makes sense from a technical point of view. – Buildstarted Oct 17 '11 at 16:17

3 Answers3

25

For everyone coming here who actually NEEDS an absolute path and are behind a load balanced system, here's what I came up with:

//http://stackoverflow.com/questions/126242/how-do-i-turn-a-relative-url-into-a-full-url
public static string AbsoluteAction(this UrlHelper url, string actionName, string controllerName, object routeValues = null)
{
  Uri publicFacingUrl = GetPublicFacingUrl(url.RequestContext.HttpContext.Request, url.RequestContext.HttpContext.Request.ServerVariables);
  string relAction = url.Action(actionName, controllerName, routeValues);
  //this will always have a / in front of it.
  var newPort = publicFacingUrl.Port == 80 || publicFacingUrl.Port == 443 ? "" : ":"+publicFacingUrl.Port.ToString();
  return publicFacingUrl.Scheme + Uri.SchemeDelimiter + publicFacingUrl.Host + newPort + relAction;
}

And then, from https://github.com/aarnott/dotnetopenid/blob/v3.4/src/DotNetOpenAuth/Messaging/HttpRequestInfo.cs via http://go4answers.webhost4life.com/Example/azure-messing-port-numbers-creates-28516.aspx

   /// <summary>
    /// Gets the public facing URL for the given incoming HTTP request.
    /// </summary>
    /// <param name="request">The request.</param>
    /// <param name="serverVariables">The server variables to consider part of the request.</param>
    /// <returns>
    /// The URI that the outside world used to create this request.
    /// </returns>
    /// <remarks>
    /// Although the <paramref name="serverVariables"/> value can be obtained from
    /// <see cref="HttpRequest.ServerVariables"/>, it's useful to be able to pass them
    /// in so we can simulate injected values from our unit tests since the actual property
    /// is a read-only kind of <see cref="NameValueCollection"/>.
    /// </remarks>
internal static Uri GetPublicFacingUrl(HttpRequestBase request, NameValueCollection serverVariables)
{
  //Contract.Requires<ArgumentNullException>(request != null);
  //Contract.Requires<ArgumentNullException>(serverVariables != null);

  // Due to URL rewriting, cloud computing (i.e. Azure)
  // and web farms, etc., we have to be VERY careful about what
  // we consider the incoming URL.  We want to see the URL as it would
  // appear on the public-facing side of the hosting web site.
  // HttpRequest.Url gives us the internal URL in a cloud environment,
  // So we use a variable that (at least from what I can tell) gives us
  // the public URL:
  if (serverVariables["HTTP_HOST"] != null)
  {
    //ErrorUtilities.VerifySupported(request.Url.Scheme == Uri.UriSchemeHttps || request.Url.Scheme == Uri.UriSchemeHttp, "Only HTTP and HTTPS are supported protocols.");
    string scheme = serverVariables["HTTP_X_FORWARDED_PROTO"] ?? request.Url.Scheme;
    Uri hostAndPort = new Uri(scheme + Uri.SchemeDelimiter + serverVariables["HTTP_HOST"]);
    UriBuilder publicRequestUri = new UriBuilder(request.Url);
    publicRequestUri.Scheme = scheme;
    publicRequestUri.Host = hostAndPort.Host;
    publicRequestUri.Port = hostAndPort.Port; // CC missing Uri.Port contract that's on UriBuilder.Port
    return publicRequestUri.Uri;
  }
  // Failover to the method that works for non-web farm enviroments.
  // We use Request.Url for the full path to the server, and modify it
  // with Request.RawUrl to capture both the cookieless session "directory" if it exists
  // and the original path in case URL rewriting is going on.  We don't want to be
  // fooled by URL rewriting because we're comparing the actual URL with what's in
  // the return_to parameter in some cases.
  // Response.ApplyAppPathModifier(builder.Path) would have worked for the cookieless
  // session, but not the URL rewriting problem.
  return new Uri(request.Url, request.RawUrl);
}
scaryman
  • 1,880
  • 1
  • 19
  • 30
1

What happens if you just use Url.Action("Action", "Controller")? That should just generate a relative URL, which should work.

(Or perhaps a better question is: why aren't you using that overload?)

user94559
  • 59,196
  • 6
  • 103
  • 103
  • It would seem in the past we've had trouble with the generation of http and https links which lead to the explicit use of the protocol parameter. However in this case just generating the relative link is exactly what I want and I believe it'll solve all the problems. Many thanks! –  Oct 18 '11 at 08:34
0

I found this worked for me...

var request = HttpContext.Request;
string url = request.Url.Scheme + "://" +
             request.UserHostAddress +  ":" +
             request.Url.Port;
devlord
  • 4,054
  • 4
  • 37
  • 55
spadelives
  • 1,588
  • 13
  • 23
  • 1
    Using referrer for this purpose is very bad idea. Use current url, not referrer url. Referrer is previous url, which can be null. – Hogan Jul 31 '14 at 12:08