Chemins relatifs ASP.NET MVC

100

Dans mes applications, je dois souvent utiliser des chemins relatifs. Par exemple, lorsque je référence JQuery, je le fais généralement comme ceci:

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

Maintenant que je fais la transition vers MVC, je dois tenir compte des différents chemins qu'une page peut avoir, par rapport à la racine. C'était bien sûr un problème de réécriture d'URL dans le passé, mais j'ai réussi à le contourner en utilisant des chemins cohérents.

Je suis conscient que la solution standard consiste à utiliser des chemins absolus tels que:

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

mais cela ne fonctionnera pas pour moi car pendant le cycle de développement, je dois déployer sur une machine de test sur laquelle l'application s'exécutera dans un répertoire virtuel. Les chemins relatifs racine ne fonctionnent pas lorsque la racine change. De plus, pour des raisons de maintenance, je ne peux pas simplement changer tous les chemins pendant la durée du déploiement du test - ce serait un cauchemar en soi.

Alors, quelle est la meilleure solution?

Éditer:

Étant donné que cette question reçoit toujours des vues et des réponses, j'ai pensé qu'il pourrait être prudent de la mettre à jour pour noter qu'à partir de Razor V2, la prise en charge des URL relatives à la racine est intégrée, vous pouvez donc utiliser

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

sans aucune syntaxe côté serveur, et le moteur de vue remplace automatiquement ~ / par quelle que soit la racine actuelle du site.

Chris
la source

Réponses:

93

Essaye ça:

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

Ou utilisez MvcContrib et procédez comme suit :

<%=Html.ScriptInclude("~/Content/Script/jquery.1.2.6.js")%>
Tim Scott
la source
1
Cela est demandé si souvent que cela devrait être une FAQ, je pense qu'ils doivent inclure un exemple dans le modèle.
Simon Steele
Génial, cela m'a vraiment sorti d'une impasse. Merci!
Jared
2
(Je sais que ce message est ancien) - N'utilise pas <% = Url.Content ("~ / Scripts / jquery-1.2.6.js")%> pour que le serveur affiche le chemin, alors que si vous avez utilisé "/ Scripts / jquery-1.2.6.js ", il serait simplement servi directement au client, réduisant ainsi une chose de plus que le serveur doit faire? Je pensais lire quelque part plus vous pouvez éviter d'avoir le processus serveur, mieux c'est - en particulier avec du contenu statique comme les chemins * .js? Je me rends compte que cela utilise des ressources minimes, mais si vous avez quelques centaines / milliers de Url.Content () dans votre application, c'est quelques nanosecondes rasées, non?
Losbear
53

Bien qu'il s'agisse d'un ancien article, les nouveaux lecteurs doivent savoir que Razor 2 et versions ultérieures (par défaut dans MVC4 +) résout complètement ce problème.

Ancien MVC3 avec Razor 1:

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

Nouveau MVC4 avec Razor 2 et versions ultérieures:

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

Pas de syntaxe de fonction Razor maladroite. Aucune balise de balisage non standard.

Le préfixe d'un chemin dans n'importe quel attribut HTML avec un tilde ('~') indique à Razor 2 de "le faire fonctionner" en remplaçant le chemin correct. C'est bien.

Charles Burns
la source
Oui, et étant donné la simplicité de l'analyse du préfixe ~ /, je me demande pourquoi quelque chose comme ça n'a pas été intégré à ASP.NET depuis le début.
Chris
4
J'ai souvent constaté que plus la conception était simple, plus on y réfléchissait.
Charles Burns
1
Cette réponse est légèrement trompeuse. La syntaxe affichée pour MVC4 dépend en fait du moteur de rasoir. Il ne peut pas utiliser de balisage spécial, mais seul le moteur Razor v2 + gère correctement la syntaxe affichée.
Chris
1
Vous avez raison, @Chris. J'ai mis à jour la réponse pour refléter cela.
Charles Burns
10

Changement de rupture - MVC 5

Méfiez-vous d'un changement de rupture dans MVC 5 (à partir des notes de publication de MVC 5 )

Réécriture d'URL et tilde (~)

Après la mise à niveau vers ASP.NET Razor 3 ou ASP.NET MVC 5, la notation tilde (~) peut ne plus fonctionner correctement si vous utilisez des réécritures d'URL. La réécriture d'URL affecte le tilde (~) la notation des éléments HTML tels que <A/>, <SCRIPT/>, <LINK/>et par conséquent le tilde plus des cartes dans le répertoire racine.

Par exemple, si vous réécrivez des requêtes pour asp.net/content vers asp.net , l'attribut href dans se <A href="~/content/"/>résout en / content / content / au lieu de / . Pour supprimer cette modification, vous pouvez définir le contexte IIS_WasUrlRewritten sur false dans chaque page Web ou dans Application_BeginRequest dans Global.asax.

Ils n'expliquent pas vraiment comment le faire, mais j'ai trouvé cette réponse :

Si vous exécutez en mode Pipeline intégré IIS 7, essayez de mettre ce qui suit dans votre Global.asax:

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

Remarque: vous voudrez peut-être d'abord vérifier Request.ServerVariablescontient réellement IIS_WasUrlRewrittenpour être sûr que c'est bien votre problème.


PS. Je pensais que j'avais une situation où cela m'arrivait et que j'obtenais des src="~/content/..."URLs générées dans mon HTML - mais il s'est avéré que quelque chose n'était tout simplement pas rafraîchissant lorsque mon code était en cours de compilation. La modification et la réenregistrement des fichiers de mise en page et de page cshtml ont en quelque sorte déclenché quelque chose qui fonctionnait.

Simon_Weaver
la source
6

Dans ASP.NET, j'utilise habituellement <img src='<%= VirtualPathUtility.ToAbsolute("~/images/logo.gif") %>' alt="Our Company Logo"/>. Je ne vois pas pourquoi une solution similaire ne devrait pas fonctionner dans ASP.NET MVC.

kͩeͣmͮpͥ ͩ
la source
6
<script src="<%=ResolveUrl("~/Scripts/jquery-1.2.6.min.js") %>" type="text/javascript"></script>

C'est ce que j'ai utilisé. Changez le chemin pour correspondre à votre exemple.

Jesper Palm
la source
5

Pour ce que ça vaut, je déteste vraiment l'idée de joncher mon application avec des balises de serveur juste pour résoudre les chemins, alors j'ai fait un peu plus de recherches et j'ai choisi d'utiliser quelque chose que j'avais déjà essayé pour réécrire les liens - un filtre de réponse. De cette façon, je peux préfixer tous les chemins absolus avec un préfixe connu et le remplacer au moment de l'exécution à l'aide de l'objet Response.Filter sans avoir à me soucier des balises de serveur inutiles. Le code est affiché ci-dessous au cas où cela aiderait quelqu'un d'autre.

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
la source
4

Le moteur de vue Razor pour MVC 3 rend encore plus facile et plus propre l'utilisation de chemins relatifs de racine virtuelle qui sont correctement résolus au moment de l'exécution. Déposez simplement la méthode Url.Content () dans la valeur d'attribut href et elle se résoudra correctement.

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

Comme Chris, je ne supporte vraiment pas d'avoir à mettre des balises côté serveur gonflées dans mon balisage propre uniquement pour dire à la chose stupide de regarder de la racine vers le haut. Cela devrait être une chose très simple et raisonnable à demander. Mais je déteste aussi l'idée d'avoir à faire l'effort d'écrire des classes C # personnalisées pour faire une chose aussi simple, pourquoi devrais-je le faire? Quelle perte de temps.

Pour moi, j'ai simplement compromis sur la "perfection" et codé en dur le nom du chemin racine du répertoire virtuel dans mes références de chemin. Alors comme ça:

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

Aucun traitement côté serveur ou code C # requis pour résoudre l'URL, ce qui est le meilleur pour les performances, même si je sais que ce serait négligeable malgré tout. Et pas de chaos gonflé et laid côté serveur dans mon joli balisage propre.

Je vais juste devoir vivre en sachant que cela est codé en dur et devra être supprimé lorsque l'objet migre vers un domaine approprié au lieu de http: // MyDevServer / MyProject /

À votre santé

Aaron
la source
1
J'ai voté pour vous ramener à 0. Tout à fait d'accord avec vos sentiments. Je suis nouveau dans le développement Web après 5 ans en pur C # et quel désastre est le pays du chaos spaghetti.
Luke Puplett
Cela semble être un compromis acceptable jusqu'à ce que vous deviez effectuer quelque chose comme le déploiement sur une application Web imbriquée. L'utilisation du balisage du résolveur résoudra ce problème, mais votre lien statique sera rompu. Exemple: vous créez localement sur le serveur Web intégré, puis poussez l'application vers domain.com/myNewWebApp
plyawn
Cela va casser dans de nombreux scénarios de production
Oskar Duveborn
J'aime bien cette solution: thinkstuff.co.uk/2013/02/…
Dion
1

Tard dans le jeu, mais cet article contient un résumé très complet de la gestion des chemins ASP.Net.

pliawn
la source
1

J'utilise une méthode d'aide simple. Vous pouvez facilement l'utiliser dans les vues et les contrôleurs.

Balisage:

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

Méthode d'assistance:

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