Ścieżki względne ASP.NET MVC

100

W moich aplikacjach często muszę używać ścieżek względnych. Na przykład, kiedy odwołuję się do JQuery, zwykle robię to w ten sposób:

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

Teraz, gdy przechodzę do MVC, muszę wziąć pod uwagę różne ścieżki, które może mieć strona, względem katalogu głównego. Był to oczywiście problem z przepisywaniem adresów URL w przeszłości, ale udało mi się to obejść, używając spójnych ścieżek.

Zdaję sobie sprawę, że standardowym rozwiązaniem jest użycie ścieżek bezwzględnych takich jak:

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

ale to nie zadziała, ponieważ podczas cyklu programistycznego muszę wdrożyć na maszynie testowej, na której aplikacja będzie działać w katalogu wirtualnym. Względne ścieżki główne nie działają, gdy zmienia się katalog główny. Ponadto ze względów konserwacyjnych nie mogę po prostu zmienić wszystkich ścieżek na czas wdrażania testu - to byłby koszmar sam w sobie.

Więc jakie jest najlepsze rozwiązanie?

Edytować:

Ponieważ na to pytanie wciąż pojawiają się widoki i odpowiedzi, pomyślałem, że rozsądnie byłoby zaktualizować je, aby zauważyć, że od Razor V2 obsługa adresów URL powiązanych z rootem jest wbudowana, więc możesz użyć

<img src="~/Content/MyImage.jpg">

bez żadnej składni po stronie serwera, a mechanizm widoku automatycznie zamienia ~ / na dowolny bieżący katalog główny witryny.

Chris
źródło

Odpowiedzi:

93

Spróbuj tego:

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

Lub użyj MvcContrib i zrób to:

<%=Html.ScriptInclude("~/Content/Script/jquery.1.2.6.js")%>
Tim Scott
źródło
1
Jest to zadawane tak często, że powinno to być FAQ, myślę, że muszą zawierać przykład w szablonie.
Simon Steele,
Wspaniale, to naprawdę wyrwało mnie z opresji. Dzięki!
Jared
2
(Wiem, że ten post jest stary) - Nie używa <% = Url.Content ("~ / Scripts / jquery-1.2.6.js")%> sprawia, że ​​serwer renderuje ścieżkę, podczas gdy, jeśli użyłeś "/ Scripts / jquery-1.2.6.js ”, zostałby on dostarczony prosto do klienta, co ograniczyłoby jeszcze jedną rzecz, którą musi wykonać serwer? Pomyślałem, że przeczytałem gdzieś, że im więcej możesz uniknąć procesu serwera, tym lepiej - szczególnie w przypadku zawartości statycznej, takiej jak ścieżki * .js? Zdaję sobie sprawę, że wymaga to minimalnych zasobów, ale jeśli masz w aplikacji kilkaset tysięcy Url.Content (), to jest to kilka nanosekund, prawda?
Losbear
53

Chociaż jest to stary post, nowi czytelnicy powinni wiedzieć, że Razor 2 i nowsze (domyślnie w MVC4 +) całkowicie rozwiązują ten problem.

Stary MVC3 z Razor 1:

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

Nowy MVC4 z Razor 2 i nowszymi:

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

Brak niezręcznej składni podobnej do funkcji Razor. Brak niestandardowych tagów.

Prefiks ścieżki w dowolnych atrybutach HTML tyldą („~”) mówi Razorowi 2, aby „po prostu działał”, zastępując prawidłową ścieżkę. Wspaniale.

Charles Burns
źródło
Tak, a biorąc pod uwagę prostotę analizowania prefiksu ~ /, zastanawiam się, dlaczego coś takiego nie było wbudowane w ASP.NET od samego początku.
Chris
4
Często stwierdzałem, że im prostszy projekt, tym więcej myśli się w nim.
Charles Burns
1
Ta odpowiedź jest nieco myląca. Składnia przesłana dla MVC4 jest w rzeczywistości zależna od silnika maszynki do golenia. Może nie używać żadnych specjalnych znaczników, ale tylko silnik Razor v2 + obsługuje poprawnie wyświetlaną składnię.
Chris
1
Masz rację, @Chris. Zaktualizowałem odpowiedź, aby to odzwierciedlić.
Charles Burns
10

Przełomowa zmiana - MVC 5

Uważaj na przełomową zmianę w MVC 5 (z informacji o wydaniu MVC 5 )

Przepisanie adresu URL i tylda (~)

Po uaktualnieniu do ASP.NET Razor 3 lub ASP.NET MVC 5 notacja tyldy (~) może nie działać poprawnie, jeśli używasz funkcji ponownego zapisywania adresów URL. URL rewrite wpływa na tyldy (~) w notacji elementów HTML, takich jak <A/>, <SCRIPT/>, <LINK/>, i wskutek tyldy nie odwzorowuje do katalogu głównego.

Na przykład, jeśli ponownie zapisujesz żądania dotyczące asp.net/content do asp.net , atrybut href w jest <A href="~/content/"/>rozpoznawany jako / content / content / zamiast / . Aby pominąć tę zmianę, można ustawić kontekst IIS_WasUrlRewritten na false na każdej stronie sieci Web lub w Application_BeginRequest w Global.asax.

Właściwie nie wyjaśniają, jak to zrobić, ale wtedy znalazłem tę odpowiedź :

Jeśli korzystasz z trybu zintegrowanego potoku usług IIS 7, spróbuj umieścić następujące elementy w Global.asax:

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

Uwaga: możesz najpierw sprawdzić, czy Request.ServerVariablesfaktycznie zawiera on, IIS_WasUrlRewrittenaby upewnić się, że na tym polega Twój problem.


PS. Wydawało mi się, że mam sytuację, w której to się dzieje i otrzymuję src="~/content/..."adresy URL generowane w moim HTML-u - ale okazało się, że coś nie odświeża się, gdy mój kod był kompilowany. Edytowanie i ponowne zapisywanie plików układu i strony cshtml w jakiś sposób spowodowało, że coś zadziałało.

Simon_Weaver
źródło
6

W ASP.NET zwykle używam <img src='<%= VirtualPathUtility.ToAbsolute("~/images/logo.gif") %>' alt="Our Company Logo"/>. Nie rozumiem, dlaczego podobne rozwiązanie nie powinno działać w ASP.NET MVC.

kͩeͣmͮpͥ ͩ
źródło
6
<script src="<%=ResolveUrl("~/Scripts/jquery-1.2.6.min.js") %>" type="text/javascript"></script>

Czy to, czego użyłem. Zmień ścieżkę, aby pasowała do Twojego przykładu.

Jesper Palm
źródło
5

Jeśli chodzi o to, co jest warte, naprawdę nienawidzę pomysłu zaśmiecania mojej aplikacji tagami serwera tylko po to, aby rozwiązać ścieżki, więc przeprowadziłem trochę więcej badań i zdecydowałem się użyć czegoś, czego próbowałem wcześniej, do przepisywania linków - filtra odpowiedzi. W ten sposób mogę poprzedzać wszystkie ścieżki bezwzględne znanym prefiksem i zastępować go w czasie wykonywania za pomocą obiektu Response.Filter i nie muszę martwić się o niepotrzebne tagi serwera. Kod jest zamieszczony poniżej na wypadek, gdyby pomógł komukolwiek innemu.

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
źródło
4

Silnik widoku Razor dla MVC 3 sprawia, że ​​korzystanie ze ścieżek względnych wirtualnego katalogu głównego, które są poprawnie rozpoznawane w czasie wykonywania, jest jeszcze łatwiejsze i czystsze. Po prostu upuść metodę Url.Content () do wartości atrybutu href, a zostanie ona poprawnie rozwiązana.

<a href="@Url.Content("~/Home")">Application home page</a>
JPC
źródło
1

Podobnie jak Chris, naprawdę nie mogę znieść konieczności umieszczania rozdętych tagów po stronie serwera w moim czystym znaczniku tylko po to, aby powiedzieć głupocie, aby patrzył od roota w górę. To powinno być bardzo proste i rozsądne pytanie. Ale nienawidzę też pomysłu konieczności pisania jakichkolwiek niestandardowych klas C # w celu zrobienia tak prostej rzeczy, dlaczego miałbym to robić? Co za strata czasu.

Dla mnie po prostu naraziłem się na „perfekcję” i zakodowałem na stałe nazwę ścieżki katalogu wirtualnego w moich odniesieniach do ścieżki. Więc tak:

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

Do rozwiązania adresu URL nie jest wymagane żadne przetwarzanie po stronie serwera ani kod C #, co jest najlepsze pod względem wydajności, chociaż wiem, że byłoby to pomijalne niezależnie od tego. I bez rozdętego, brzydkiego chaosu po stronie serwera w moim ładnym, czystym znaczniku.

Będę musiał po prostu żyć, wiedząc, że to jest zakodowane na stałe i będzie musiało zostać usunięte, gdy rzecz zostanie przeniesiona do właściwej domeny zamiast http: // MyDevServer / MyProject /

Twoje zdrowie

Aaron
źródło
1
Głosowałem „za”, aby sprowadzić cię z powrotem do 0. Całkowicie zgadzam się z twoimi odczuciami. Jestem nowy w tworzeniu stron internetowych po 5 latach w czystym języku C # i jak katastrofalna kraina chaosu spaghetti to wszystko jest.
Luke Puplett
Wydaje się, że jest to akceptowalny kompromis, dopóki nie musisz wykonać czegoś takiego jak wdrożenie w zagnieżdżonej aplikacji internetowej. Użycie znacznika resolvera naprawi ten problem, ale Twój statyczny link zostanie uszkodzony. Przykład: budujesz lokalnie na wbudowanym serwerze internetowym, a następnie
wypychasz
To przerwie wiele scenariuszy produkcyjnych
Oskar Duveborn
Bardzo podoba mi się to rozwiązanie: thinkstuff.co.uk/2013/02/…
Dion
1

Spóźniona gra, ale ten post zawiera bardzo kompletne podsumowanie obsługi ścieżek ASP.Net.

plyawn
źródło
1

Używam prostej metody pomocniczej. Możesz go łatwo używać w widokach i kontrolerach.

Narzut:

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

Metoda pomocnicza:

public static string Root()
{
    if (HttpContext.Current.Request.Url.Host == "localhost")
    {
        return "";
    }
    else
    {
        return "/productionroot";
    }
}
James Lawruk
źródło