MVC4 StyleBundle non risolve le immagini


293

La mia domanda è simile a questa:

ASP.NET MVC 4 Immagini di sfondo e di minimizzazione

Solo che voglio attenermi al raggruppamento di MVC, se posso. Sto avendo un crollo del cervello nel tentativo di capire quale sia lo schema corretto per specificare i bundle di stile in modo tale che css autonomi e set di immagini come l'interfaccia utente jQuery funzionino.

Ho una tipica struttura del sito MVC con la /Content/css/quale contiene i miei CSS di base come styles.css. All'interno di quella cartella CSS ho anche delle sottocartelle /jquery-uiche contengono il suo file CSS più un/images cartella. I percorsi delle immagini nel CSS dell'interfaccia utente jQuery sono relativi a quella cartella e non voglio fare confusione con loro.

A quanto ho capito, quando specifico un StyleBundleho bisogno di specificare un percorso virtuale che non corrisponda anche a un percorso di contenuto reale, perché (supponendo che sto ignorando le rotte al contenuto) IIS cercherebbe quindi di risolvere quel percorso come un file fisico. Quindi sto specificando:

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

reso usando:

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

Vedo la richiesta in uscita a:

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

Questo sta restituendo la risposta CSS corretta e minimizzata. Ma poi il browser invia una richiesta per un'immagine relativamente collegata come:

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

Che è un 404.

Capisco che l'ultima parte del mio URL jquery-uiè un URL senza estensione, un gestore per il mio pacchetto, quindi posso capire perché la relativa richiesta per l'immagine è semplicemente /styles/images/.

Quindi la mia domanda è: qual è il modo corretto di gestire questa situazione?


9
dopo essere stato frustrato più e più volte con la nuova parte Bundling and Minification, sono passato a La strega di Cassete ora è gratuita e funziona molto meglio!
Balexandre,

3
Grazie per il link, Cassette sembra carino e lo controllerò sicuramente. Ma voglio attenermi all'approccio fornito, se possibile, sicuramente questo deve essere possibile senza fare confusione con i percorsi delle immagini nei file CSS di terze parti ogni volta che viene rilasciata una nuova versione. per ora ho mantenuto i miei ScriptBundles (che funzionano bene) ma sono tornato a semplici collegamenti CSS fino a quando non ho ottenuto una risoluzione. Saluti.
Tom W Hall,

Aggiunta del probabile errore per motivi SEO: il controller per il percorso '/bundles/images/blah.jpg' non è stato trovato o non implementa IController.
Luke Puplett,

Risposte:


361

Secondo questo thread sul bundle css MVC4 e sui riferimenti alle immagini , se si definisce il bundle come:

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

Laddove si definisce il bundle sullo stesso percorso dei file di origine che lo compongono, i relativi percorsi immagine continueranno a funzionare. L'ultima parte del percorso del bundle è davvero quella file nameper quel bundle specifico (cioè, /bundlepuò essere qualsiasi nome che ti piace).

Funzionerà solo se stai raggruppando CSS dalla stessa cartella (che penso abbia senso dal punto di vista del raggruppamento).

Aggiornare

Come da commento di @Hao Kung, in alternativa, questo può ora essere ottenuto applicando un CssRewriteUrlTransformation( Modifica i riferimenti URL relativi ai file CSS quando raggruppati ).

NOTA: non ho confermato i commenti relativi a problemi con la riscrittura in percorsi assoluti all'interno di una directory virtuale, quindi potrebbe non funzionare per tutti (?).

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

1
Leggenda! Sì, funziona perfettamente. Ho CSS a livelli diversi ma ognuno ha le proprie cartelle di immagini, ad esempio il mio sito principale CSS è nella cartella CSS di root e quindi jquery-ui è all'interno di quello con la sua cartella di immagini, quindi specifica solo 2 bundle, uno per il mio CSS di base e uno per l'interfaccia utente di jQuery, che forse non è del tutto ottimale in termini di richieste, ma la vita è breve. Saluti!
Tom W Hall,

3
Sì, sfortunatamente, fino a quando il raggruppamento non ha il supporto per riscrivere gli URL incorporati all'interno del CSS stesso, è necessario che la directory virtuale del bundle CSS corrisponda ai file CSS prima del raggruppamento. Questo è il motivo per cui i bundle di template predefiniti non hanno URL come ~ / bundles / themes, e invece assomigliano alla struttura della directory: ~ / content / theemes / base / css
Hao Kung

27
Questo è ora supportato tramite ItemTransforms, .Include ("~ / Content / css / jquery-ui / *. Css", nuovo CssRewriteUrlTransform ())); in 1.1Beta1 dovrebbe risolvere questo problema
Hao Kung

2
Questo problema è stato risolto in Microsoft ASP.NET Web Optimization Framework 1.1.3? Ho trovato qualche informazione su cosa è cambiato in questo?
Andrus,

13
nuovo CssRewriteUrlTransform () va bene se si dispone di un sito Web in IIS. ma se si tratta di un'applicazione o di un'applicazione secondaria questo non funzionerà e dovrai ricorrere alla definizione del pacchetto nella stessa posizione del CSS.
avidenico

34

La soluzione Grinn / ThePirat funziona bene.

Non mi è piaciuto che fosse nuovo il metodo Include sul bundle e che avesse creato file temporanei nella directory dei contenuti. (hanno finito per essere registrati, distribuiti, quindi il servizio non si è avviato!)

Quindi, per seguire il design del Bundling, ho scelto di eseguire essenzialmente lo stesso codice, ma in un'implementazione 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);
            }
        }
    }
}

E poi concluso questo in un'implementazione del pacchetto:

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());
    }
}

Esempio di utilizzo:

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"
            ));

Ecco il mio metodo di estensione per 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(@"\", "/");
    }

Anche questo mi sembra più pulito. Grazie. Sto votando tutti e tre perché sembra uno sforzo di squadra. :)
Josh Mouch,

Il codice come lo hai ora non funziona per me. Sto cercando di risolverlo, ma ho pensato di fartelo sapere. Il metodo context.HttpContext.RelativeFromAbsolutePath non esiste. Inoltre, se il percorso dell'URL inizia con un "/" (rendendolo assoluto), il percorso che combina la logica è disattivato.
Josh Mouch,

2
@AcidPAT ottimo lavoro. La logica ha esito negativo se l'URL ha una stringa di query (alcune librerie di terze parti lo aggiungono, come FontAwesome per il suo riferimento .woff). Tuttavia, è una soluzione semplice. Si può regolare Regex o correggere relativeToCSSprima di chiamare Path.GetFullPath().
sergiopereira,

2
@ChrisMarisic il tuo codice non sembra funzionare - response.Files è un array di BundleFiles, questi oggetti non hanno proprietà come "Exists", "DirectoryName" ecc.
Nick Coad,

2
@ChrisMarisic esiste forse uno spazio dei nomi che dovrei importare che fornisce metodi di estensione per la classe BundleFile?
Nick Coad,

20

Meglio ancora (IMHO) implementare un pacchetto personalizzato che corregge i percorsi delle immagini. Ne ho scritto uno per la mia app.

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;
    }

}

Per usarlo, fai:

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

...invece di...

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

Quello che fa è (quando non in modalità debug) lo cerca url(<something>)e lo sostituisce con url(<absolute\path\to\something>). Ho scritto la cosa circa 10 secondi fa, quindi potrebbe essere necessario un piccolo ritocco. Ho preso in considerazione gli URL completi e i DataURI base64 assicurandomi che non ci siano due punti (:) nel percorso dell'URL. Nel nostro ambiente, le immagini normalmente risiedono nella stessa cartella dei loro file css, ma l'ho provato sia con le cartelle padre ( url(../someFile.png)) che con le cartelle figlio ( url(someFolder/someFile.png).


Questa è un'ottima soluzione Ho modificato leggermente il tuo Regex in modo che funzionasse anche con meno file, ma il concetto originale era esattamente quello di cui avevo bisogno. Grazie.
Tim Coulter,

Potresti mettere l'inizializzazione regex anche al di fuori del ciclo. Forse come proprietà di sola lettura statica.
Miha Markic,

12

Non è necessario specificare una trasformazione o avere percorsi di sottodirectory pazzi. Dopo aver risolto molti problemi l'ho isolato in questa "semplice" regola (è un bug?) ...

Se il percorso del bundle non inizia con la radice relativa degli elementi inclusi, la radice dell'applicazione Web non verrà presa in considerazione.

Mi sembra più un bug, ma comunque è così che lo risolvi con l'attuale versione di .NET 4.51. Forse le altre risposte erano necessarie su build ASP.NET precedenti, non posso dire di non avere tempo per testare retrospettivamente tutto ciò.

Per chiarire, ecco un esempio:

Ho questi file ...

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

Quindi imposta il pacchetto come ...

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

E renderlo come ...

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

E ottieni il "comportamento" (bug), i file CSS stessi hanno la radice dell'applicazione (es. "Http: // localhost: 1234 / MySite / Content / Site.css") ma l'immagine CSS è all'inizio "/ Content / Images / ... "o" / Immagini / ... "a seconda che aggiunga o meno la trasformazione.

Ho anche provato a creare la cartella "Bundles" per vedere se aveva a che fare con il percorso esistente o meno, ma ciò non ha cambiato nulla. La soluzione al problema è davvero il requisito che il nome del bundle deve iniziare con la radice del percorso.

Il significato di questo esempio è risolto registrando e visualizzando il percorso del bundle come ..

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

Quindi, naturalmente, potresti dire che si tratta di RTFM, ma sono abbastanza sicuro che io e altri abbiamo raccolto questo percorso "~ / Bundles / ..." dal modello predefinito o da qualche parte nella documentazione sul sito Web MSDN o ASP.NET, oppure ci siamo semplicemente imbattuti perché in realtà è un nome abbastanza logico per un percorso virtuale e ha senso scegliere tali percorsi virtuali che non sono in conflitto con directory reali.

Ad ogni modo, è così. Microsoft non vede alcun bug. Non sono d'accordo con questo, o dovrebbe funzionare come previsto o dovrebbe essere generata qualche eccezione, o un'ulteriore sostituzione per aggiungere il percorso del bundle che opta per includere la radice dell'applicazione o meno. Non riesco a immaginare perché qualcuno non vorrebbe includere la root dell'applicazione quando ce n'era una (normalmente a meno che tu non abbia installato il tuo sito web con un alias DNS / root del sito Web predefinito). Quindi in realtà dovrebbe essere comunque l'impostazione predefinita.


Mi sembra la "soluzione" più semplice. Gli altri possono avere effetti collaterali, come con image: data.
Fabrice,

@MohamedEmaish funziona, probabilmente hai sbagliato qualcosa. Scopri come tracciare le richieste, ad esempio usa lo strumento Fiddler per vedere quali URL sono richiesti dal browser. L'obiettivo non è di codificare l'intero percorso relativo in modo da poter installare il sito Web in posizioni diverse (percorsi root) sullo stesso server o il prodotto può modificare l'URL predefinito senza dover riscrivere molto del sito Web (il punto di avere e la variabile radice dell'applicazione).
Tony Wall,

Sono andato con questa opzione e ha funzionato benissimo. Dovevo assicurarmi che ogni pacchetto avesse solo elementi da una singola cartella (non può includere elementi da altre cartelle o sottocartelle), il che è leggermente fastidioso ma finché funziona sono felice! Grazie per il post.
hvaughan3,

1
Grazie. Sospiro. Un giorno mi piacerebbe passare più tempo a scrivere codice che a sfogliare Stack.
Bruce Pierson,

Ho avuto un problema simile in cui un jquery-ui personalizzato che aveva cartelle nidificate. non appena ho livellato le cose come sopra, ha funzionato. Non gli piacciono le cartelle nidificate.
Andrei Bazanov,

11

Ho scoperto che CssRewriteUrlTransform non riesce a funzionare se stai facendo riferimento a un *.cssfile e ne hai associato*.min.css file nella stessa cartella.

Per risolvere questo problema, elimina il *.min.cssfile o fai riferimento direttamente nel pacchetto:

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

Dopodiché, i tuoi URL verranno trasformati correttamente e le tue immagini dovrebbero essere risolte correttamente.


1
Grazie! Dopo due giorni di ricerche online, questa è la prima menzione che ho visto ovunque in CssRewriteUrlTransform che funziona con i file * .css, ma non con il file * .min.css associato che viene inserito quando non si esegue un debug ambiente. Sicuramente mi sembra un bug. Dovrà controllare manualmente il tipo di ambiente per definire un bundle con la versione non ridotta per il debug, ma almeno ho una soluzione alternativa ora!
Sean,

1
Questo ha risolto il problema per me. Questo sembra certamente un bug. Non ha senso che dovrebbe ignorare CssRewriteUrlTransform se trova un file .min.css preesistente.
user1751825

10

Forse sono di parte, ma mi piace molto la mia soluzione in quanto non fa alcuna trasformazione, regex ecc. Ed ha la minima quantità di codice :)

Questo funziona per un sito ospitato come Directory virtuale in un sito Web IIS e come sito Web principale su IIS

Quindi ho creato un impianto di IItemTransformincapsulato CssRewriteUrlTransforme usato VirtualPathUtilityper correggere il percorso e chiamare il codice esistente:

/// <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()));
}

Sembra funzionare bene per me?


1
Questa è la suite perfetta per me. soluzione eccellente. il mio voto è +1
imdadhusen

1
Questa è la risposta corretta La classe CssUrlTransformWrapper fornita dal framework risolve il problema, tranne per il fatto che non funziona solo quando l'applicazione non si trova nella radice del sito Web. Questo wrapper risolve in modo succinto quel difetto.
Nove code

7

Sebbene la risposta di Chris Baxter aiuti con il problema originale, non funziona nel mio caso quando l'applicazione è ospitata nella directory virtuale . Dopo aver esaminato le opzioni, ho finito con la soluzione fai-da-te.

ProperStyleBundleLa classe include il codice preso in prestito dall'originale CssRewriteUrlTransformper trasformare correttamente i percorsi relativi all'interno della directory virtuale. Genera anche se il file non esiste e impedisce il riordino dei file nel bundle (codice prelevato da 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 ) + ")" ) );
            }
        }
    }
}

Usalo come 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
Bella soluzione, ma comunque fallisce (proprio come CssRewriteUrlTransform) se hai un URI di dati nel tuo CSS (es. "Data: image / png; base64, ..."). Non dovresti cambiare l'URL che inizia con "data:" in RebaseUrlToAbsolute ().
miglia82

1
@ miglia82 Certo! Grazie per averlo segnalato. Ho cambiato RebaseUrlToAbsolute ().
nrodic

6

A partire da v1.1.0-alpha1 (pacchetto pre-release) il framework utilizza i VirtualPathProviderfile per accedere ai file anziché toccare il file system fisico.

Il trasformatore aggiornato può essere visualizzato di seguito:

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);
            }
        }
    }
}

In realtà, cosa succede se si sostituiscono gli URL relativi nei CSS con quelli assoluti.
Fabrice,

6

Ecco una trasformazione del bundle che sostituirà gli URL CSS con gli URL relativi a quel file CSS. Aggiungilo al tuo pacchetto e dovrebbe risolvere il 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);
    }

}

Come si usa ?, Mi mostra un'eccezione:cannot convert type from BundleFile to FileInfo
Stiger

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

Potrei usare questo errore, ma questo foreach riscrive tutti gli URL su ogni iterazione e li lascia relativi all'ultimo file CSS che ha visto?
Andyrooger,

4

Un'altra opzione sarebbe quella di utilizzare il modulo di riscrittura URL IIS per mappare la cartella dell'immagine del bundle virtuale sulla cartella dell'immagine fisica. Di seguito è riportato un esempio di una regola di riscrittura che potresti usare per un bundle chiamato "~ / bundles / yourpage / styles" - nota le corrispondenze regex su caratteri alfanumerici, nonché trattini, trattini bassi e punti, che sono comuni nei nomi dei file di immagini .

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

Questo approccio crea un piccolo sovraccarico, ma consente di avere un maggiore controllo sui nomi dei pacchetti e riduce anche il numero di pacchetti che potrebbe essere necessario fare riferimento su una pagina. Ovviamente, se devi fare riferimento a più file css di terze parti che contengono riferimenti relativi al percorso dell'immagine, non puoi ancora aggirare la creazione di più bundle.


4

La soluzione Grinn è fantastica.

Tuttavia non funziona per me quando ci sono riferimenti relativi alla cartella principale nell'URL. vale a direurl('../../images/car.png')

Quindi, ho leggermente modificato il Includemetodo al fine di risolvere i percorsi per ogni corrispondenza regex, consentendo percorsi relativi e anche di incorporare facoltativamente le immagini nel CSS.

Ho anche cambiato IF DEBUG per verificare BundleTable.EnableOptimizationsinvece di 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;
    }

Spero che aiuti, saluti.


2

Puoi semplicemente aggiungere un altro livello di profondità al percorso del tuo bundle virtuale

    //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"));

Questa è una risposta super low-tech e un tipo di hack ma funziona e non richiede alcuna pre-elaborazione. Data la lunghezza e la complessità di alcune di queste risposte, preferisco farlo in questo modo.


Ciò non aiuta quando si dispone dell'app Web come applicazione virtuale in IIS. Voglio dire, può funzionare ma devi dare un nome all'app virtuale IIS come nel tuo codice, che non è quello che vuoi, giusto?
psulek,

Ho lo stesso problema quando l'app è un'applicazione virtuale in IIS. Questa risposta mi aiuta.
BILL

2

Ho avuto questo problema con pacchetti che avevano un percorso errato verso le immagini e che CssRewriteUrlTransformnon risolvevano ..correttamente i relativi percorsi principali (c'era anche un problema con risorse esterne come i webfonts). Ecco perché ho scritto questa trasformazione personalizzata (sembra fare tutto quanto sopra correttamente):

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;
            }
        );
    }
}

Modifica: non me ne sono reso conto, ma ho usato alcuni metodi di estensione personalizzati nel codice. Il codice sorgente di questi è:

/// <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;
}

Ovviamente dovrebbe essere possibile sostituirlo String.StartsWith(char)con String.StartsWith(string).


Non ho un sovraccarico String.Count () che accetta una stringa ( m.Groups[2].Value.Count("..")non funziona). E Value.StartsWith('/')non funziona neanche perché StartsWith si aspetta una stringa anziché un carattere.
jao,

@jao mio male ho incluso i miei metodi di estensione nel codice senza accorgermene.
jahu,

1
@jao ha aggiunto il codice sorgente di questi metodi di estensione alla risposta.
jahu,

1

Dopo una piccola indagine ho concluso quanto segue: Hai 2 opzioni:

  1. vai con le trasformazioni. Pacchetto molto utile per questo: https://bundletransformer.codeplex.com/ è necessario la seguente trasformazione per ogni pacchetto problematico:

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

Vantaggi: di questa soluzione, puoi nominare il tuo bundle come preferisci => puoi combinare i file css in un bundle da diverse directory. Svantaggi: è necessario trasformare ogni pacchetto problematico

  1. Usa la stessa radice relativa per il nome del bundle come dove si trova il file css. Vantaggi: non è necessaria alcuna trasformazione. Svantaggi: hai un limite nel combinare fogli CSS di directory diverse in un unico pacchetto.

0

CssRewriteUrlTransformrisolto il mio problema.
Se il codice non carica ancora le immagini dopo l'uso CssRewriteUrlTransform, modifica il nome del tuo file css da:

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

Per:

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

In qualche modo (punti) non vengono riconosciuti nell'URL.


0

Ricorda solo di correggere più inclusioni CSS in un bundle come:

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

Non puoi semplicemente aggiungere new CssRewriteUrlTransform()alla fine come puoi con un file CSS poiché il metodo non lo supporta, quindi devi usarlo Includepiù volte :

bundles.Add(new StyleBundle("~/Content/styles/jquery-ui")
    .Include("~/Content/css/path1/somestyle1.css", new CssRewriteUrlTransform())
    .Include("~/Content/css/path2/somestyle2.css", new CssRewriteUrlTransform()));
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.