102

In my applications, I often have to use relative paths. For example, when I reference JQuery, I usually do so like this:

<script type="text/javascript" src="../Scripts/jquery-1.2.6.js"></script>

Now that I'm making the transition to MVC, I need to account for the different paths a page might have, relative to the root. This was of course an issue with URL rewriting in the past, but I managed to work around it by using consistent paths.

I'm aware that the standard solution is to use absolute paths such as:

<script type="text/javascript" src="/Scripts/jquery-1.2.6.js"></script>

but this will not work for me as during the development cycle, I have to deploy to a test machine on which the app will run in a virtual directory. Root relative paths don't work when the root changes. Also, for maintenance reasons, I cannot simply change out all the paths for the duration of deploying the test - that would be a nightmare in itself.

So what's the best solution?

Edit:

Since this question is still receiving views and answers, I thought it might be prudent to update it to note that as of Razor V2, support for root-relative urls is baked in, so you can use

<img src="~/Content/MyImage.jpg">

without any server-side syntax, and the view engine automatically replaces ~/ with whatever the current site root is.

Chris
  • 27,596
  • 25
  • 124
  • 225
  • Late to the game, but [this post](http://www.west-wind.com/weblog/posts/132081.aspx) has a very complete summary of handling ASP.Net paths. – plyawn Mar 02 '11 at 21:06

9 Answers9

95

Try this:

<script type="text/javascript" src="<%=Url.Content("~/Scripts/jquery-1.2.6.js")%>"></script>

Or use MvcContrib and do this:

<%=Html.ScriptInclude("~/Content/Script/jquery.1.2.6.js")%>
Tim Scott
  • 15,106
  • 9
  • 65
  • 79
  • 1
    This gets asked so often it should be a FAQ, I think they need to include an example in the template. – Simon Steele Nov 25 '08 at 14:05
  • Awesome, this really got me out of a bind. Thanks! – Jared Jan 06 '09 at 04:55
  • 2
    (I know this post is old) - Doesn't using <%=Url.Content("~/Scripts/jquery-1.2.6.js")%> make the server render the path, whereas, if you used "/Scripts/jquery-1.2.6.js", it would just be served straight up to the client, therefore, reducing one more thing the server has to do? I thought i read somewhere the more you can avoid having the server process, the better - especially with static content like *.js paths? I realize this uses minimal resources, but if you had a couple hundred/thousand Url.Content() in your app, that's a couple nanoseconds shaved off, no? – Losbear Oct 01 '12 at 13:28
55

While an old post, new readers should know that Razor 2 and later (default in MVC4+) completely resolves this problem.

Old MVC3 with Razor 1:

<a href="@Url.Content("~/Home")">Application home page</a>

New MVC4 with Razor 2 and later:

<a href="~/Home">Application home page</a>

No awkward Razor function-like syntax. No non-standard markup tags.

Prefixing a path in any HTML attributes with a tilde ('~') tells Razor 2 to "just make it work" by substituting the correct path. It's great.

Charles Burns
  • 10,310
  • 7
  • 64
  • 81
  • Yes, and given the simplicity of parsing the ~/ prefix, I wonder why something like this wasn't built in to ASP.NET from the start. – Chris Sep 17 '12 at 15:24
  • 4
    I've often found that the simpler the design, the more thought has gone into it. – Charles Burns Jan 29 '13 at 16:34
  • 1
    This answer is slightly misleading. The syntax posted for MVC4 is in fact dependent on the razor engine. It may not use any special markup, but only the Razor v2+ engine handles the syntax shown correctly. – Chris Jan 30 '13 at 03:22
  • 1
    You're right, @Chris. I've updated the answer to reflect this. – Charles Burns Jun 17 '13 at 16:27
10

Breaking change - MVC 5

Watch out for a breaking change change in MVC 5 (from the MVC 5 release notes)

Url Rewrite and Tilde(~)

After upgrading to ASP.NET Razor 3 or ASP.NET MVC 5, the tilde(~) notation may no longer work correctly if you are using URL rewrites. The URL rewrite affects the tilde(~) notation in HTML elements such as <A/>, <SCRIPT/>, <LINK/>, and as a result the tilde no longer maps to the root directory.

For example, if you rewrite requests for asp.net/content to asp.net, the href attribute in <A href="~/content/"/> resolves to /content/content/ instead of /. To suppress this change, you can set the IIS_WasUrlRewritten context to false in each Web Page or in Application_BeginRequest in Global.asax.

They don't actually explain how to do it, but then I found this answer:

If you are running in IIS 7 Integrated Pipeline mode try putting the following in your Global.asax:

 protected void Application_BeginRequest(object sender, EventArgs e)
 {
     Request.ServerVariables.Remove("IIS_WasUrlRewritten");
 }

Note: You may want to check Request.ServerVariables actually contains IIS_WasUrlRewritten first to be sure this is what your problem is.


PS. I thought I had a situation where this was happening to me and I was getting src="~/content/..." URLS generated into my HTML - but it turned out something just wasn't refreshing when my code was being compiled. Editing and resaving the Layout and page cshtml files somehow triggered something to work.

Community
  • 1
  • 1
Simon_Weaver
  • 140,023
  • 84
  • 646
  • 689
6

In ASP.NET I usually use <img src='<%= VirtualPathUtility.ToAbsolute("~/images/logo.gif") %>' alt="Our Company Logo"/>. I don't see why a similar solution shouldn't work in ASP.NET MVC.

kͩeͣmͮpͥ ͩ
  • 7,783
  • 26
  • 40
6
<script src="<%=ResolveUrl("~/Scripts/jquery-1.2.6.min.js") %>" type="text/javascript"></script>

Is what I used. Change path to match your example.

Jesper Palm
  • 7,170
  • 31
  • 36
5

For what it's worth, I really hate the idea of littering my app with server tags just to resolve paths, so I did a bit more research and opted to use something I'd tried before for rewriting links - a response filter. In this way, I can prefix all absolute paths with a known prefix and replace it at runtime using the Response.Filter object and not have to worry about unnecessary server tags. The code is posted below in case it will help anyone else.

using System;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;

namespace Demo
{
    public class PathRewriter : Stream
    {
        Stream filter;
        HttpContext context;
        object writeLock = new object();
        StringBuilder sb = new StringBuilder();

        Regex eofTag = new Regex("</html>", RegexOptions.IgnoreCase | RegexOptions.Compiled);
        Regex rootTag = new Regex("/_AppRoot_", RegexOptions.IgnoreCase | RegexOptions.Compiled);

        public PathRewriter(Stream filter, HttpContext context)
        {
            this.filter = filter;
            this.context = context;
        }

        public override void Write(byte[] buffer, int offset, int count)
        {
            string temp;

            lock (writeLock)
            {
                temp = Encoding.UTF8.GetString(buffer, offset, count);
                sb.Append(temp);

                if (eofTag.IsMatch(temp))
                    RewritePaths();
            }
        }

        public void RewritePaths()
        {
            byte[] buffer;
            string temp;
            string root;

            temp = sb.ToString();
            root = context.Request.ApplicationPath;
            if (root == "/") root = "";

            temp = rootTag.Replace(temp, root);
            buffer = Encoding.UTF8.GetBytes(temp);
            filter.Write(buffer, 0, buffer.Length);
        }

        public override bool CanRead
        {
            get { return true; }
        }

        public override bool CanSeek
        {
            get { return filter.CanSeek; }
        }

        public override bool CanWrite
        {
            get { return true; }
        }

        public override void Flush()
        {
            return;
        }

        public override long Length
        {
            get { return Encoding.UTF8.GetBytes(sb.ToString()).Length; }
        }

        public override long Position
        {
            get { return filter.Position; }
            set { filter.Position = value; }
        }

        public override int Read(byte[] buffer, int offset, int count)
        {
            return filter.Read(buffer, offset, count);
        }

        public override long Seek(long offset, SeekOrigin origin)
        {
            return filter.Seek(offset, origin);
        }

        public override void SetLength(long value)
        {
            throw new NotImplementedException();
        }
    }

    public class PathFilterModule : IHttpModule
    {
        public void Dispose()
        {
            return;
        }

        public void Init(HttpApplication context)
        {
            context.ReleaseRequestState += new EventHandler(context_ReleaseRequestState);
        }

        void context_ReleaseRequestState(object sender, EventArgs e)
        {
            HttpApplication app = sender as HttpApplication;
            if (app.Response.ContentType == "text/html")
                app.Response.Filter = new PathRewriter(app.Response.Filter, app.Context);
        }
    }
}
Chris
  • 27,596
  • 25
  • 124
  • 225
4

The Razor view engine for MVC 3 makes it even easier and cleaner to use virtual-root relative paths that are properly resolved at run-time. Just drop the Url.Content() method into the href attribute value and it will resolve properly.

<a href="@Url.Content("~/Home")">Application home page</a>
JPC
  • 412
  • 4
  • 6
1

Like Chris, I really can't stand having to put bloated server-side tags inside my clean markup just purely to tell the stupid thing to look from the root upwards. That should be a very simple, reasonable thing to ask for. But I also hate the idea of having to go to the effort of writing any custom C# classes to do such a simple thing, why should I have to? What a waste of time.

For me, I simply compromised on "perfection" and hardcoded the virtual directory's root path name inside my path references. So like this:

<script type="text/javascript" src="/MyProject/Scripts/jquery-1.2.6.js"></script>

No server-side processing or C# code required to resolve the URL, which is best for performance although I know it would be negligible regardless. And no bloated ugly server-side chaos in my nice clean markup.

I'll just have to live with knowing that this is hardcoded and will need to be removed when the thing migrates to a proper domain instead of http://MyDevServer/MyProject/

Cheers

Aaron
  • 1,802
  • 3
  • 23
  • 50
  • 1
    I voted up to bring you back to 0. Totally agree with your sentiments. I'm new to web dev after 5 years in pure C# and what a disastrours land of spaghetti chaos it all is. – Luke Puplett May 18 '10 at 20:35
  • This seems like an acceptable compromise until you need to do something like deploy to a nested web app. Using the resolver mark-up will fix this but your static link will be broken. Example: you build locally against the built-in web server and then push the app to domain.com/myNewWebApp – plyawn Mar 02 '11 at 20:57
  • This will break in a lot of production scenarios – Oskar Duveborn Jul 29 '13 at 11:40
  • I quite like this solution: http://thoughtstuff.co.uk/2013/02/fixing-relative-css-paths-when-using-net-mvc-4-bundling/ – Dion Nov 27 '13 at 23:46
1

I use a simple helper method. You can easily use it in the Views and Controllers.

Markup:

<a href=@Helper.Root()/about">About Us</a>

Helper method:

public static string Root()
{
    if (HttpContext.Current.Request.Url.Host == "localhost")
    {
        return "";
    }
    else
    {
        return "/productionroot";
    }
}
James Lawruk
  • 30,112
  • 19
  • 130
  • 137