Caminhos relativos da ASP.NET MVC

100

Em minhas aplicações, geralmente preciso usar caminhos relativos. Por exemplo, quando faço referência a JQuery, geralmente faço assim:

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

Agora que estou fazendo a transição para MVC, preciso levar em consideração os diferentes caminhos que uma página pode ter em relação à raiz. É claro que isso era um problema com a regravação de URL no passado, mas consegui contornar isso usando caminhos consistentes.

Estou ciente de que a solução padrão é usar caminhos absolutos, como:

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

mas isso não funcionará para mim, pois durante o ciclo de desenvolvimento, tenho que implantar em uma máquina de teste na qual o aplicativo será executado em um diretório virtual. Os caminhos relativos à raiz não funcionam quando a raiz muda. Além disso, por motivos de manutenção, não posso simplesmente alterar todos os caminhos durante a implantação do teste - isso já seria um pesadelo.

Qual é a melhor solução?

Editar:

Uma vez que esta questão ainda está recebendo visualizações e respostas, achei que seria prudente atualizá-la para observar que, a partir do Razor V2, o suporte para urls relativos à raiz está integrado, para que você possa usar

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

sem qualquer sintaxe do lado do servidor, e o mecanismo de visualização substitui automaticamente ~ / por qualquer que seja a raiz do site atual.

Chris
fonte

Respostas:

93

Experimente isto:

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

Ou use MvcContrib e faça o seguinte:

<%=Html.ScriptInclude("~/Content/Script/jquery.1.2.6.js")%>
Tim Scott
fonte
1
Isso é perguntado com tanta frequência que deveria ser um FAQ, acho que eles precisam incluir um exemplo no modelo.
Simon Steele
Incrível, isso realmente me livrou de uma armadilha. Obrigado!
Jared
2
(Eu sei que este post é antigo) - Não usar <% = Url.Content ("~ / Scripts / jquery-1.2.6.js")%> faz com que o servidor renderize o caminho, enquanto, se você usou "/ Scripts / jquery-1.2.6.js ", seria apenas servido direto para o cliente, portanto, reduzindo mais uma coisa que o servidor tem que fazer? Eu pensei ter lido em algum lugar, quanto mais você evitar ter o processo do servidor, melhor - especialmente com conteúdo estático como caminhos * .js? Sei que isso usa recursos mínimos, mas se você tivesse algumas centenas / mil Url.Content () em seu aplicativo, seriam alguns nanossegundos perdidos, não?
Losbear,
53

Embora seja uma postagem antiga, os novos leitores devem saber que o Razor 2 e posterior (padrão no MVC4 +) resolve completamente este problema.

MVC3 antigo com Razor 1:

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

Novo MVC4 com Razor 2 e posterior:

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

Nenhuma sintaxe estranha do tipo função do Razor. Sem tags de marcação não padrão.

Prefixar um caminho em qualquer atributo HTML com um til ('~') diz ao Razor 2 para "apenas fazer funcionar" substituindo o caminho correto. É ótimo.

Charles Burns
fonte
Sim, e dada a simplicidade de análise do prefixo ~ /, me pergunto por que algo assim não foi integrado ao ASP.NET desde o início.
Chris
4
Muitas vezes descobri que quanto mais simples é o design, mais atenção é dedicada a ele.
Charles Burns
1
Essa resposta é um pouco enganosa. A sintaxe postada para MVC4 na verdade depende do mecanismo de barbear. Ele não pode usar nenhuma marcação especial, mas apenas o motor do Razor v2 + lida com a sintaxe mostrada corretamente.
Chris
1
Você está certo, @Chris. Eu atualizei a resposta para refletir isso.
Charles Burns
10

Mudança decisiva - MVC 5

Fique atento a uma mudança significativa no MVC 5 (das notas de lançamento do MVC 5 )

Url Rewrite e Til (~)

Depois de atualizar para ASP.NET Razor 3 ou ASP.NET MVC 5, a notação til (~) pode não funcionar mais corretamente se você estiver usando regravações de URL. A reescrita URL afeta a notação til (~) em elementos HTML, como <A/>, <SCRIPT/>, <LINK/>, e como resultado o til não mapeia para o diretório raiz.

Por exemplo, se você reescrever solicitações de asp.net/content para asp.net , o atributo href é <A href="~/content/"/>resolvido para / content / content / em vez de / . Para suprimir essa alteração, você pode definir o contexto IIS_WasUrlRewritten como false em cada página da Web ou em Application_BeginRequest em Global.asax.

Eles não explicam realmente como fazer isso, mas então eu encontrei esta resposta :

Se você estiver executando no modo Pipeline integrado do IIS 7, tente colocar o seguinte em Global.asax:

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

Nota: Você pode querer verificar primeiro Request.ServerVariablesrealmente contém IIS_WasUrlRewrittenpara ter certeza de que este é o seu problema.


PS. Achei que estava acontecendo uma situação em que isso estava acontecendo comigo e estava obtendo src="~/content/..."URLs gerados em meu HTML - mas aconteceu que algo simplesmente não estava atualizando quando meu código estava sendo compilado. Editar e salvar novamente o Layout e os arquivos cshtml da página de alguma forma fez com que algo funcionasse.

Simon_Weaver
fonte
6

Geralmente uso ASP.NET <img src='<%= VirtualPathUtility.ToAbsolute("~/images/logo.gif") %>' alt="Our Company Logo"/>. Não vejo por que uma solução semelhante não deve funcionar na ASP.NET MVC.

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

É o que eu usei. Mude o caminho para corresponder ao seu exemplo.

Jesper Palm
fonte
5

Pelo que vale a pena, eu realmente odeio a ideia de encher meu aplicativo com tags de servidor apenas para resolver caminhos, então eu fiz um pouco mais de pesquisa e optei por usar algo que eu tentei antes para reescrever links - um filtro de resposta. Dessa forma, posso prefixar todos os caminhos absolutos com um prefixo conhecido e substituí-lo em tempo de execução usando o objeto Response.Filter e não precisar me preocupar com tags de servidor desnecessárias. O código está postado abaixo para o caso de ajudar mais alguém.

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

O mecanismo de visualização Razor para MVC 3 torna ainda mais fácil e limpo o uso de caminhos relativos de raiz virtual que são resolvidos corretamente em tempo de execução. Basta soltar o método Url.Content () no valor do atributo href e ele será resolvido corretamente.

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

Como Chris, eu realmente não suporto ter que colocar tags inchadas do lado do servidor dentro da minha marcação limpa apenas para dizer a essa coisa estúpida para olhar da raiz para cima. Isso deve ser algo muito simples e razoável de se pedir. Mas também odeio a ideia de ter que me esforçar para escrever qualquer classe C # customizada para fazer uma coisa tão simples, por que deveria? Que perda de tempo.

Para mim, simplesmente comprometi a "perfeição" e codifiquei o nome do caminho raiz do diretório virtual dentro das minhas referências de caminho. Assim:

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

Nenhum processamento do lado do servidor ou código C # necessário para resolver a URL, o que é melhor para o desempenho, embora eu saiba que seria insignificante de qualquer maneira. E nenhum caos feio do lado do servidor inchado na minha marcação limpa agradável.

Vou ter que viver sabendo que isso está codificado e precisará ser removido quando a coisa migrar para um domínio adequado em vez de http: // MyDevServer / MyProject /

Felicidades

Aaron
fonte
1
Votei para trazê-lo de volta a 0. Concordo totalmente com seus sentimentos. Sou novo no desenvolvimento da web depois de 5 anos em puro C # e que desastroso caos de espaguete.
Luke Puplett
Isso parece um meio-termo aceitável até que você precise fazer algo como implantar em um aplicativo da web aninhado. Usar a marcação do resolvedor corrigirá isso, mas seu link estático será quebrado. Exemplo: você constrói localmente no servidor da web integrado e, em seguida,
envia
Isso vai quebrar em muitos cenários de produção
Oskar Duveborn
Eu gosto bastante desta solução: thinkingstuff.co.uk/2013/02/…
Dion
1

Atrasado para o jogo, mas este post tem um resumo muito completo sobre como lidar com caminhos ASP.Net.

plyawn
fonte
1

Eu uso um método auxiliar simples. Você pode usá-lo facilmente nas Visualizações e Controladores.

Markup:

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

Método auxiliar:

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