MVC4 StyleBundle löst keine Bilder auf


293

Meine Frage ist ähnlich:

ASP.NET MVC 4 Minimierungs- und Hintergrundbilder

Nur dass ich bei MVCs eigener Bündelung bleiben möchte, wenn ich kann. Ich habe einen Brain Crash, bei dem versucht wird, das richtige Muster für die Angabe von Stilpaketen zu finden, sodass eigenständige CSS- und Image-Sets wie die jQuery-Benutzeroberfläche funktionieren.

Ich habe eine typische MVC-Site-Struktur, mit /Content/css/der mein Basis-CSS wie z styles.css. In diesem /jquery-uiCSS- Ordner befinden sich auch Unterordner, z. B. die CSS-Datei sowie einen /imagesOrdner. Bildpfade im jQuery UI CSS sind relativ zu diesem Ordner und ich möchte mich nicht mit ihnen herumschlagen.

So wie ich es verstehe, muss ich bei der Angabe von a StyleBundleeinen virtuellen Pfad angeben, der nicht auch mit einem realen Inhaltspfad übereinstimmt, da IIS (vorausgesetzt, ich ignoriere Routen zu Inhalten) dann versuchen würde, diesen Pfad als physische Datei aufzulösen. Also spezifiziere ich:

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

gerendert mit:

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

Ich kann sehen, dass die Anfrage an folgende Adresse geht:

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

Dies gibt die korrekte, minimierte CSS-Antwort zurück. Dann sendet der Browser eine Anfrage für ein relativ verknüpftes Bild wie folgt:

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

Welches ist ein 404.

Ich verstehe, dass der letzte Teil meiner URL jquery-uieine URL ohne Erweiterung ist, ein Handler für mein Bundle, sodass ich sehen kann, warum die relative Anforderung für das Bild einfach ist /styles/images/.

Meine Frage ist also, wie ich mit dieser Situation richtig umgehen kann.


9
Nachdem ich mit dem neuen Teil "Bündeln und Minifizieren" immer wieder frustriert war , ging ich weiter zu Cassete, die jetzt frei ist und viel besser funktioniert!
Balexandre

3
Danke für den Link, Cassette sieht gut aus und ich werde es auf jeden Fall ausprobieren. Aber ich möchte mich nach Möglichkeit an den bereitgestellten Ansatz halten. Dies muss sicherlich möglich sein, ohne bei jeder Veröffentlichung einer neuen Version mit Bildpfaden in CSS-Dateien von Drittanbietern herumzuspielen. Im Moment habe ich meine ScriptBundles (die gut funktionieren) beibehalten, aber zu einfachen CSS-Links zurückgekehrt, bis ich eine Lösung erhalten habe. Prost.
Tom W Hall

Hinzufügen des wahrscheinlichen Fehlers aus SEO-Gründen: Der Controller für den Pfad '/bundles/images/blah.jpg' wurde nicht gefunden oder implementiert IController nicht.
Luke Puplett

Antworten:


361

Laut diesem Thread zur MVC4-CSS-Bündelung und zu Bildreferenzen definieren Sie Ihr Bündel wie folgt :

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

Wenn Sie das Bundle auf demselben Pfad wie die Quelldateien definieren, aus denen das Bundle besteht, funktionieren die relativen Bildpfade weiterhin. Der letzte Teil des Bundle-Pfads ist wirklich der file namefür dieses bestimmte Bundle (dh /bundlekann ein beliebiger Name sein, den Sie mögen).

Dies funktioniert nur, wenn Sie CSS aus demselben Ordner bündeln (was aus Sicht der Bündelung meiner Meinung nach sinnvoll ist).

Aktualisieren

Gemäß dem Kommentar unten von @Hao Kung kann dies jetzt alternativ durch Anwenden von a CssRewriteUrlTransformation( Ändern der relativen URL-Verweise auf CSS-Dateien im Bundle ) erreicht werden.

HINWEIS: Ich habe keine Kommentare zu Problemen beim Umschreiben in absolute Pfade innerhalb eines virtuellen Verzeichnisses bestätigt, daher funktioniert dies möglicherweise nicht für alle (?).

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

1
Legende! Ja, das funktioniert perfekt. Ich habe CSS auf verschiedenen Ebenen, aber sie haben jeweils ihre eigenen Bildordner, z. B. befindet sich mein Hauptwebsite-CSS im CSS-Stammordner, und dann befindet sich jquery-ui darin mit einem eigenen Bildordner. Daher gebe ich nur zwei Bundles an, eines für meine Basis-CSS und eines für die jQuery-Benutzeroberfläche - was in Bezug auf Anforderungen möglicherweise nicht optimal ist, aber die Lebensdauer ist kurz. Prost!
Tom W Hall

3
Ja, leider muss das virtuelle Verzeichnis des CSS-Bundles vor dem Bündeln mit den CSS-Dateien übereinstimmen, bis die Bündelung das Umschreiben eingebetteter URLs im CSS selbst unterstützt. Aus diesem Grund haben die Standardvorlagenpakete keine URLs wie ~ / bundles / theme und sehen stattdessen wie die Verzeichnisstruktur aus: ~ / content / theemes / base / css
Hao Kung

27
Dies wird jetzt über ItemTransforms, .Include ("~ / Content / css / jquery-ui / *. Css", new CssRewriteUrlTransform ()) unterstützt. in der 1.1Beta1 sollte dieses Problem beheben
Hao Kung

2
Ist dies in Microsoft ASP.NET Web Optimization Framework 1.1.3 behoben? Ich habe Informationen darüber gefunden, was sich daran geändert hat.
Andrus

13
new CssRewriteUrlTransform () ist in Ordnung, wenn Sie eine Website in IIS haben. Wenn es sich jedoch um eine Anwendung oder Unteranwendung handelt, funktioniert dies nicht und Sie müssen Ihr Bundle am selben Speicherort wie Ihr CSS definieren.
Avidenic

34

Die Grinn / ThePirat-Lösung funktioniert gut.

Mir hat es nicht gefallen, dass die Include-Methode für das Bundle neu ist und temporäre Dateien im Inhaltsverzeichnis erstellt wurden. (Sie wurden eingecheckt und bereitgestellt, dann wurde der Dienst nicht gestartet!)

Um dem Design der Bündelung zu folgen, habe ich mich entschieden, im Wesentlichen denselben Code auszuführen, jedoch in einer IBundleTransform-Implementierung:

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

Und dann packte dies in eine Bundle Implemetation:

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

Beispielnutzung:

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

Hier ist meine Erweiterungsmethode für 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(@"\", "/");
    }

Das scheint mir auch am saubersten zu sein. Vielen Dank. Ich stimme euch allen dreien zu, weil es eine Teamleistung zu sein schien. :)
Josh Mouch

Der Code, wie Sie ihn jetzt haben, funktioniert bei mir nicht. Ich versuche es zu beheben, dachte aber, ich würde es dich wissen lassen. Die Methode context.HttpContext.RelativeFromAbsolutePath ist nicht vorhanden. Wenn der URL-Pfad mit einem "/" beginnt (was ihn absolut macht), ist Ihre Pfadkombinationslogik deaktiviert.
Josh Mouch

2
@ AcidPAT tolle Arbeit. Die Logik schlug fehl, wenn die URL einen Querystring hatte (einige Bibliotheken von Drittanbietern fügen ihn hinzu, wie FontAwesome als .woff-Referenz). Dies ist jedoch eine einfache Lösung. Man kann den Regex anpassen oder korrigieren, relativeToCSSbevor man anruft Path.GetFullPath().
Sergiopereira

2
@ChrisMarisic Ihr Code scheint nicht zu funktionieren - response.Files ist ein Array von BundleFiles, diese Objekte haben keine Eigenschaften wie "Exists", "DirectoryName" usw.
Nick Coad

2
@ChrisMarisic Gibt es vielleicht einen Namespace, den ich importieren sollte, der Erweiterungsmethoden für die BundleFile-Klasse bereitstellt?
Nick Coad

20

Besser noch (IMHO) implementieren Sie ein benutzerdefiniertes Bundle, das die Bildpfade korrigiert. Ich habe eine für meine App geschrieben.

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

}

Um es zu verwenden, gehen Sie wie folgt vor:

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

...anstatt...

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

Was es tut, ist (wenn es nicht im Debug-Modus ist), es zu suchen url(<something>)und durch zu ersetzen url(<absolute\path\to\something>). Ich habe das Ding vor ungefähr 10 Sekunden geschrieben, daher muss es möglicherweise etwas angepasst werden. Ich habe vollqualifizierte URLs und base64-DataURIs berücksichtigt, indem ich sichergestellt habe, dass der URL-Pfad keine Doppelpunkte (:) enthält. In unserer Umgebung befinden sich Bilder normalerweise im selben Ordner wie ihre CSS-Dateien, aber ich habe sie sowohl mit übergeordneten Ordnern ( url(../someFile.png)) als auch mit untergeordneten Ordnern ( ) getestet url(someFolder/someFile.png.


Dies ist eine großartige Lösung. Ich habe Ihren Regex leicht modifiziert, damit er auch mit WENIGER Dateien funktioniert, aber das ursprüngliche Konzept war genau das, was ich brauchte. Vielen Dank.
Tim Coulter

Sie können die Regex-Initialisierung auch außerhalb der Schleife platzieren. Vielleicht als statische schreibgeschützte Eigenschaft.
Miha Markic

12

Es ist nicht notwendig, eine Transformation anzugeben oder verrückte Unterverzeichnispfade zu haben. Nach langer Fehlerbehebung habe ich es auf diese "einfache" Regel isoliert (ist es ein Fehler?) ...

Wenn Ihr Bundle-Pfad nicht mit dem relativen Stamm der enthaltenen Elemente beginnt, wird der Stamm der Webanwendung nicht berücksichtigt.

Klingt für mich eher nach einem Fehler, aber so beheben Sie ihn trotzdem mit der aktuellen .NET 4.51-Version. Vielleicht waren die anderen Antworten bei älteren ASP.NET-Builds notwendig. Ich kann nicht sagen, dass Sie keine Zeit haben, dies alles nachträglich zu testen.

Zur Verdeutlichung hier ein Beispiel:

Ich habe diese Dateien ...

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

Dann richten Sie das Bundle wie ...

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

Und machen Sie es wie ...

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

Und erhalten Sie das "Verhalten" (Fehler), die CSS-Dateien selbst haben das Anwendungsstammverzeichnis (z. B. "http: // localhost: 1234 / MySite / Content / Site.css"), aber das CSS-Image in allen Starts "/ Content / Images / ... "oder" / Images / ... ", je nachdem, ob ich die Transformation hinzufüge oder nicht.

Ich habe sogar versucht, den Ordner "Bundles" zu erstellen, um festzustellen, ob er mit dem vorhandenen Pfad zu tun hat oder nicht, aber das hat nichts geändert. Die Lösung des Problems besteht darin, dass der Name des Bundles mit dem Pfadstamm beginnen muss.

Das heißt, dieses Beispiel wird behoben, indem der Bündelpfad wie folgt registriert und gerendert wird.

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

Man könnte natürlich sagen, dass dies RTFM ist, aber ich bin mir ziemlich sicher, dass ich und andere diesen "~ / Bundles / ..." - Pfad von der Standardvorlage oder irgendwo in der Dokumentation auf der MSDN- oder ASP.NET-Website oder übernommen haben Ich bin nur darauf gestoßen, weil es eigentlich ein ziemlich logischer Name für einen virtuellen Pfad ist und es sinnvoll ist, solche virtuellen Pfade zu wählen, die nicht mit realen Verzeichnissen in Konflikt stehen.

Jedenfalls ist das so. Microsoft sieht keinen Fehler. Ich bin damit nicht einverstanden, entweder sollte es wie erwartet funktionieren oder eine Ausnahme sollte ausgelöst werden, oder eine zusätzliche Überschreibung zum Hinzufügen des Bundle-Pfads, der sich dafür entscheidet, den Anwendungsstamm einzuschließen, oder nicht. Ich kann mir nicht vorstellen, warum jemand nicht möchte, dass das Anwendungsstammverzeichnis enthalten ist, wenn es eines gab (normalerweise, es sei denn, Sie haben Ihre Website mit einem DNS-Alias ​​/ Standard-Website-Stammverzeichnis installiert). Eigentlich sollte das sowieso die Standardeinstellung sein.


Scheint mir die einfachste "Lösung". Die anderen können Nebenwirkungen haben, wie bei image: data.
Fabrice

@ MohamedEmaish es funktioniert, du hast wahrscheinlich etwas falsch gemacht. Erfahren Sie, wie Sie die Anforderungen verfolgen, z. B. mithilfe des Fiddler-Tools, um festzustellen, welche URLs vom Browser angefordert werden. Das Ziel besteht nicht darin, den gesamten relativen Pfad fest zu codieren, damit Ihre Website an verschiedenen Orten (Stammpfaden) auf demselben Server installiert werden kann oder Ihr Produkt die Standard-URL ändern kann, ohne einen Großteil der Website neu schreiben zu müssen (der Punkt mit und Anwendungsstammvariable).
Tony Wall

Ging mit dieser Option und es hat super funktioniert. Musste sicherstellen, dass jedes Bundle nur Elemente aus einem einzelnen Ordner enthält (kann keine Elemente aus anderen Ordnern oder Unterordnern enthalten), was etwas ärgerlich ist, aber solange es funktioniert, bin ich glücklich! Danke für den Beitrag.
Hvaughan3

1
Vielen Dank. Seufzer. Eines Tages würde ich gerne mehr Zeit damit verbringen, Code zu schreiben, als Stack zu durchsuchen.
Bruce Pierson

Ich hatte ein ähnliches Problem mit einer benutzerdefinierten jquery-ui, die verschachtelte Ordner hatte. Sobald ich die Dinge wie oben geebnet hatte, funktionierte es. Es mag keine verschachtelten Ordner.
Andrei Bazanov

11

Ich habe festgestellt, dass CssRewriteUrlTransform nicht ausgeführt werden kann, wenn Sie auf eine *.cssDatei verweisen und sich die zugehörige *.min.cssDatei im selben Ordner befindet.

Um dies zu beheben, löschen Sie die *.min.cssDatei entweder oder verweisen Sie direkt auf sie in Ihrem Bundle:

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

Danach werden Ihre URLs korrekt transformiert und Ihre Bilder sollten korrekt aufgelöst werden.


1
Danke dir! Nach zwei Tagen Online-Suche ist dies die erste Erwähnung von CssRewriteUrlTransform, die mit * .css-Dateien arbeitet, jedoch nicht mit der zugehörigen * .min.css-Datei, die abgerufen wird, wenn Sie kein Debug ausführen Umgebung. Scheint mir definitiv ein Fehler zu sein. Müssen den Umgebungstyp manuell überprüfen, um ein Bundle mit der nicht minimierten Version zum Debuggen zu definieren, aber zumindest habe ich jetzt eine Problemumgehung!
Sean

1
Dies hat das Problem für mich behoben. Dies scheint sicherlich ein Fehler zu sein. Es macht keinen Sinn, CssRewriteUrlTransform zu ignorieren, wenn eine bereits vorhandene .min.css-Datei gefunden wird.
user1751825

10

Vielleicht bin ich voreingenommen, aber ich mag meine Lösung sehr, da sie keine Transformationen, Regex usw. durchführt und die geringste Menge an Code enthält :)

Dies funktioniert für eine Site, die als virtuelles Verzeichnis auf einer IIS-Website und als Root-Website auf IIS gehostet wird

Also habe ich eine Implentation von IItemTransformgekapseltem erstellt CssRewriteUrlTransformund verwendet VirtualPathUtility, um den Pfad zu reparieren und den vorhandenen Code aufzurufen:

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

Scheint gut für mich zu funktionieren?


1
Das ist perfekt für mich. ausgezeichnete Lösung. Meine Stimme ist +1
imdadhusen

1
Dies ist die richtige Antwort. Die vom Framework bereitgestellte CssUrlTransformWrapper-Klasse behebt das Problem, außer dass sie nicht nur funktioniert, wenn sich die Anwendung nicht im Stammverzeichnis der Website befindet. Dieser Wrapper behebt dieses Manko auf den Punkt.
Neun Schwänze

7

Obwohl die Antwort von Chris Baxter bei dem ursprünglichen Problem hilft, funktioniert sie in meinem Fall nicht, wenn die Anwendung in einem virtuellen Verzeichnis gehostet wird . Nachdem ich die Optionen untersucht hatte, beendete ich die DIY-Lösung.

ProperStyleBundleDie Klasse enthält Code, der vom Original entlehnt wurde CssRewriteUrlTransform, um relative Pfade innerhalb des virtuellen Verzeichnisses ordnungsgemäß zu transformieren. Es wird auch ausgelöst, wenn keine Datei vorhanden ist, und verhindert die Neuordnung von Dateien im Bundle (Code aus 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 ) + ")" ) );
            }
        }
    }
}

Verwenden Sie es wie 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
Gute Lösung, schlägt aber immer noch fehl (genau wie CssRewriteUrlTransform), wenn Sie einen Daten-URI in Ihrem CSS haben (z. B. "data: image / png; base64, ..."). Sie sollten URLs, die mit "data:" beginnen, in RebaseUrlToAbsolute () nicht ändern.
Meilen82

1
@ Meilen82 Natürlich! Vielen Dank für den Hinweis. Ich habe RebaseUrlToAbsolute () geändert.
Nrodic

6

Ab Version 1.1.0-alpha1 (Pre-Release-Paket) verwendet das Framework das, VirtualPathProviderum auf Dateien zuzugreifen, anstatt das physische Dateisystem zu berühren.

Der aktualisierte Transformator ist unten zu sehen:

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

Was macht das eigentlich, wenn die relativen URLs in CSS durch absolute ersetzt werden?
Fabrice

6

Hier ist eine Bundle-Transformation, die CSS-URLs durch URLs relativ zu dieser CSS-Datei ersetzt. Fügen Sie es einfach Ihrem Bundle hinzu und es sollte das Problem beheben.

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

}

Wie man es benutzt?, Es zeigt mir eine Ausnahme:cannot convert type from BundleFile to FileInfo
Stiger

@ Stiger ändern css.FullName.Replace (zu css.VirtualFile.VirtualPath.Replace (
lkurylo

Ich verwende dies möglicherweise falsch, aber schreibt das foreach alle URLs bei jeder Iteration neu und belässt sie relativ zur letzten CSS-Datei, die es gesehen hat?
Andyrooger

4

Eine andere Option wäre die Verwendung des IIS URL Rewrite-Moduls, um den virtuellen Bundle-Image-Ordner dem physischen Image-Ordner zuzuordnen. Im Folgenden finden Sie ein Beispiel für eine Umschreiberegel, die Sie für ein Bundle mit dem Namen "~ / bundles / yourpage / styles" verwenden können. Beachten Sie die Regex-Übereinstimmungen für alphanumerische Zeichen sowie Bindestriche, Unterstriche und Punkte, die in Bilddateinamen häufig vorkommen .

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

Dieser Ansatz verursacht einen zusätzlichen Aufwand, ermöglicht Ihnen jedoch eine bessere Kontrolle über Ihre Bundle-Namen und reduziert auch die Anzahl der Bundles, auf die Sie möglicherweise auf einer Seite verweisen müssen. Wenn Sie auf mehrere CSS-Dateien von Drittanbietern verweisen müssen, die relative Bildpfadreferenzen enthalten, können Sie natürlich immer noch nicht um das Erstellen mehrerer Bundles herumkommen.


4

Grinn-Lösung ist großartig.

Es funktioniert jedoch nicht für mich, wenn die URL relative Verweise auf übergeordnete Ordner enthält. dhurl('../../images/car.png')

Daher habe ich die IncludeMethode geringfügig geändert , um die Pfade für jede Regex-Übereinstimmung aufzulösen, relative Pfade zuzulassen und die Bilder optional in das CSS einzubetten.

Ich habe auch den IF DEBUG geändert, um zu überprüfen, BundleTable.EnableOptimizationsanstatt 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;
    }

Hoffe es hilft, Grüße.


2

Sie können Ihrem virtuellen Bundle-Pfad einfach eine weitere Tiefe hinzufügen

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

Dies ist eine Super-Low-Tech-Antwort und eine Art Hack, aber sie funktioniert und erfordert keine Vorverarbeitung. Angesichts der Länge und Komplexität einiger dieser Antworten mache ich es lieber so.


Dies hilft nicht, wenn Sie Ihre Webanwendung als virtuelle Anwendung in IIS haben. Ich meine, es kann funktionieren, aber Sie müssen Ihre virtuelle IIS-App wie in Ihrem Code benennen, was nicht das ist, was Sie wollen, oder?
Psulek

Ich habe das gleiche Problem, wenn App eine virtuelle Anwendung in IIS ist. Diese Antwort hilft mir.
Rechnung

2

Ich hatte dieses Problem mit Bundles, die falsche Pfade zu Bildern hatten und die CssRewriteUrlTransformrelativen übergeordneten Pfade nicht ..korrekt auflösten (es gab auch Probleme mit externen Ressourcen wie Webfonts). Aus diesem Grund habe ich diese benutzerdefinierte Transformation geschrieben (scheint alle oben genannten Schritte korrekt auszuführen):

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

Bearbeiten: Ich habe es nicht bemerkt, aber ich habe einige benutzerdefinierte Erweiterungsmethoden im Code verwendet. Der Quellcode von diesen ist:

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

Natürlich sollte es möglich sein , zu ersetzen String.StartsWith(char)mit String.StartsWith(string).


Ich habe keine String.Count () - Überladung, die eine Zeichenfolge akzeptiert ( m.Groups[2].Value.Count("..")funktioniert nicht). Sie Value.StartsWith('/')funktioniert auch nicht, da StartsWith eine Zeichenfolge anstelle eines Zeichens erwartet.
Jao

@jao my bad Ich habe meine eigenen Erweiterungsmethoden in den Code aufgenommen, ohne es zu merken.
Jahu

1
@jao fügte der Antwort den Quellcode dieser Erweiterungsmethoden hinzu.
Jahu

1

Nach kleinen Nachforschungen kam ich zu folgendem Schluss: Sie haben zwei Möglichkeiten:

  1. gehe mit Transformationen. Sehr nützliches Paket dafür: https://bundletransformer.codeplex.com/ Sie benötigen die folgende Transformation für jedes problematische Bundle:

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

Vorteile: Mit dieser Lösung können Sie Ihr Bundle beliebig benennen => Sie können CSS-Dateien aus verschiedenen Verzeichnissen zu einem Bundle kombinieren. Nachteile: Sie müssen jedes problematische Bundle transformieren

  1. Verwenden Sie für den Namen des Bundles denselben relativen Stamm wie für die CSS-Datei. Vorteile: Transformationsbedarf besteht nicht. Nachteile: Sie können CSS-Blätter aus verschiedenen Verzeichnissen nur zu einem Bundle kombinieren.

0

CssRewriteUrlTransformmein Problem behoben.
Wenn Ihr Code nach der Verwendung immer noch keine Bilder lädt CssRewriteUrlTransform, ändern Sie den CSS-Dateinamen von:

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

Zu:

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

Irgendwie (Punkte) werden in der URL nicht erkannt.


0

Denken Sie daran, mehrere CSS-Einschlüsse in einem Bundle zu korrigieren, z.

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

Sie können nicht nur hinzufügen , new CssRewriteUrlTransform()bis zum Ende , wie Sie können mit einer CSS - Datei als die Methode nicht unterstützt, so dass Sie zu haben , verwenden Includemehrfach :

bundles.Add(new StyleBundle("~/Content/styles/jquery-ui")
    .Include("~/Content/css/path1/somestyle1.css", new CssRewriteUrlTransform())
    .Include("~/Content/css/path2/somestyle2.css", new CssRewriteUrlTransform()));
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.