views:

3480

answers:

7

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?

+2  A: 

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.

David Kemp
+32  A: 

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
This gets asked so often it should be a FAQ, I think they need to include an example in the template.
Simon Steele
Awesome, this really got me out of a bind. Thanks!
Jared
+6  A: 
<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
+3  A: 

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
A: 

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
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
A: 

I went with a bit of a different approach based off a similar SO post, but with much less code...

http://dis.mantle.me/?p=187

Burke Holland
A: 

Hi, I am having a problem with this paths, first, when I am referencing a new script by using the 'src=..."(Pick URL) the editor pust "../../Scripts/myScript.js", this runs perfectly in my local computer but when it goes to the production server the scripts does not load. I changed the path in the production server to "../Script/myScript.js" and it worked well for scripts but not for css. I looked at NerdDinner but they have "/Scripts/theScript.js" but this does not work for me.

Maybe I am missing something in the IIS related to the routing or whatever, but I am wasting a lot of time by puting paths for debugging and then changing them for production.

Thanks

Julian Vargas