user310988
user310988

Reputation:

How do I get Url.Action to use the right port number?

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.

Upvotes: 15

Views: 7684

Answers (3)

spadelives
spadelives

Reputation: 1628

I found this worked for me...

var request = HttpContext.Request;
string url = request.Url.Scheme + "://" +
             request.UserHostAddress +  ":" +
             request.Url.Port;

Upvotes: 0

scaryman
scaryman

Reputation: 1900

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);
}

Upvotes: 25

user94559
user94559

Reputation: 60153

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?)

Upvotes: 1

Related Questions