ASP.NET MVC 相对路径

102

在我的应用程序中,我经常需要使用相对路径。例如,当我引用 JQuery 时,通常会这样做:

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

现在我正在转向MVC,我需要考虑页面相对于根路径可能有不同的路径。当然,在过去使用URL重写时,这是一个问题,但我通过使用一致的路径来解决了它。

我知道标准解决方案是使用绝对路径,例如:

<script type="text/javascript" src="/Scripts/jquery-1.2.6.js"></script>
但是这对我来说行不通,因为在开发周期中,我必须部署到一个测试机器上,在该机器上应用程序将在虚拟目录中运行。 当根目录更改时,根相对路径无法使用。 此外,出于维护原因,我不能仅为测试部署期间更改所有路径 - 这本身就是一场噩梦。 那么最好的解决方案是什么?
编辑:
自Razor V2以来,支持根相对URL已经内置,因此您可以使用。
<img src="~/Content/MyImage.jpg">

没有任何服务器端语法,视图引擎会自动将~/替换为当前站点根目录。


虽然有些晚了,但是这篇文章提供了一个非常完整的处理ASP.Net路径的总结。 - plyawn
9个回答

95

试试这个:

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

或者使用MvcContrib,然后执行以下操作:

<%=Html.ScriptInclude("~/Content/Script/jquery.1.2.6.js")%>

1
这个问题被问得如此频繁,以至于它应该成为常见问题解答(FAQ)的一部分。我认为他们需要在模板中包含一个示例。 - Simon Steele
太棒了,这确实帮我摆脱了困境。谢谢! - Jared
2
(我知道这篇文章有点旧了)- 使用<%=Url.Content(“〜/ Scripts / jquery-1.2.6.js”)%>是否会使服务器呈现路径,而如果您使用“/ Scripts / jquery-1.2.6.js”,它将直接提供给客户端,因此减少了服务器必须处理的内容? 我记得在某个地方读到过,尽可能避免服务器处理的内容越多越好-特别是对于像* .js路径这样的静态内容? 我知道这使用的资源很少,但是如果您的应用程序中有几百/千个Url.Content(),那么就可以节省几个纳秒,对吧? - Losbear

55

虽然这是一个旧帖子,但新读者应该知道 Razor 2 及更高版本(MVC4+ 的默认版本)完全解决了这个问题。

使用旧版的 MVC3 和 Razor 1:

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

使用Razor 2或更高版本的新MVC4:

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

没有笨拙的 Razor 函数式语法。 没有非标准的标记。

在任何 HTML 属性中加上波浪符号('~')前缀,告诉 Razor 2 "只需让它正常工作",然后替换正确的路径。这很棒。


是的,考虑到解析“~/”前缀的简单性,我想知道为什么像这样的东西不是从一开始就内置在ASP.NET中。 - Chris
4
我经常发现,设计越简单,就意味着在其中投入了更多的思考。 - Charles Burns
1
这个答案有点误导性。MVC4的语法实际上依赖于Razor引擎。它可能不使用任何特殊标记,但只有Razor v2+引擎才能正确处理所显示的语法。 - Chris
1
你说得对,@Chris。我已经更新了答案以反映这一点。 - Charles Burns

10

重大变更 - MVC 5

请注意,MVC 5 中存在一个重大变更(来自 MVC 5 发布说明

网址重写和波浪号(~)

升级到 ASP.NET Razor 3 或 ASP.NET MVC 5 后,如果您使用 URL 重写,波浪号(~)符号可能不再正确工作。URL 重写会影响 HTML 元素中的波浪号(~)符号,例如 <A/><SCRIPT/><LINK/> 等。因此,波浪号不再映射到根目录。

例如,如果您将对 asp.net/content 的请求重写为 asp.net,则 <A href="~/content/"/> 中的 href 属性会解析为/content/content/而不是/。 要消除此更改,您可以在每个 Web 页面中,或者在 Global.asax 的 Application_BeginRequest 中, 将 IIS_WasUrlRewritten 上下文设置为 false。

他们实际上没有解释如何执行此操作,但我找到了这个答案

如果您在 IIS 7 集成管道模式下运行,请尝试将以下内容放入您的 Global.asax 中:

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

注意:您可能需要首先检查Request.ServerVariables实际上是否包含IIS_WasUrlRewritten以确保这是您的问题所在。


附注:我曾经遇到过一个情况,HTML中生成了src="~/content/..."的URLS,但最终发现只是我的代码编译时未刷新。编辑并重新保存布局和页面cshtml文件,可以触发某些操作使其正常工作。


6
在ASP.NET中,我通常使用<img src='<%= VirtualPathUtility.ToAbsolute("~/images/logo.gif") %>' alt="Our Company Logo"/>。我认为在ASP.NET MVC中也可以采用类似的解决方案。

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

我使用的是这个。将路径更改为与您的示例匹配。


5

就我而言,我非常讨厌在我的应用程序中添加服务器标记以解析路径的想法,因此我进行了更多的研究,并选择使用我之前尝试过的用于重写链接的东西 - 响应过滤器。通过这种方式,我可以为所有绝对路径添加已知前缀并在运行时使用Response.Filter对象替换它,而不必担心不必要的服务器标记。如果有帮助,下面是代码。

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

4

MVC 3的Razor视图引擎使得在运行时使用虚拟根相对路径更加容易和清晰。只需将Url.Content()方法放入href属性值中,它就会正确解析。

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

1

就像克里斯一样,我真的受不了在我的干净标记中放置臃肿的服务器端标记,仅仅是为了告诉这个愚蠢的东西从根目录向上查找。这应该是一个非常简单、合理的要求。但我也讨厌为了做这么简单的事情而去编写任何自定义的C#类,我为什么要这样做呢?太浪费时间了。

对我来说,我只是妥协了"完美",在我的路径引用中硬编码了虚拟目录的根路径名称。就像这样:

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

不需要服务器端处理或C#代码来解析URL,这对性能最好,尽管我知道无论如何都是微不足道的。在我的干净整洁的标记中没有臃肿丑陋的服务器端混乱。

我只需要知道这是硬编码的,并且当该项目迁移到适当的域而不是http://MyDevServer/MyProject/时,需要将其删除。

干杯


1
我投了赞成票,让你回到0。完全同意你的观点。我在纯C#领域工作了5年后转向Web开发,发现这是一个混乱的意大利面团世界。 - Luke Puplett
这似乎是一个可以接受的妥协,直到你需要执行像部署到嵌套的 Web 应用程序之类的操作。使用解析器标记将修复此问题,但您的静态链接将会中断。例如:您在本地构建内置 Web 服务器,然后将应用程序推送到 domain.com/myNewWebApp。 - plyawn
这将在许多生产场景中出现问题。 - Oskar Duveborn
我非常喜欢这个解决方案: http://thoughtstuff.co.uk/2013/02/fixing-relative-css-paths-when-using-net-mvc-4-bundling/ - Dion

1
我使用一个简单的帮助方法。您可以轻松地在视图和控制器中使用它。
标记:
<a href=@Helper.Root()/about">About Us</a>

辅助方法:

public static string Root()
{
    if (HttpContext.Current.Request.Url.Host == "localhost")
    {
        return "";
    }
    else
    {
        return "/productionroot";
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接