166

I need to generate some URLs in a model in ASP.NET MVC. I'd like to call something like UrlHelper.Action() which uses the routes to generate the URL. I don't mind filling the usual blanks, like the hostname, scheme and so on.

Is there any method I can call for that? Is there a way to construct an UrlHelper?

Pablo Fernandez
  • 279,434
  • 135
  • 377
  • 622
  • 1
    I was thinking about this myself, but do be aware that Url.Action will generate a relative URL. Be sure that that's what you want. – Vivian River Apr 26 '13 at 19:29

8 Answers8

283

Helpful tip, in any ASP.NET application, you can get a reference of the current HttpContext

HttpContext.Current

which is derived from System.Web. Therefore, the following will work anywhere in an ASP.NET MVC application:

UrlHelper url = new UrlHelper(HttpContext.Current.Request.RequestContext);
url.Action("ContactUs"); // Will output the proper link according to routing info

Example:

public class MyModel
{
    public int ID { get; private set; }
    public string Link
    {
        get
        {
            UrlHelper url = new UrlHelper(HttpContext.Current.Request.RequestContext);
            return url.Action("ViewAction", "MyModelController", new { id = this.ID });
        }
    }

    public MyModel(int id)
    {
        this.ID = id;
    }
}

Calling the Link property on a created MyModel object will return the valid Url to view the Model based on the routing in Global.asax

Omar
  • 39,496
  • 45
  • 145
  • 213
  • Are you sure there's a HttpContext.Current.Request.RequestContext? HttpContext.Current.Request seems not to have a RequestContext. – Pablo Fernandez Jan 09 '10 at 03:07
  • 1
    Thats odd. I just tested this solution out and it works perfectly. I'm running ASP.NET MVC 2 Preview 2, but I think this works across all versions. Not sure why it's not working for you. Are you creating the class outside of an MVC project? Also make sure there are `using` for both `System.Web` and `System.Web.Mvc` – Omar Jan 09 '10 at 03:18
  • I'm on an ASP.NET MVC 1 project, I thought about missing usings but I have both of them. – Pablo Fernandez Jan 09 '10 at 03:20
  • Not really sure why it's not showing. If anyone else could confirm this doesn't exist in ASP.NET MVC 1 that would be great. I only have one machine with VS2010 and MVC 2 installed. If you're interested, MVC RC 2 http://haacked.com/archive/2009/12/16/aspnetmvc-2-rc.aspx – Omar Jan 09 '10 at 03:36
  • Thanks Baddie. Worked like a champ. Using MVC 2 though. – Rake36 Jul 09 '10 at 15:43
  • 6
    Note that Request.RequestContex is supported in .NET4+ – h--n Oct 08 '11 at 13:31
  • Doesn't work. `UrlHelper.Action` will always return `null`. See also https://stackoverflow.com/questions/18151014/mvc-urlhelper-called-from-service#18595481 – Florian Winter Mar 09 '18 at 14:53
70

I like Omar's answer but that's not working for me. Just for the record this is the solution I'm using now:

var httpContext = HttpContext.Current;

if (httpContext == null) {
  var request = new HttpRequest("/", "http://example.com", "");
  var response = new HttpResponse(new StringWriter());
  httpContext = new HttpContext(request, response);
}

var httpContextBase = new HttpContextWrapper(httpContext);
var routeData = new RouteData();
var requestContext = new RequestContext(httpContextBase, routeData);

return new UrlHelper(requestContext);
Omar
  • 39,496
  • 45
  • 145
  • 213
Pablo Fernandez
  • 279,434
  • 135
  • 377
  • 622
  • It contains the URL of my site. There, I removed it. – Pablo Fernandez Feb 07 '10 at 14:29
  • 4
    Considering that the UrlHelper class relies on the request context (and the HTTP context), constructing those context objects manually could yield unexpected results. If HttpContext.Current is null and you use this approach, I would proceed with caution. – Sean May 05 '15 at 00:24
  • 4
    Beware this answer - the dummied RequestContext results in a UrlHelper that always returns empty string. – gknicker May 30 '16 at 20:46
  • Anyone know how to make this work in .NET Core? – Johannes Mols Jun 16 '23 at 09:07
50

A UrlHelper can be constructed from within a Controller action with the following:

 var url = new UrlHelper(this.ControllerContext.RequestContext);
 url.Action(...);

Outside of a controller, a UrlHelper can be constructed by creating a RequestContext from the RouteTable.Routes RouteData.

HttpContextWrapper httpContextWrapper = new HttpContextWrapper(System.Web.HttpContext.Current);
UrlHelper urlHelper = new UrlHelper(new RequestContext(httpContextWrapper, RouteTable.Routes.GetRouteData(httpContextWrapper)));

(Based on Brian's answer, with a minor code correction added.)

Nathan Taylor
  • 24,423
  • 19
  • 99
  • 156
8

Yes, you can instantiate it. You can do something like:

var ctx = new HttpContextWrapper(HttpContext.Current);
UrlHelper helper = new UrlHelper(
   new RequestContext(ctx,
   RouteTable.Routes.GetRouteData(ctx));

RouteTable.Routes is a static property, so you should be OK there; to get a HttpContextBase reference, HttpContextWrapper takes a reference to HttpContext, and HttpContext delivers that.

Brian Mains
  • 50,520
  • 35
  • 148
  • 257
4

After trying all the other answers, I ended up with

$"/api/Things/Action/{id}"

Haters gonna hate ¯\_(ツ)_/¯

Florian Winter
  • 4,750
  • 1
  • 44
  • 69
0

I was trying to do something similar from within a page (outside of a controller).

UrlHelper did not allow me to construct it as easily as Pablos answer, but then I remembered a old trick to effective do the same thing:

string ResolveUrl(string pathWithTilde)
vGHazard
  • 117
  • 1
  • 3
0

Previous responses didn't help me. My approach has been create an action in my Home controller with the same functionality that UrlHelper.

    [HttpPost]
    [Route("format-url")]
    public ActionResult FormatUrl()
    {
        string action = null;
        string controller = null;
        string protocol = null;
        dynamic parameters = null;

        foreach (var key in this.Request.Form.AllKeys)
        {
            var value = this.Request.Form[key];
            if (key.Similar("action"))
            {
                action = value;
            }
            else if (key.Similar("controller"))
            {
                controller = value;
            }
            else if (key.Similar("protocol"))
            {
                protocol = value;
            }
            else if (key.Similar("parameters"))
            {
                JObject jObject = JsonConvert.DeserializeObject<dynamic>(value);

                var dict = new Dictionary<string, object>();
                foreach (var item in jObject)
                {
                    dict[item.Key] = item.Value;
                }

                parameters = AnonymousType.FromDictToAnonymousObj(dict);
            }
        }

        if (string.IsNullOrEmpty(action))
        {
            return new ContentResult { Content = string.Empty };
        }

        int flag = 1;
        if (!string.IsNullOrEmpty(controller))
        {
            flag |= 2;
        }

        if (!string.IsNullOrEmpty(protocol))
        {
            flag |= 4;
        }

        if (parameters != null)
        {
            flag |= 8;
        }

        var url = string.Empty;
        switch (flag)
        {
            case 1: url = this.Url.Action(action); break;
            case 3: url = this.Url.Action(action, controller); break;
            case 7: url = this.Url.Action(action, controller, protocol); break;
            case 9: url = this.Url.Action(action, parameters); break;
            case 11: url = this.Url.Action(action, controller, parameters); break;
            case 15: url = this.Url.Action(action, controller, parameters, protocol); break;
        }

        return new ContentResult { Content = url };
    }

Been an action, you can request it from anywhere, even inside the Hub:

            var postData = "action=your-action&controller=your-controller";

            // Add, for example, an id parameter of type integer
            var json = "{\"id\":3}";
            postData += $"&parameters={json}";

            var data = Encoding.ASCII.GetBytes(postData);

#if DEBUG
            var url = $"https://localhost:44301/format-url";
#else
            var url = $"https://your-domain-name/format-url";
#endif

            var request = (HttpWebRequest)WebRequest.Create(url);
            request.Method = "POST";
            request.ContentType = "application/text/plain";
            request.ContentLength = data.Length;

            using (var stream = request.GetRequestStream())
            {
                stream.Write(data, 0, data.Length);
            }

            var response = (HttpWebResponse)request.GetResponse();
            var link = new StreamReader(stream: response.GetResponseStream()).ReadToEnd();

You can get source code of AnonymousType here.

Victor
  • 2,313
  • 2
  • 5
  • 13
-30

I think what you're looking for is this:

Url.Action("ActionName", "ControllerName");
user246874
  • 29
  • 2