412

Is there a built-in way of getting the full URL of an action?

I am looking for something like GetFullUrl("Action", "Controller") that would return something like http://www.fred.com/Controller/Action.

The reason I am looking for this is to avoid hardcoding URLs in automated emails that are generated so that the URLs can always be generated relative to the current location of the site.

George Stocker
  • 57,289
  • 29
  • 176
  • 237
Alan Spark
  • 8,152
  • 8
  • 56
  • 91

6 Answers6

622

There is an overload of Url.Action that takes your desired protocol (e.g. http, https) as an argument - if you specify this, you get a fully qualified URL.

Here's an example that uses the protocol of the current request in an action method:

var fullUrl = this.Url.Action("Edit", "Posts", new { id = 5 }, this.Request.Url.Scheme);

HtmlHelper (@Html) also has an overload of the ActionLink method that you can use in razor to create an anchor element, but it also requires the hostName and fragment parameters. So I'd just opt to use @Url.Action again:

<span>
  Copy
  <a href='@Url.Action("About", "Home", null, Request.Url.Scheme)'>this link</a> 
  and post it anywhere on the internet!
</span>
Jalal
  • 6,594
  • 9
  • 63
  • 100
Paddy
  • 33,309
  • 15
  • 79
  • 114
  • 8
    No bother - you'd think that there should still be a better way of doing this, but hey... – Paddy Jan 05 '10 at 11:26
  • When testing locally with Azure emulator this returns port 82 instead of the correct port 81. Not sure if this works well when published or not. The answer proposed by Marius Schulz works perfect though. – angularsen Oct 16 '12 at 04:52
  • 1
    The right url is `https://127.0.0.1/Home/Index` but it generates `https://127.0.0.1:444/Home/Index`. It must have no port. I'm running on Azure emulator. Please help. – fiberOptics Mar 14 '14 at 08:01
  • 2
    @fiberOptics - rather late for you, but for others: the issue you're having is that the you're running the Azure Emulator on a non-standard port (there's usually a note about that as it starts), as such the port is required for this work. In production it should use the standard port (443) so it won't be included in the URL. – Zhaph - Ben Duguid Aug 07 '15 at 10:15
  • 12
    In MVC6 I did it like this `Log in` – Christopher Oct 10 '15 at 05:44
  • 1
    This doesn't work; it loses the Port. If your site is running on a non-standard port this will generate broken URLs – Orion Edwards Oct 21 '16 at 03:52
  • Doesn't work, because `this.Url` doesn't have a method `Action`. – Florian Winter Mar 09 '18 at 15:03
  • I think it would be good to specify that if you have no parameters (routeValues), you HAVE to put null to get the full url. – Robin Leblond Oct 05 '20 at 11:20
142

As Paddy mentioned: if you use an overload of UrlHelper.Action() that explicitly specifies the protocol to use, the generated URL will be absolute and fully qualified instead of being relative.

I wrote a blog post called How to build absolute action URLs using the UrlHelper class in which I suggest to write a custom extension method for the sake of readability:

/// <summary>
/// Generates a fully qualified URL to an action method by using
/// the specified action name, controller name and route values.
/// </summary>
/// <param name="url">The URL helper.</param>
/// <param name="actionName">The name of the action method.</param>
/// <param name="controllerName">The name of the controller.</param>
/// <param name="routeValues">The route values.</param>
/// <returns>The absolute URL.</returns>
public static string AbsoluteAction(this UrlHelper url,
    string actionName, string controllerName, object routeValues = null)
{
    string scheme = url.RequestContext.HttpContext.Request.Url.Scheme;

    return url.Action(actionName, controllerName, routeValues, scheme);
}

You can then simply use it like that in your view:

@Url.AbsoluteAction("Action", "Controller")
Ahmed Mansour
  • 527
  • 5
  • 13
Marius Schulz
  • 15,976
  • 12
  • 63
  • 97
  • Wish you got more upvotes, this is actually a no-brainer that should be in the framework IMO – Roger May 04 '12 at 03:55
  • 9
    The only thing i would add (or modify) is to replace the literal "http" with HttpContext.Current.Request.Url.Scheme. This will allow https to be used when appropriate. – Jay Jun 07 '12 at 03:08
  • @Jay, thanks for your suggestion, I have updated my code example. I received the same feedback on my blog post ;-). – Marius Schulz Jun 07 '12 at 09:53
  • 1
    Using the Scheme from the original URL ensure that you do not move from HTTPS to HTTP by mistake. Prevent TLS disclosure. You get my vote. – Pierre-Alain Vigeant Jul 16 '12 at 15:55
  • 1
    @AndreasLarsen Hmm, are you sure that this is the solution to your problem? Technically, the above extension method doesn't do anything else than calling the `UrlHelper.Action` method with the current HTTP request's scheme. – Marius Schulz Oct 18 '12 at 06:45
  • @MariusSchulz, You are correct. I misread and am using the solution from a different answer that fixes the bug where port 82 is returned instead of the correct port 81 when running locally with Azure deployment project: http://stackoverflow.com/a/11888846/134761 – angularsen Oct 18 '12 at 11:11
  • 2
    This is great. Thanks. Small note... Would method name "ActionAbsolute" be a better choice for sake of VS autocomplete as Url.Action() will come next to Url.ActionAbsolute(). – Max Apr 25 '13 at 09:56
  • [mvc newb] where would i user this method? my own class somewhere or a controller? – heyNow Oct 03 '13 at 13:59
  • @heyNow Since you need access to a URL helper, you'd typically use it within your view or your controller. – Marius Schulz Oct 03 '13 at 23:09
4

This what you need to do.

@Url.Action(action,controller, null, Request.Url.Scheme)
Salman Zahid
  • 328
  • 1
  • 7
  • 8
    This is the same as the accepted answer. Answers are best posted when something new is introduced. – IAmGroot Feb 16 '17 at 11:34
2

This question is specific to ASP .NET however I am sure some of you will benefit of system agnostic javascript which is beneficial in many situations.

UPDATE: The way to get url formed outside of the page itself is well described in answers above.

Or you could do a oneliner like following:

new UrlHelper(actionExecutingContext.RequestContext).Action(
    "SessionTimeout", "Home", 
    new {area = string.Empty}, 
    actionExecutingContext.Request.Url!= null? 
    actionExecutingContext.Request.Url.Scheme : "http"
);

from filter or:

new UrlHelper(this.Request.RequestContext).Action(
    "Details", 
    "Journey", 
    new { area = productType }, 
    this.Request.Url!= null? this.Request.Url.Scheme : "http"
);

However quite often one needs to get the url of current page, for those cases using Html.Action and putting he name and controller of page you are in to me feels awkward. My preference in such cases is to use JavaScript instead. This is especially good in systems that are half re-written MVT half web-forms half vb-script half God knows what - and to get URL of current page one needs to use different method every time.

One way is to use JavaScript to get URL is window.location.href another - document.URL

Matas Vaitkevicius
  • 58,075
  • 31
  • 238
  • 265
  • 1
    This would only work to present the URL of the current page. What the OP wants to do is pass the names of a .Net Controller and View to generate the URL to get to that page. – Matthew Verstraete Feb 09 '16 at 16:33
1

I was having an issue with this, my server was running behind a load balancer. The load balancer was terminating the SSL/TLS connection. It then passed the request to the web servers using http.

Using the Url.Action() method with Request.Url.Schema, it kept creating a http url, in my case to create a link in an automated email (which my PenTester didn't like).

I may have cheated a little, but it is exactly what I needed to force a https url:

<a href="@Url.Action("Action", "Controller", new { id = Model.Id }, "https")">Click Here</a>

I actually use a web.config AppSetting so I can use http when debugging locally, but all test and prod environments use transformation to set the https value.

Matt D
  • 3,289
  • 1
  • 15
  • 29
  • `Request.Url.Schema` uses the schema of the "current request" to determine HTTP vs HTTPS. If your anchor tag was generated by an HTTP request, then the schema won't be HTTPS. Hardcoding a secure protocol seems fine; I don't think it's "cheating" to do this, and I'm surprised the other answers didn't acknowledge that using `Request.Url.Schema` for a site that serves both HTTP & HTTPS resources could be an issue. App Settings / Web Config is probably the way to go if the protocol needs to be different on different environments. – Lovethenakedgun Jun 20 '21 at 12:40
0

This may be just me being really, really picky, but I like to only define constants once. If you use any of the approaches defined above, your action constant will be defines multiple times.

To avoid this, you can do the following:

    public class Url
    {
        public string LocalUrl { get; }

        public Url(string localUrl)
        {
            LocalUrl = localUrl;
        }

        public override string ToString()
        {
            return LocalUrl;
        }
    }

    public abstract class Controller
    {
        public Url RootAction => new Url(GetUrl());

        protected abstract string Root { get; }

        public Url BuildAction(string actionName)
        {
            var localUrl = GetUrl() + "/" + actionName;
            return new Url(localUrl);
        }

        private string GetUrl()
        {
            if (Root == "")
            {
                return "";
            }

            return "/" + Root;
        }

        public override string ToString()
        {
            return GetUrl();
        }
    }

Then create your controllers, say for example the DataController:

    public static readonly DataController Data = new DataController();
    public class DataController : Controller
    {
        public const string DogAction = "dog";
        public const string CatAction = "cat";
        public const string TurtleAction = "turtle";

        protected override string Root => "data";

        public Url Dog => BuildAction(DogAction);
        public Url Cat => BuildAction(CatAction);
        public Url Turtle => BuildAction(TurtleAction);
    }

Then just use it like:

    // GET: Data/Cat
    [ActionName(ControllerRoutes.DataController.CatAction)]
    public ActionResult Etisys()
    {
        return View();
    }

And from your .cshtml (or any code)

<ul>
    <li><a href="@ControllerRoutes.Data.Dog">Dog</a></li>
    <li><a href="@ControllerRoutes.Data.Cat">Cat</a></li>
</ul>

This is definitely a lot more work, but I rest easy knowing compile time validation is on my side.

Jorge Aguirre
  • 2,787
  • 3
  • 20
  • 27