Відносні шляхи 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 (), це пару наносекунд поголене, ні?
Лосбер

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 та Tilde (~)

Після оновлення до ASP.NET Razor 3 або ASP.NET MVC 5 позначення tilde (~) може більше не працювати правильно, якщо ви використовуєте переписування 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 якимось чином спричинило щось для роботи.


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

Система перегляду Razor для MVC 3 ще більше спрощує та зрозуміліше використання віртуальних кореневих контурів, які належним чином вирішені під час виконання. Просто введіть метод 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>

Для вирішення URL-адреси не потрібна обробка на стороні сервера або код C #, що найкраще для продуктивності, хоча я знаю, що це було б незначно. І ніякого роздутого потворного хаосу на стороні сервера в моїй приємній чистій розмітці.

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

Ура


1
Я проголосував за те, щоб повернути вас до 0. Повністю згоден з вашими настроями. Я новачок у веб-розробниках після 5 років у чистому C #, і яка така руйнівна хаос спагетті це все.
Люк Пуплетт

Це здається прийнятним компромісом, поки вам не потрібно зробити щось на зразок розгортання в вкладений веб-додаток. Використання розмітки на роздільній здатності виправить це, але статичне посилання буде розірвано. Приклад: ви будуєте локально на вбудованому веб-сервері, а потім натискаєте додаток на domain.com/myNewWebApp
plyawn

Це зірветься у багатьох виробничих сценаріях
Оскар Дувеборн

Мені дуже подобається таке рішення: thinkstuff.co.uk/2013/02/…
Діон


1

Я використовую простий хелперний метод. Ви можете легко використовувати його в режимах перегляду та контролерів.

Розмітка:

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

Хелперний метод:

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.