Rutas relativas de ASP.NET MVC

100

En mis aplicaciones, a menudo tengo que usar rutas relativas. Por ejemplo, cuando hago referencia a JQuery, generalmente lo hago así:

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

Ahora que estoy haciendo la transición a MVC, necesito tener en cuenta las diferentes rutas que puede tener una página, en relación con la raíz. Por supuesto, esto fue un problema con la reescritura de URL en el pasado, pero me las arreglé para solucionarlo utilizando rutas consistentes.

Soy consciente de que la solución estándar es usar rutas absolutas como:

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

pero esto no funcionará para mí, ya que durante el ciclo de desarrollo, tengo que implementarlo en una máquina de prueba en la que la aplicación se ejecutará en un directorio virtual. Las rutas relativas a la raíz no funcionan cuando cambia la raíz. Además, por razones de mantenimiento, no puedo simplemente cambiar todas las rutas durante la implementación de la prueba, eso sería una pesadilla en sí mismo.

Entonces, ¿cuál es la mejor solución?

Editar:

Dado que esta pregunta aún recibe vistas y respuestas, pensé que sería prudente actualizarla para tener en cuenta que a partir de Razor V2, el soporte para URL relativas a la raíz está integrado, por lo que puede usar

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

sin ninguna sintaxis del lado del servidor, y el motor de visualización reemplaza automáticamente ~ / con la raíz del sitio actual.

Chris
fuente

Respuestas:

93

Prueba esto:

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

O use MvcContrib y haga esto:

<%=Html.ScriptInclude("~/Content/Script/jquery.1.2.6.js")%>
Tim Scott
fuente
1
Esto se pregunta con tanta frecuencia que debería ser una pregunta frecuente, creo que deben incluir un ejemplo en la plantilla.
Simon Steele
Impresionante, esto realmente me sacó de un aprieto. ¡Gracias!
Jared
2
(Sé que esta publicación es antigua) - No usa <% = Url.Content ("~ / Scripts / jquery-1.2.6.js")%> hace que el servidor represente la ruta, mientras que, si usó "/ Scripts / jquery-1.2.6.js ", se entregaría directamente al cliente, por lo tanto, ¿se reduce una cosa más que el servidor tiene que hacer? Pensé que había leído en alguna parte, cuanto más puedas evitar tener el proceso del servidor, mejor, especialmente con contenido estático como las rutas * .js. Me doy cuenta de que esto usa recursos mínimos, pero si tuviera un par de cientos / miles de Url.Content () en su aplicación, eso es un par de nanosegundos reducido, ¿no?
Losbear
52

Si bien es una publicación antigua, los nuevos lectores deben saber que Razor 2 y posterior (predeterminado en MVC4 +) resuelve completamente este problema.

Antiguo MVC3 con Razor 1:

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

Nuevo MVC4 con Razor 2 y posterior:

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

Sin una sintaxis incómoda similar a una función de Razor. Sin etiquetas de marcado no estándar.

Prefijar una ruta en cualquier atributo HTML con una tilde ('~') le dice a Razor 2 que "simplemente haga que funcione" sustituyendo la ruta correcta. Es genial.

Charles Burns
fuente
Sí, y dada la simplicidad de analizar el prefijo ~ /, me pregunto por qué algo como esto no estaba integrado en ASP.NET desde el principio.
Chris
4
A menudo descubrí que cuanto más simple es el diseño, más se ha pensado en él.
Charles Burns
1
Esta respuesta es un poco engañosa. La sintaxis publicada para MVC4 depende de hecho del motor de afeitar. Puede que no utilice ningún marcado especial, pero solo el motor Razor v2 + maneja la sintaxis que se muestra correctamente.
Chris
1
Tienes razón, @Chris. Actualicé la respuesta para reflejar esto.
Charles Burns
10

Rompiendo el cambio - MVC 5

Tenga cuidado con un cambio importante en MVC 5 (de las notas de la versión de MVC 5 )

Reescritura de URL y tilde (~)

Después de actualizar a ASP.NET Razor 3 o ASP.NET MVC 5, es posible que la notación de tilde (~) ya no funcione correctamente si está utilizando reescrituras de URL. La reescritura de URL afecta a la notación de tilde (~) en los elementos HTML tales como <A/>, <SCRIPT/>, <LINK/>, y como resultado de la tilde ya no se asigna al directorio raíz.

Por ejemplo, si reescribe solicitudes para asp.net/content en asp.net , el atributo href en se <A href="~/content/"/>resuelve en / content / content / en lugar de / . Para suprimir este cambio, puede establecer el contexto IIS_WasUrlRewritten en falso en cada página web o en Application_BeginRequest en Global.asax.

En realidad, no explican cómo hacerlo, pero luego encontré esta respuesta :

Si está ejecutando en el modo de canalización integrado de IIS 7, intente poner lo siguiente en su Global.asax:

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

Nota: Es posible que desee verificar primero que Request.ServerVariablescontiene realmente IIS_WasUrlRewrittenpara asegurarse de que este es su problema.


PD. Pensé que tenía una situación en la que esto me estaba sucediendo y estaba src="~/content/..."generando URLS en mi HTML, pero resultó que algo simplemente no se actualizaba cuando se estaba compilando mi código. Editar y volver a guardar los archivos cshtml de diseño y página de alguna manera hizo que algo funcionara.

Simon_Weaver
fuente
6

En ASP.NET suelo utilizar <img src='<%= VirtualPathUtility.ToAbsolute("~/images/logo.gif") %>' alt="Our Company Logo"/>. No veo por qué una solución similar no debería funcionar en ASP.NET MVC.

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

Es lo que usé. Cambie la ruta para que coincida con su ejemplo.

Jesper Palm
fuente
5

Por lo que vale, realmente odio la idea de ensuciar mi aplicación con etiquetas de servidor solo para resolver rutas, así que investigué un poco más y opté por usar algo que había probado antes para reescribir enlaces: un filtro de respuesta. De esta manera, puedo prefijar todas las rutas absolutas con un prefijo conocido y reemplazarlo en tiempo de ejecución usando el objeto Response.Filter y no tener que preocuparme por etiquetas de servidor innecesarias. El código se publica a continuación en caso de que ayude a alguien más.

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
fuente
4

El motor de vista de Razor para MVC 3 hace que sea aún más fácil y limpio el uso de rutas relativas de raíz virtual que se resuelven correctamente en tiempo de ejecución. Simplemente suelte el método Url.Content () en el valor del atributo href y se resolverá correctamente.

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

Al igual que Chris, realmente no puedo soportar tener que poner etiquetas hinchadas del lado del servidor dentro de mi marcado limpio solo para decirle a la estúpida cosa que mire desde la raíz hacia arriba. Eso debería ser algo muy simple y razonable de pedir. Pero también odio la idea de tener que hacer el esfuerzo de escribir cualquier clase de C # personalizada para hacer algo tan simple, ¿por qué debería tener que hacerlo? Que perdida de tiempo.

Para mí, simplemente me comprometí con la "perfección" y codifiqué el nombre de la ruta raíz del directorio virtual dentro de mis referencias de ruta. Así que así:

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

No se requiere procesamiento del lado del servidor o código C # para resolver la URL, que es lo mejor para el rendimiento, aunque sé que sería insignificante independientemente. Y no hay un caos feo e inflado del lado del servidor en mi bonito marcado limpio.

Solo tendré que vivir sabiendo que esto está codificado y deberá eliminarse cuando la cosa migre a un dominio adecuado en lugar de http: // MyDevServer / MyProject /

Salud

Aaron
fuente
1
He votado a favor para que vuelvas a 0. Estoy totalmente de acuerdo con tus opiniones. Soy nuevo en el desarrollo web después de 5 años en C # puro y qué desastre es la tierra del caos de espaguetis.
Luke Puplett
Esto parece un compromiso aceptable hasta que necesite hacer algo como implementar en una aplicación web anidada. El uso de la marca de resolución solucionará esto, pero su enlace estático se romperá. Ejemplo: compila localmente contra el servidor web incorporado y luego
envía
Esto se romperá en muchos escenarios de producción
Oskar Duveborn
Me gusta bastante esta solución: thoughtstuff.co.uk/2013/02/…
Dion
1

Tarde para el juego, pero esta publicación tiene un resumen muy completo del manejo de las rutas ASP.Net.

plyawn
fuente
1

Utilizo un método de ayuda simple. Puede usarlo fácilmente en Vistas y Controladores.

Margen:

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

Método de ayuda:

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