Относительные пути ASP.NET MVC

100

В своих приложениях мне часто приходится использовать относительные пути. Например, когда я ссылаюсь на 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">

без какого-либо синтаксиса на стороне сервера, а механизм просмотра автоматически заменяет ~ / тем, что является текущим корнем сайта.

Крис
источник

Ответы:

93

Попробуй это:

<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, я думаю, им нужно включить пример в шаблон.
Саймон Стил,
Потрясающе, это действительно вывело меня из затруднительного положения. Спасибо!
Джаред
2
(Я знаю, что этот пост старый) - Не использует <% = Url.Content ("~ / Scripts / jquery-1.2.6.js")%>, чтобы сервер отображал путь, тогда как, если вы использовали "/ Scripts / jquery-1.2.6.js ", он будет просто передаваться прямо клиенту, следовательно, сокращая еще одну вещь, которую должен делать сервер? Я думал, что где-то читал, чем больше вы можете избежать серверного процесса, тем лучше - особенно со статическим контентом, таким как пути * .js? Я понимаю, что это требует минимальных ресурсов, но если бы у вас было несколько сотен / тысяч Url.Content () в вашем приложении, это было бы сэкономлено на пару наносекунд, не так ли?
Losbear 01
53

Хотя это старый пост, новые читатели должны знать, что Razor 2 и более поздние версии (по умолчанию в MVC4 +) полностью решают эту проблему.

Старый MVC3 с Razor 1:

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

Новый MVC4 с Razor 2 и новее:

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

Никакого неудобного синтаксиса, похожего на функции Razor. Никаких нестандартных тегов разметки.

Добавление к пути в любых HTML-атрибутах тильды ('~') указывает Razor 2 «просто заставить его работать», подставив правильный путь. Здорово.

Чарльз Бернс
источник
Да, и учитывая простоту синтаксического анализа префикса ~ /, мне интересно, почему нечто подобное не было встроено в ASP.NET с самого начала.
Крис
4
Я часто обнаруживал, что чем проще дизайн, тем больше в него вкладывается мысли.
Чарльз Бернс
1
Этот ответ немного вводит в заблуждение. Синтаксис, опубликованный для MVC4, фактически зависит от движка бритвы. Он может не использовать какую-либо специальную разметку, но только механизм Razor v2 + правильно обрабатывает синтаксис.
Крис
1
Ты прав, @Chris. Я обновил ответ, чтобы отразить это.
Чарльз Бернс
10

Критическое изменение - MVC 5

Следите за критическими изменениями в MVC 5 (из примечаний к выпуску MVC 5 )

Перезапись URL и тильда (~)

После обновления до ASP.NET Razor 3 или ASP.NET MVC 5 нотация тильды (~) может больше не работать правильно, если вы используете перезапись URL-адресов. Перезаписи URL - адрес влияет на тильду (~) в обозначениях элементов , таких как HTML <A/>, <SCRIPT/>,<LINK/> , и в результате тильда больше не отображается в корневой каталог.

Например, если вы переписываете запросы для asp.net/content на asp.net , атрибут href в <A href="~/content/"/>преобразуется в / content / content / вместо / . Чтобы подавить это изменение, вы можете установить для контекста IIS_WasUrlRewritten значение false на каждой веб-странице или в Application_BeginRequest в Global.asax.

На самом деле они не объясняют, как это сделать, но потом я нашел такой ответ :

Если вы работаете в режиме интегрированного конвейера IIS 7, попробуйте ввести следующее Global.asax:

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

Примечание: вы можете сначала проверить, Request.ServerVariablesдействительно ли содержит, IIS_WasUrlRewrittenчтобы убедиться, что это ваша проблема.


PS. Я думал, что у меня была ситуация, когда это происходило со мной, и я получал src="~/content/..."URL-адреса, сгенерированные в моем HTML, но оказалось, что что-то просто не обновлялось, когда мой код компилировался. Редактирование и повторное сохранение файлов макета и страницы cshtml каким-то образом заставило что-то работать.

Simon_Weaver
источник
6

В ASP.NET я обычно использую <img src='<%= VirtualPathUtility.ToAbsolute("~/images/logo.gif") %>' alt="Our Company Logo"/>. Я не понимаю, почему подобное решение не должно работать в ASP.NET MVC.

kͩeͣmͮpͥ ͩ
источник
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

Механизм представления Razor для MVC 3 упрощает и упрощает использование относительных путей виртуального корня, которые правильно разрешаются во время выполнения. Просто поместите метод Url.Content () в значение атрибута href, и он разрешится правильно.

<a href="@Url.Content("~/Home")">Application home page</a>
JPC
источник
1

Как и Крис, я действительно терпеть не могу помещать раздутые серверные теги в мою чистую разметку просто для того, чтобы указать этой глупой штуке, чтобы она смотрела от корня вверх. Это должно быть очень простой и разумный вопрос. Но я также ненавижу мысль о необходимости писать какие-либо собственные классы C #, чтобы делать такую ​​простую вещь, зачем мне это нужно? Что за трата времени.

Что касается меня, я просто пошел на компромисс с «совершенством» и жестко запрограммировал имя корневого пути виртуального каталога внутри своих ссылок на пути. Ну вот так:

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

Для разрешения URL-адреса не требуется никакой обработки на стороне сервера или кода C #, что лучше всего для производительности, хотя я знаю, что в любом случае это будет незначительно. И никакого раздутого уродливого серверного хаоса в моей красивой чистой разметке.

Мне просто придется смириться с тем, что это жестко запрограммировано и его нужно будет удалить, когда вещь перейдет в правильный домен вместо http: // MyDevServer / MyProject /

Ура

Аарон
источник
1
Я проголосовал за то, чтобы вернуть вас к нулю. Полностью согласен с вашими мнениями. Я новичок в веб-разработке после 5 лет работы на чистом C # и того, что это за ужасная страна спагетти-хаоса.
Люк Пуплетт
Это кажется приемлемым компромиссом до тех пор, пока вам не понадобится выполнить что-то вроде развертывания во вложенном веб-приложении. Использование разметки резолвера исправит это, но ваша статическая ссылка будет неработающей. Пример: вы выполняете локальную сборку на встроенном веб-сервере, а затем
отправляете
Это нарушит многие производственные сценарии
Оскар Дювеборн
Мне очень нравится это решение: thinkstuff.co.uk/2013/02/…
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";
    }
}
Джеймс Лорук
источник