MVC4 StyleBundle não resolve imagens


293

Minha pergunta é semelhante a esta:

Minificação do ASP.NET MVC 4 e imagens de plano de fundo

Só que eu quero ficar com o próprio pacote do MVC, se puder. Estou tendo um acidente cerebral tentando descobrir qual é o padrão correto para especificar pacotes de estilos, de modo que CSS e conjuntos de imagens independentes, como o jQuery UI, funcionem.

Eu tenho uma estrutura de site MVC típica com a /Content/css/qual contém meu CSS base, como styles.css. Dentro dessa pasta css, também tenho subpastas como a /jquery-uique contém seu arquivo CSS mais uma /imagespasta. Os caminhos de imagem no CSS da UI do jQuery são relativos a essa pasta e não quero mexer com eles.

Pelo que entendi, ao especificar um StyleBundle, preciso especificar um caminho virtual que também não corresponda a um caminho de conteúdo real, porque (assumindo que estou ignorando rotas para o Conteúdo), o IIS tentaria resolver esse caminho como um arquivo físico. Então, eu estou especificando:

bundles.Add(new StyleBundle("~/Content/styles/jquery-ui")
       .Include("~/Content/css/jquery-ui/*.css"));

processado usando:

@Styles.Render("~/Content/styles/jquery-ui")

Eu posso ver a solicitação saindo para:

http://localhost/MySite/Content/styles/jquery-ui?v=nL_6HPFtzoqrts9nwrtjq0VQFYnhMjY5EopXsK8cxmg1

Isso está retornando a resposta CSS correta e minificada. Mas o navegador envia uma solicitação para uma imagem relativamente vinculada como:

http://localhost/MySite/Content/styles/images/ui-bg_highlight-soft_100_eeeeee_1x100.png

Qual é um 404.

Entendo que a última parte do meu URL jquery-uié um URL sem extensão, um manipulador para o meu pacote, para que eu possa entender por que a solicitação relativa da imagem é simples /styles/images/.

Então, minha pergunta é qual é a maneira correta de lidar com essa situação?


9
Depois de ter sido frustrado repetidamente com a nova parte de Agrupamento e Minificação, passei para o Cassete, que agora é gratuito e funciona muito melhor!
Balexandre:

3
Obrigado pelo link, o Cassette está bonito e eu definitivamente vou dar uma olhada. Mas eu quero seguir a abordagem fornecida, se possível, certamente isso deve ser possível sem mexer com os caminhos da imagem nos arquivos CSS de terceiros sempre que uma nova versão for lançada. por enquanto eu mantive meus ScriptBundles (que funcionam muito bem), mas reverti para links CSS simples até que eu obtenha uma resolução. Felicidades.
Tom W Hall

Adicionando o provável erro por motivos de SEO: O controlador do caminho '/bundles/images/blah.jpg' não foi encontrado ou não implementa o IController.
Luke Puplett

Respostas:


361

De acordo com este encadeamento no MVC4, empacotamento de css e referências de imagem , se você definir seu pacote como:

bundles.Add(new StyleBundle("~/Content/css/jquery-ui/bundle")
                   .Include("~/Content/css/jquery-ui/*.css"));

Onde você define o pacote configurável no mesmo caminho que os arquivos de origem que compunham o pacote configurável, os caminhos de imagem relativos ainda funcionarão. A última parte do caminho do pacote configurável é realmente a do file namepacote configurável específico (ou seja, /bundlepode ser o nome que você quiser).

Isso só funcionará se você estiver agrupando CSS da mesma pasta (o que eu acho que faz sentido da perspectiva de agrupar).

Atualizar

De acordo com o comentário abaixo de @Hao Kung, agora isso pode ser conseguido aplicando um CssRewriteUrlTransformation( Altere as referências de URL relativas aos arquivos CSS quando agrupadas ).

NOTA: Não confirmei comentários sobre problemas com a reescrita para caminhos absolutos em um diretório virtual; portanto, isso pode não funcionar para todos (?).

bundles.Add(new StyleBundle("~/Content/css/jquery-ui/bundle")
                   .Include("~/Content/css/jquery-ui/*.css",
                    new CssRewriteUrlTransform()));

1
Lenda! Sim, isso funciona perfeitamente. Eu tenho CSS em níveis diferentes, mas cada um deles tem suas próprias pastas de imagens, por exemplo, o CSS do site principal está na pasta CSS raiz e, em seguida, o jquery-ui está dentro dele com sua própria pasta de imagens, então apenas especifique 2 pacotes, um para o meu CSS base e um para a interface do usuário do jQuery - que talvez não seja ideal em termos de solicitações, mas a vida é curta. Felicidades!
Tom W Hall

3
Sim, infelizmente, até que o pacote configurável tenha suporte para reescrever URLs incorporadas dentro do próprio CSS, você precisará do diretório virtual do pacote configurável para corresponder aos arquivos CSS antes do pacote configurável. É por isso que os pacotes de modelo padrão não tem urls como ~ / Bundles / temas e, em vez olhar como a estrutura de diretórios: ~ / content / theemes / base / css
Hao Kung

27
Agora isso é suportado via ItemTransforms, .Include ("~ / Content / css / jquery-ui / *. Css", novo CssRewriteUrlTransform ()); na 1.1Beta1 deve corrigir esse problema
Hao Kung

2
Isso foi corrigido no Microsoft ASP.NET Web Optimization Framework 1.1.3? Eu encontrei alguma informação sobre o que mudou nisso?
Andrus

13
new CssRewriteUrlTransform () é bom se você tiver um site no IIS. mas se for um aplicativo ou sub-aplicativo, isso não funcionará e você precisará definir seu pacote no mesmo local que o CSS.
Avidenic

34

A solução Grinn / ThePirat funciona bem.

Eu não gostei do fato de ele ter iniciado o método Include no pacote e de ter criado arquivos temporários no diretório de conteúdo. (eles acabaram sendo registrados, implantados e o serviço não iniciava!)

Então, para seguir o design do Bundling, optei por executar essencialmente o mesmo código, mas em uma implementação IBundleTransform:

class StyleRelativePathTransform
    : IBundleTransform
{
    public StyleRelativePathTransform()
    {
    }

    public void Process(BundleContext context, BundleResponse response)
    {
        response.Content = String.Empty;

        Regex pattern = new Regex(@"url\s*\(\s*([""']?)([^:)]+)\1\s*\)", RegexOptions.IgnoreCase);
        // open each of the files
        foreach (FileInfo cssFileInfo in response.Files)
        {
            if (cssFileInfo.Exists)
            {
                // apply the RegEx to the file (to change relative paths)
                string contents = File.ReadAllText(cssFileInfo.FullName);
                MatchCollection matches = pattern.Matches(contents);
                // Ignore the file if no match 
                if (matches.Count > 0)
                {
                    string cssFilePath = cssFileInfo.DirectoryName;
                    string cssVirtualPath = context.HttpContext.RelativeFromAbsolutePath(cssFilePath);
                    foreach (Match match in matches)
                    {
                        // this is a path that is relative to the CSS file
                        string relativeToCSS = match.Groups[2].Value;
                        // combine the relative path to the cssAbsolute
                        string absoluteToUrl = Path.GetFullPath(Path.Combine(cssFilePath, relativeToCSS));

                        // make this server relative
                        string serverRelativeUrl = context.HttpContext.RelativeFromAbsolutePath(absoluteToUrl);

                        string quote = match.Groups[1].Value;
                        string replace = String.Format("url({0}{1}{0})", quote, serverRelativeUrl);
                        contents = contents.Replace(match.Groups[0].Value, replace);
                    }
                }
                // copy the result into the response.
                response.Content = String.Format("{0}\r\n{1}", response.Content, contents);
            }
        }
    }
}

Em seguida, envolva isso em uma Implementação de pacote configurável:

public class StyleImagePathBundle 
    : Bundle
{
    public StyleImagePathBundle(string virtualPath)
        : base(virtualPath)
    {
        base.Transforms.Add(new StyleRelativePathTransform());
        base.Transforms.Add(new CssMinify());
    }

    public StyleImagePathBundle(string virtualPath, string cdnPath)
        : base(virtualPath, cdnPath)
    {
        base.Transforms.Add(new StyleRelativePathTransform());
        base.Transforms.Add(new CssMinify());
    }
}

Uso da amostra:

static void RegisterBundles(BundleCollection bundles)
{
...
    bundles.Add(new StyleImagePathBundle("~/bundles/Bootstrap")
            .Include(
                "~/Content/css/bootstrap.css",
                "~/Content/css/bootstrap-responsive.css",
                "~/Content/css/jquery.fancybox.css",
                "~/Content/css/style.css",
                "~/Content/css/error.css",
                "~/Content/validation.css"
            ));

Aqui está o meu método de extensão para RelativeFromAbsolutePath:

   public static string RelativeFromAbsolutePath(this HttpContextBase context, string path)
    {
        var request = context.Request;
        var applicationPath = request.PhysicalApplicationPath;
        var virtualDir = request.ApplicationPath;
        virtualDir = virtualDir == "/" ? virtualDir : (virtualDir + "/");
        return path.Replace(applicationPath, virtualDir).Replace(@"\", "/");
    }

Isso parece mais limpo para mim também. Obrigado. Estou votando em todos vocês três, porque parecia ser um esforço de equipe. :)
Josh Mouch 8/11

O código que você possui agora não está funcionando para mim. Estou tentando consertar, mas pensei em avisar. O método context.HttpContext.RelativeFromAbsolutePath não existe. Além disso, se o caminho da URL começar com um "/" (tornando-o absoluto), o caminho que combina a lógica está desativado.
Josh Mouch

2
@AcidPAT ótimo trabalho. A lógica falhou se o URL tivesse uma string de consulta (algumas bibliotecas de terceiros o adicionam, como FontAwesome para sua referência .woff.) É uma solução fácil. Pode-se ajustar o Regex ou corrigir relativeToCSSantes de ligar Path.GetFullPath().
Sergiopereira

2
@ChrisMarisic seu código não parece trabalho - response.Files é uma matriz de BundleFiles, estes objeto não tem propriedades tais como "existe", "DirectoryName" etc.
Nick Coad

2
@ ChrisMarisic, talvez exista um espaço para nome que eu deva importar, que fornece métodos de extensão para a classe BundleFile?
precisa

20

Melhor ainda (IMHO) implementar um pacote personalizado que corrige os caminhos da imagem. Eu escrevi um para o meu aplicativo.

using System;
using System.Collections.Generic;
using IO = System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.Optimization;

...

public class StyleImagePathBundle : Bundle
{
    public StyleImagePathBundle(string virtualPath)
        : base(virtualPath, new IBundleTransform[1]
      {
        (IBundleTransform) new CssMinify()
      })
    {
    }

    public StyleImagePathBundle(string virtualPath, string cdnPath)
        : base(virtualPath, cdnPath, new IBundleTransform[1]
      {
        (IBundleTransform) new CssMinify()
      })
    {
    }

    public new Bundle Include(params string[] virtualPaths)
    {
        if (HttpContext.Current.IsDebuggingEnabled)
        {
            // Debugging. Bundling will not occur so act normal and no one gets hurt.
            base.Include(virtualPaths.ToArray());
            return this;
        }

        // In production mode so CSS will be bundled. Correct image paths.
        var bundlePaths = new List<string>();
        var svr = HttpContext.Current.Server;
        foreach (var path in virtualPaths)
        {
            var pattern = new Regex(@"url\s*\(\s*([""']?)([^:)]+)\1\s*\)", RegexOptions.IgnoreCase);
            var contents = IO.File.ReadAllText(svr.MapPath(path));
            if(!pattern.IsMatch(contents))
            {
                bundlePaths.Add(path);
                continue;
            }


            var bundlePath = (IO.Path.GetDirectoryName(path) ?? string.Empty).Replace(@"\", "/") + "/";
            var bundleUrlPath = VirtualPathUtility.ToAbsolute(bundlePath);
            var bundleFilePath = String.Format("{0}{1}.bundle{2}",
                                               bundlePath,
                                               IO.Path.GetFileNameWithoutExtension(path),
                                               IO.Path.GetExtension(path));
            contents = pattern.Replace(contents, "url($1" + bundleUrlPath + "$2$1)");
            IO.File.WriteAllText(svr.MapPath(bundleFilePath), contents);
            bundlePaths.Add(bundleFilePath);
        }
        base.Include(bundlePaths.ToArray());
        return this;
    }

}

Para usá-lo, faça:

bundles.Add(new StyleImagePathBundle("~/bundles/css").Include(
  "~/This/Is/Some/Folder/Path/layout.css"));

...ao invés de...

bundles.Add(new StyleBundle("~/bundles/css").Include(
  "~/This/Is/Some/Folder/Path/layout.css"));

O que ele faz é (quando não está no modo de depuração) procura url(<something>)e o substitui por url(<absolute\path\to\something>). Eu escrevi a coisa cerca de 10 segundos atrás, para que possa precisar de alguns ajustes. Levei em consideração URLs totalmente qualificadas e DataURIs base64, certificando-me de que não haja dois pontos (:) no caminho da URL. Em nosso ambiente, as imagens normalmente residem na mesma pasta que os arquivos css, mas eu testei com as pastas pai ( url(../someFile.png)) e filho ( url(someFolder/someFile.png).


Esta é uma otima soluçao. Modifiquei seu Regex levemente para que ele também funcionasse com arquivos MENOS, mas o conceito original era exatamente o que eu precisava. Obrigado.
Tim Coulter

Você também pode colocar a inicialização do regex fora do loop. Talvez como uma propriedade estática somente leitura.
Miha Markic

12

Não é necessário especificar uma transformação ou ter caminhos de subdiretório louco. Depois de muita solução de problemas, eu o isolei dessa regra "simples" (é um bug?) ...

Se o caminho do pacote configurável não iniciar com a raiz relativa dos itens incluídos, a raiz do aplicativo da web não será levada em consideração.

Parece mais um bug para mim, mas de qualquer maneira é assim que você o corrige com a versão atual do .NET 4.51. Talvez as outras respostas fossem necessárias em versões mais antigas do ASP.NET, não posso dizer que não tenho tempo para testar retrospectivamente tudo isso.

Para esclarecer, aqui está um exemplo:

Eu tenho esses arquivos ...

~/Content/Images/Backgrounds/Some_Background_Tile.gif
~/Content/Site.css  - references the background image relatively, i.e. background: url('Images/...')

Em seguida, configure o pacote como ...

BundleTable.Add(new StyleBundle("~/Bundles/Styles").Include("~/Content/Site.css"));

E torná-lo como ...

@Styles.Render("~/Bundles/Styles")

E obtenha o "comportamento" (bug), os arquivos CSS possuem a raiz do aplicativo (por exemplo, "http: // localhost: 1234 / MySite / Content / Site.css"), mas a imagem CSS em todo o início "/ Content / Images / ... "ou" / Images / ... "dependendo se eu adiciono a transformação ou não.

Até tentei criar a pasta "Bundles" para ver se tinha a ver com o caminho existente ou não, mas isso não mudou nada. A solução para o problema é realmente o requisito de que o nome do pacote configurável comece com a raiz do caminho.

O que significa que este exemplo é corrigido registrando e renderizando o caminho do pacote configurável como ..

BundleTable.Add(new StyleBundle("~/Content/StylesBundle").Include("~/Content/Site.css"));
...
@Styles.Render("~/Content/StylesBundle")

Portanto, é claro que você poderia dizer que é RTFM, mas tenho certeza de que eu e outros buscamos esse caminho "~ / Bundles / ..." no modelo padrão ou em algum lugar da documentação no site MSDN ou ASP.NET, ou simplesmente tropeçou nele, porque na verdade é um nome bastante lógico para um caminho virtual e faz sentido escolher caminhos virtuais que não entrem em conflito com diretórios reais.

Enfim, é assim que é. A Microsoft não vê nenhum bug. Não concordo com isso: ele deve funcionar conforme o esperado ou alguma exceção deve ser lançada ou uma substituição adicional para adicionar o caminho do pacote que opta por incluir a raiz do aplicativo ou não. Não consigo imaginar por que alguém não gostaria que a raiz do aplicativo fosse incluída quando houvesse uma (normalmente, a menos que você tenha instalado seu site com um alias de DNS / raiz padrão do site). Então, na verdade, esse deve ser o padrão de qualquer maneira.


Parece-me a "solução" mais simples. Os outros podem ter efeitos colaterais, como na imagem: dados.
Fabrice

@MohamedEmaish funciona, você provavelmente entendeu algo errado. Aprenda a rastrear as solicitações, por exemplo, use a ferramenta Fiddler para ver quais URLs estão sendo solicitados pelo navegador. O objetivo não é codificar todo o caminho relativo para que seu site possa ser instalado em locais diferentes (caminhos raiz) no mesmo servidor ou seu produto possa alterar o URL padrão sem precisar reescrever muito do site (o ponto de ter e variável raiz do aplicativo).
Tony Muro

Fui com esta opção e funcionou muito bem. Tinha que garantir que cada pacote tivesse apenas itens de uma única pasta (não pode incluir itens de outras pastas ou subpastas), o que é um pouco chato, mas, contanto que funcione, estou feliz! Obrigado pelo post.
precisa saber é o seguinte

1
Obrigado. Suspiro. Um dia, eu gostaria de gastar mais tempo escrevendo código do que navegando no Stack.
Bruce Pierson

Eu tive um problema semelhante, onde um jquery-ui personalizado que tinha pastas aninhadas. assim que eu levantei as coisas como acima, funcionou. Não gosta de pastas aninhadas.
Andrei Bazanov 30/03/16

11

Descobri que CssRewriteUrlTransform falha ao executar se você estiver referenciando um *.cssarquivo e tiver o *.min.cssarquivo associado na mesma pasta.

Para corrigir isso, exclua o *.min.cssarquivo ou faça referência a ele diretamente em seu pacote configurável :

bundles.Add(new Bundle("~/bundles/bootstrap")
    .Include("~/Libs/bootstrap3/css/bootstrap.min.css", new CssRewriteUrlTransform()));

Depois disso, seus URLs serão transformados corretamente e suas imagens deverão ser resolvidas corretamente.


1
Obrigado! Após dois dias de pesquisa on-line, esta é a primeira menção que eu já vi em qualquer lugar do CssRewriteUrlTransform trabalhando com arquivos * .css, mas não com o arquivo * .min.css associado que é extraído quando você não está executando uma depuração meio Ambiente. Definitivamente parece um bug para mim. Terá que verificar manualmente o tipo de ambiente para definir um pacote com a versão não minificada para depuração, mas pelo menos eu tenho uma solução alternativa agora!
21316 Sean

1
Isso corrigiu o problema para mim. Isso certamente parece um bug. Não faz sentido ignorar CssRewriteUrlTransform se encontrar um arquivo .min.css preexistente.
user1751825

10

Talvez eu seja tendencioso, mas eu gosto bastante da minha solução, pois ela não faz nenhuma transformação, etc de regex e tem a menor quantidade de código :)

Isso funciona para um site hospedado como um diretório virtual em um site do IIS e como um site raiz no IIS

Então, eu criei uma implementação de IItemTransformencapsulado CssRewriteUrlTransforme usado VirtualPathUtilitypara corrigir o caminho e chamar o código existente:

/// <summary>
/// Is a wrapper class over CssRewriteUrlTransform to fix url's in css files for sites on IIS within Virutal Directories
/// and sites at the Root level
/// </summary>
public class CssUrlTransformWrapper : IItemTransform
{
    private readonly CssRewriteUrlTransform _cssRewriteUrlTransform;

    public CssUrlTransformWrapper()
    {
        _cssRewriteUrlTransform = new CssRewriteUrlTransform();
    }

    public string Process(string includedVirtualPath, string input)
    {
        return _cssRewriteUrlTransform.Process("~" + VirtualPathUtility.ToAbsolute(includedVirtualPath), input);
    }
}


//App_Start.cs
public static void Start()
{
      BundleTable.Bundles.Add(new StyleBundle("~/bundles/fontawesome")
                         .Include("~/content/font-awesome.css", new CssUrlTransformWrapper()));
}

Parece funcionar bem para mim?


1
Isso é perfeitamente adequado para mim. excelente solução. meu voto é +1
imdadhusen

1
Essa é a resposta correta. A classe CssUrlTransformWrapper fornecida pela estrutura soluciona o problema, exceto que ela não funciona apenas quando o aplicativo não está na raiz do site. Este invólucro aborda sucintamente essa falha.
Nine Tails

7

Embora a resposta de Chris Baxter ajude com o problema original, ela não funciona no meu caso quando o aplicativo está hospedado no diretório virtual . Depois de investigar as opções, terminei com a solução DIY.

ProperStyleBundleA classe inclui código emprestado do original CssRewriteUrlTransformpara transformar adequadamente os caminhos relativos no diretório virtual. Também lança se o arquivo não existir e impede a reordenação de arquivos no pacote (código retirado BetterStyleBundle).

using System;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.Optimization;
using System.Linq;

namespace MyNamespace
{
    public class ProperStyleBundle : StyleBundle
    {
        public override IBundleOrderer Orderer
        {
            get { return new NonOrderingBundleOrderer(); }
            set { throw new Exception( "Unable to override Non-Ordered bundler" ); }
        }

        public ProperStyleBundle( string virtualPath ) : base( virtualPath ) {}

        public ProperStyleBundle( string virtualPath, string cdnPath ) : base( virtualPath, cdnPath ) {}

        public override Bundle Include( params string[] virtualPaths )
        {
            foreach ( var virtualPath in virtualPaths ) {
                this.Include( virtualPath );
            }
            return this;
        }

        public override Bundle Include( string virtualPath, params IItemTransform[] transforms )
        {
            var realPath = System.Web.Hosting.HostingEnvironment.MapPath( virtualPath );
            if( !File.Exists( realPath ) )
            {
                throw new FileNotFoundException( "Virtual path not found: " + virtualPath );
            }
            var trans = new List<IItemTransform>( transforms ).Union( new[] { new ProperCssRewriteUrlTransform( virtualPath ) } ).ToArray();
            return base.Include( virtualPath, trans );
        }

        // This provides files in the same order as they have been added. 
        private class NonOrderingBundleOrderer : IBundleOrderer
        {
            public IEnumerable<BundleFile> OrderFiles( BundleContext context, IEnumerable<BundleFile> files )
            {
                return files;
            }
        }

        private class ProperCssRewriteUrlTransform : IItemTransform
        {
            private readonly string _basePath;

            public ProperCssRewriteUrlTransform( string basePath )
            {
                _basePath = basePath.EndsWith( "/" ) ? basePath : VirtualPathUtility.GetDirectory( basePath );
            }

            public string Process( string includedVirtualPath, string input )
            {
                if ( includedVirtualPath == null ) {
                    throw new ArgumentNullException( "includedVirtualPath" );
                }
                return ConvertUrlsToAbsolute( _basePath, input );
            }

            private static string RebaseUrlToAbsolute( string baseUrl, string url )
            {
                if ( string.IsNullOrWhiteSpace( url )
                     || string.IsNullOrWhiteSpace( baseUrl )
                     || url.StartsWith( "/", StringComparison.OrdinalIgnoreCase )
                     || url.StartsWith( "data:", StringComparison.OrdinalIgnoreCase )
                    ) {
                    return url;
                }
                if ( !baseUrl.EndsWith( "/", StringComparison.OrdinalIgnoreCase ) ) {
                    baseUrl = baseUrl + "/";
                }
                return VirtualPathUtility.ToAbsolute( baseUrl + url );
            }

            private static string ConvertUrlsToAbsolute( string baseUrl, string content )
            {
                if ( string.IsNullOrWhiteSpace( content ) ) {
                    return content;
                }
                return new Regex( "url\\(['\"]?(?<url>[^)]+?)['\"]?\\)" )
                    .Replace( content, ( match =>
                                         "url(" + RebaseUrlToAbsolute( baseUrl, match.Groups["url"].Value ) + ")" ) );
            }
        }
    }
}

Use-o como StyleBundle:

bundles.Add( new ProperStyleBundle( "~/styles/ui" )
    .Include( "~/Content/Themes/cm_default/style.css" )
    .Include( "~/Content/themes/custom-theme/jquery-ui-1.8.23.custom.css" )
    .Include( "~/Content/DataTables-1.9.4/media/css/jquery.dataTables.css" )
    .Include( "~/Content/DataTables-1.9.4/extras/TableTools/media/css/TableTools.css" ) );

2
Solução agradável, mas ainda falha (assim como CssRewriteUrlTransform) se você tiver um URI de dados em seu CSS (por exemplo, "dados: imagem / png; base64, ..."). Você não deve alterar os URLs começando com "data:" em RebaseUrlToAbsolute ().
usar o seguinte código

1
@ miles82 Claro! Obrigado por apontar isso. Eu mudei RebaseUrlToAbsolute ().
Nrodic

6

A partir da v1.1.0-alpha1 (pacote de pré-lançamento), a estrutura usa o VirtualPathProviderpara acessar arquivos em vez de tocar no sistema de arquivos físico.

O transformador atualizado pode ser visto abaixo:

public class StyleRelativePathTransform
    : IBundleTransform
{
    public void Process(BundleContext context, BundleResponse response)
    {
        Regex pattern = new Regex(@"url\s*\(\s*([""']?)([^:)]+)\1\s*\)", RegexOptions.IgnoreCase);

        response.Content = string.Empty;

        // open each of the files
        foreach (var file in response.Files)
        {
            using (var reader = new StreamReader(file.Open()))
            {
                var contents = reader.ReadToEnd();

                // apply the RegEx to the file (to change relative paths)
                var matches = pattern.Matches(contents);

                if (matches.Count > 0)
                {
                    var directoryPath = VirtualPathUtility.GetDirectory(file.VirtualPath);

                    foreach (Match match in matches)
                    {
                        // this is a path that is relative to the CSS file
                        var imageRelativePath = match.Groups[2].Value;

                        // get the image virtual path
                        var imageVirtualPath = VirtualPathUtility.Combine(directoryPath, imageRelativePath);

                        // convert the image virtual path to absolute
                        var quote = match.Groups[1].Value;
                        var replace = String.Format("url({0}{1}{0})", quote, VirtualPathUtility.ToAbsolute(imageVirtualPath));
                        contents = contents.Replace(match.Groups[0].Value, replace);
                    }

                }
                // copy the result into the response.
                response.Content = String.Format("{0}\r\n{1}", response.Content, contents);
            }
        }
    }
}

Na verdade, o que isso faz se substituir os URLs relativos no CSS por absolutos.
Fabrice

6

Aqui está uma transformação de pacote configurável que substituirá os URLs CSS por URLs relativos a esse arquivo CSS. Basta adicioná-lo ao seu pacote e ele deve corrigir o problema.

public class CssUrlTransform: IBundleTransform
{
    public void Process(BundleContext context, BundleResponse response) {
        Regex exp = new Regex(@"url\([^\)]+\)", RegexOptions.IgnoreCase | RegexOptions.Singleline);
        foreach (FileInfo css in response.Files) {
            string cssAppRelativePath = css.FullName.Replace(context.HttpContext.Request.PhysicalApplicationPath, context.HttpContext.Request.ApplicationPath).Replace(Path.DirectorySeparatorChar, '/');
            string cssDir = cssAppRelativePath.Substring(0, cssAppRelativePath.LastIndexOf('/'));
            response.Content = exp.Replace(response.Content, m => TransformUrl(m, cssDir));
        }
    }


    private string TransformUrl(Match match, string cssDir) {
        string url = match.Value.Substring(4, match.Length - 5).Trim('\'', '"');

        if (url.StartsWith("http://") || url.StartsWith("data:image")) return match.Value;

        if (!url.StartsWith("/"))
            url = string.Format("{0}/{1}", cssDir, url);

        return string.Format("url({0})", url);
    }

}

Como usá-lo ?, É mostre-me uma exceção:cannot convert type from BundleFile to FileInfo
Stiger

@Stiger change css.FullName.Replace (para css.VirtualFile.VirtualPath.Replace (
lkurylo

Eu posso estar usando isso errado, mas esse foreach reescreve todos os URLs em cada iteração e os deixa em relação ao último arquivo css que ele viu?
Andyrooger 16/01

4

Outra opção seria usar o módulo Regravação de URL do IIS para mapear a pasta da imagem do pacote virtual para a pasta da imagem física. Abaixo está um exemplo de uma regra de reescrita que você pode usar para um pacote chamado "~ / bundles / yourpage / styles" - observe que a regex corresponde a caracteres alfanuméricos, bem como hífens, sublinhados e pontos, comuns em nomes de arquivos de imagem .

<rewrite>
  <rules>
    <rule name="Bundle Images">
      <match url="^bundles/yourpage/images/([a-zA-Z0-9\-_.]+)" />
      <action type="Rewrite" url="Content/css/jquery-ui/images/{R:1}" />
    </rule>
  </rules>
</rewrite>

Essa abordagem cria um pouco de sobrecarga extra, mas permite que você tenha mais controle sobre os nomes dos pacotes configuráveis ​​e também reduz o número de pacotes configuráveis ​​que você pode ter que fazer referência em uma página. Obviamente, se você precisar fazer referência a vários arquivos css de terceiros que contenham referências relativas ao caminho da imagem, ainda não conseguirá criar vários pacotes configuráveis.


4

A solução Grinn é ótima.

No entanto, isso não funciona para mim quando há referências relativas à pasta pai no URL. ieurl('../../images/car.png')

Então, mudei um pouco o Includemétodo para resolver os caminhos para cada correspondência de regex, permitindo caminhos relativos e, opcionalmente, incorporar as imagens no css.

Também alterei o IF DEBUG para verificar em BundleTable.EnableOptimizationsvez de HttpContext.Current.IsDebuggingEnabled.

    public new Bundle Include(params string[] virtualPaths)
    {
        if (!BundleTable.EnableOptimizations)
        {
            // Debugging. Bundling will not occur so act normal and no one gets hurt. 
            base.Include(virtualPaths.ToArray());
            return this;
        }
        var bundlePaths = new List<string>();
        var server = HttpContext.Current.Server;
        var pattern = new Regex(@"url\s*\(\s*([""']?)([^:)]+)\1\s*\)", RegexOptions.IgnoreCase);
        foreach (var path in virtualPaths)
        {
            var contents = File.ReadAllText(server.MapPath(path));
            var matches = pattern.Matches(contents);
            // Ignore the file if no matches
            if (matches.Count == 0)
            {
                bundlePaths.Add(path);
                continue;
            }
            var bundlePath = (System.IO.Path.GetDirectoryName(path) ?? string.Empty).Replace(@"\", "/") + "/";
            var bundleUrlPath = VirtualPathUtility.ToAbsolute(bundlePath);
            var bundleFilePath = string.Format("{0}{1}.bundle{2}",
                                               bundlePath,
                                               System.IO.Path.GetFileNameWithoutExtension(path),
                                               System.IO.Path.GetExtension(path));
            // Transform the url (works with relative path to parent folder "../")
            contents = pattern.Replace(contents, m =>
            {
                var relativeUrl = m.Groups[2].Value;
                var urlReplace = GetUrlReplace(bundleUrlPath, relativeUrl, server);
                return string.Format("url({0}{1}{0})", m.Groups[1].Value, urlReplace);
            });
            File.WriteAllText(server.MapPath(bundleFilePath), contents);
            bundlePaths.Add(bundleFilePath);
        }
        base.Include(bundlePaths.ToArray());
        return this;
    }


    private string GetUrlReplace(string bundleUrlPath, string relativeUrl, HttpServerUtility server)
    {
        // Return the absolute uri
        Uri baseUri = new Uri("http://dummy.org");
        var absoluteUrl = new Uri(new Uri(baseUri, bundleUrlPath), relativeUrl).AbsolutePath;
        var localPath = server.MapPath(absoluteUrl);
        if (IsEmbedEnabled && File.Exists(localPath))
        {
            var fi = new FileInfo(localPath);
            if (fi.Length < 0x4000)
            {
                // Embed the image in uri
                string contentType = GetContentType(fi.Extension);
                if (null != contentType)
                {
                    var base64 = Convert.ToBase64String(File.ReadAllBytes(localPath));
                    // Return the serialized image
                    return string.Format("data:{0};base64,{1}", contentType, base64);
                }
            }
        }
        // Return the absolute uri 
        return absoluteUrl;
    }

Espero que ajude, cumprimentos.


2

Você pode simplesmente adicionar outro nível de profundidade ao caminho do seu pacote virtual

    //Two levels deep bundle path so that paths are maintained after minification
    bundles.Add(new StyleBundle("~/Content/css/css").Include("~/Content/bootstrap/bootstrap.css", "~/Content/site.css"));

Essa é uma resposta super baixa tecnologia e meio que um hack, mas funciona e não requer pré-processamento. Dada a extensão e complexidade de algumas dessas respostas, prefiro fazê-lo dessa maneira.


Isso não ajuda quando você tem seu aplicativo Web como aplicativo virtual no IIS. Quero dizer, pode funcionar, mas você deve nomear seu aplicativo virtual do IIS como no seu código, o que não é o que você deseja, certo?
Psulek

Eu tenho o mesmo problema quando o aplicativo é um aplicativo virtual no IIS. Essa resposta me ajuda.
BILL

2

Eu tive esse problema com pacotes configuráveis ​​com caminhos incorretos para imagens e CssRewriteUrlTransformnão resolvendo ..corretamente os caminhos pai relativos (também houve problemas com recursos externos, como webfonts). Foi por isso que escrevi essa transformação personalizada (parece fazer tudo isso acima corretamente):

public class CssRewriteUrlTransform2 : IItemTransform
{
    public string Process(string includedVirtualPath, string input)
    {
        var pathParts = includedVirtualPath.Replace("~/", "/").Split('/');
        pathParts = pathParts.Take(pathParts.Count() - 1).ToArray();
        return Regex.Replace
        (
            input,
            @"(url\(['""]?)((?:\/??\.\.)*)(.*?)(['""]?\))",
            m => 
            {
                // Somehow assigning this to a variable is faster than directly returning the output
                var output =
                (
                    // Check if it's an aboslute url or base64
                    m.Groups[3].Value.IndexOf(':') == -1 ?
                    (
                        m.Groups[1].Value +
                        (
                            (
                                (
                                    m.Groups[2].Value.Length > 0 ||
                                    !m.Groups[3].Value.StartsWith('/')
                                )
                            ) ?
                            string.Join("/", pathParts.Take(pathParts.Count() - m.Groups[2].Value.Count(".."))) :
                            ""
                        ) +
                        (!m.Groups[3].Value.StartsWith('/') ? "/" + m.Groups[3].Value : m.Groups[3].Value) +
                        m.Groups[4].Value
                    ) :
                    m.Groups[0].Value
                );
                return output;
            }
        );
    }
}

Editar: não percebi, mas usei alguns métodos de extensão personalizados no código. O código fonte desses é:

/// <summary>
/// Based on: http://stackoverflow.com/a/11773674
/// </summary>
public static int Count(this string source, string substring)
{
    int count = 0, n = 0;

    while ((n = source.IndexOf(substring, n, StringComparison.InvariantCulture)) != -1)
    {
        n += substring.Length;
        ++count;
    }
    return count;
}

public static bool StartsWith(this string source, char value)
{
    if (source.Length == 0)
    {
        return false;
    }
    return source[0] == value;
}

Obviamente, deve ser possível substituir String.StartsWith(char)por String.StartsWith(string).


Eu não tenho uma sobrecarga String.Count () que aceita uma string ( m.Groups[2].Value.Count("..")não funciona.) E Value.StartsWith('/')também não funciona porque StartsWith espera uma string em vez de um caractere.
jao

@jao meu mal, eu incluí meus próprios métodos de extensão no código sem perceber.
jahu

1
O @jao adicionou o código fonte desses métodos de extensão à resposta.
Jahu 13/11/2014

1

Após pouca investigação, concluí o seguinte: Você tem 2 opções:

  1. vá com transformações. Pacote muito útil para isso: https://bundletransformer.codeplex.com/, você precisa da seguinte transformação para cada pacote problemático:

    BundleResolver.Current = new CustomBundleResolver();
    var cssTransformer = new StyleTransformer();
    standardCssBundle.Transforms.Add(cssTransformer);
    bundles.Add(standardCssBundle);

Vantagens: desta solução, você pode nomear seu pacote configurável como desejar => você pode combinar arquivos css em um pacote configurável de diretórios diferentes. Desvantagens: Você precisa transformar todos os pacotes problemáticos

  1. Use a mesma raiz relativa para o nome do pacote, como onde o arquivo css está localizado. Vantagens: não há necessidade de transformação. Desvantagens: Você tem limitações na combinação de folhas de css de diretórios diferentes em um pacote.

0

CssRewriteUrlTransformcorrigiu meu problema.
Se o seu código ainda não carrega imagens após o uso CssRewriteUrlTransform, altere o nome do arquivo css de:

.Include("~/Content/jquery/jquery-ui-1.10.3.custom.css", new CssRewriteUrlTransform())

Para:

.Include("~/Content/jquery/jquery-ui.css", new CssRewriteUrlTransform())

De alguma forma. (Pontos) não estão reconhecendo no URL.


0

Lembre-se de corrigir várias inclusões CSS em um pacote, como:

bundles.Add(new StyleBundle("~/Content/styles/jquery-ui")
    .Include("~/Content/css/path1/somestyle1.css", "~/Content/css/path2/somestyle2.css"));

Você não pode simplesmente adicionar new CssRewriteUrlTransform()até o final, como pode com um arquivo CSS, pois o método não o suporta; portanto, você deve usar Includevárias vezes :

bundles.Add(new StyleBundle("~/Content/styles/jquery-ui")
    .Include("~/Content/css/path1/somestyle1.css", new CssRewriteUrlTransform())
    .Include("~/Content/css/path2/somestyle2.css", new CssRewriteUrlTransform()));
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.