ASP.NET MVC Relative Paths
在我的应用程序中,我经常需要使用相对路径。例如,当我引用jquery时,我通常这样做:
1 | <script type="text/javascript" src="../Scripts/jquery-1.2.6.js"> |
现在我正在向MVC过渡,我需要考虑页面相对于根目录可能具有的不同路径。在过去,这当然是URL重写的一个问题,但我设法通过使用一致的路径来解决它。
我知道标准解决方案是使用绝对路径,例如:
1 | <script type="text/javascript" src="/Scripts/jquery-1.2.6.js"> |
但这对我来说并不像在开发周期中那样有效,我必须部署到一个测试机器上,在这个机器上,应用程序将在一个虚拟目录中运行。根相对路径在根更改时不起作用。另外,出于维护的原因,在部署测试期间,我不能简单地更改所有路径——这本身就是一个噩梦。
那么,最好的解决方案是什么?
编辑:
由于这个问题仍然在接收视图和答案,我认为应该谨慎地更新它,以注意到在RazorV2中,对根相对URL的支持已经成熟,因此您可以使用
1 | <img src="~/Content/MyImage.jpg"> |
如果没有任何服务器端语法,视图引擎将自动用当前站点根目录替换~/。
试试这个:
1 | <script type="text/javascript" src="<%=Url.Content("~/Scripts/jquery-1.2.6.js")%>"> |
或者使用mvcontrib执行以下操作:
1 | <%=Html.ScriptInclude("~/Content/Script/jquery.1.2.6.js")%> |
虽然是一篇旧文章,但新读者应该知道Razor2和更高版本(MVC4+中的默认版本)完全解决了这个问题。
带剃刀1的旧MVC3:
1 | Application home page |
带有Razor 2和更高版本的新MVC4:
1 | Application home page |
没有像语法那样笨拙的剃刀功能。没有非标准标记。
在任何HTML属性中用波浪线("~")前缀一个路径,都会告诉Razor2通过替换正确的路径"使其工作"。太棒了。
断开变化-MVC 5
注意MVC 5的突破性变化(摘自MVC 5发行说明)
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
, , , 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
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.
他们并没有解释怎么做,但后来我找到了答案:
If you are running in IIS 7 Integrated Pipeline mode try putting the
following in yourGlobal.asax :
1 2 3 4 | protected void Application_BeginRequest(object sender, EventArgs e) { Request.ServerVariables.Remove("IIS_WasUrlRewritten"); } |
注意:您可能需要首先检查
我以为我遇到了这样的情况,我在HTML中生成了
1 | <script src="<%=ResolveUrl("~/Scripts/jquery-1.2.6.min.js") %>" type="text/javascript"> |
是我用过的。更改路径以匹配示例。
在ASP.NET中,我通常使用
值得一提的是,我真的不喜欢为了解析路径而乱扔服务器标签的想法,所以我做了更多的研究,并选择使用我以前尝试过的东西来重写链接——一个响应过滤器。这样,我就可以在所有绝对路径前加上已知前缀,并在运行时使用response.filter对象替换它,而不必担心不必要的服务器标记。代码发布在下面,以防对其他人有所帮助。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 | 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); } } } |
MVC 3的Razor视图引擎使使用在运行时正确解析的虚拟根相对路径变得更加容易和清晰。只需将url.content()方法放到href属性值中,它就会正确解析。
1 | Application home page |
我使用一个简单的助手方法。您可以在视图和控制器中轻松地使用它。
Markup:
1 | About Us |
辅助方法:
1 2 3 4 5 6 7 8 9 10 11 | public static string Root() { if (HttpContext.Current.Request.Url.Host =="localhost") { return""; } else { return"/productionroot"; } } |
这篇文章对如何处理ASP.NET路径有一个非常完整的总结。
我使用了一种不同的方法,基于一篇类似的文章,但是代码要少得多…
http://a.shinnew.me/post/6042784654/relative-paths-in-asp-net-mvc-javascript
像Chris一样,我真的不能忍受将膨胀的服务器端标签放在我的干净标记中,仅仅是为了告诉这个愚蠢的东西从根向上看。这应该是一个非常简单,合理的要求。但我也不喜欢这样一个想法:为了做这样一件简单的事情,我必须去写任何定制的C类,为什么我必须这样做?真是浪费时间。
对于我来说,我只是妥协于"完美",并将虚拟目录的根路径名硬编码到我的路径引用中。像这样:
1 | <script type="text/javascript" src="/MyProject/Scripts/jquery-1.2.6.js"> |
解析URL不需要服务器端处理或C代码,这对性能最好,尽管我知道不管怎样它都可以忽略不计。而且在我干净的标记中没有膨胀的、丑陋的服务器端混乱。
我只需要知道这是硬编码的,当它迁移到适当的域而不是http://mydevserver/myproject时需要删除它。/
干杯