Pakiet MVC4 StyleBundle nie rozpoznaje obrazów


293

Moje pytanie jest podobne do tego:

ASP.NET MVC 4 Minifikacja i obrazy tła

Tyle że chcę pozostać przy własnym pakiecie MVC, jeśli mogę. Mam awarię mózgu, próbując dowiedzieć się, jaki jest prawidłowy wzorzec określania pakietów stylów, takich jak samodzielne zestawy css i zestawy obrazów, takie jak interfejs użytkownika jQuery.

Mam typową strukturę witryny MVC, /Content/css/która zawiera mój podstawowy CSS, taki jak styles.css. W tym folderze css mam również podfoldery, takie jak /jquery-uizawierający plik CSS plus /imagesfolder. Ścieżki obrazów w CSS interfejsu jQuery są względne w stosunku do tego folderu i nie chcę z nimi zadzierać.

Jak rozumiem, kiedy określam StyleBundle, muszę podać ścieżkę wirtualną, która nie pasuje również do prawdziwej ścieżki treści, ponieważ (zakładając, że ignoruję trasy do Treści) IIS spróbuje następnie rozwiązać tę ścieżkę jako plik fizyczny. Więc określam:

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

renderowane przy użyciu:

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

Widzę prośbę wychodzącą na:

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

To zwraca poprawną, zminimalizowaną odpowiedź CSS. Ale potem przeglądarka wysyła żądanie względnie połączonego obrazu jako:

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

Co jest 404.

Rozumiem, że ostatnia część mojego adresu URL jquery-uito adres URL bez rozszerzenia, moduł obsługi mojego pakietu, więc mogę zrozumieć, dlaczego względne żądanie obrazu jest po prostu /styles/images/.

Więc moje pytanie brzmi: jaki jest właściwy sposób radzenia sobie z tą sytuacją?


9
Po zostały udaremnione kółko z nowym wiązania i minifikacji części, przeniosłem się do cassete czarownica jest bezpłatny i działa o wiele lepiej!
balexandre,

3
Dzięki za link, kaseta wygląda ładnie i na pewno to sprawdzę. Ale jeśli to możliwe, chcę trzymać się zapewnionego podejścia, z pewnością musi to być możliwe bez bałagania się ścieżek obrazu w plikach CSS innych firm za każdym razem, gdy nowa wersja jest wydawana. na razie zachowałem moje ScriptBundles (które działają dobrze), ale wróciłem do zwykłych linków CSS, dopóki nie otrzymałem rozdzielczości. Twoje zdrowie.
Tom W Hall

Dodanie prawdopodobnego błędu z powodów SEO: Kontroler ścieżki „/bundles/images/blah.jpg” nie został znaleziony lub nie implementuje IController.
Luke Puplett

Odpowiedzi:


361

Zgodnie z tym wątkiem dotyczącym pakietowania css MVC4 i odwołań do obrazów , jeśli pakiet zostanie zdefiniowany jako:

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

Jeśli pakiet zostanie zdefiniowany na tej samej ścieżce, co pliki źródłowe, które go utworzyły, względne ścieżki obrazu będą nadal działać. Ostatnia część ścieżki pakietu jest naprawdę file namedla tego konkretnego pakietu (tzn./bundle Może to być dowolna nazwa).

Działa to tylko wtedy, gdy łączysz razem CSS z tego samego folderu (co moim zdaniem ma sens z perspektywy łączenia).

Aktualizacja

Zgodnie z komentarzem poniżej autorstwa @Hao Kung, alternatywnie można to teraz osiągnąć poprzez zastosowanie CssRewriteUrlTransformation( Zmień względne odwołania do adresów URL do plików CSS w pakiecie ).

UWAGA: Nie potwierdziłem komentarzy dotyczących problemów z przepisywaniem do absolutnych ścieżek w katalogu wirtualnym, więc może to nie działać dla wszystkich (?).

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

1
Legenda! Tak, to działa idealnie. Mam CSS na różnych poziomach, ale każdy z nich ma własne foldery z obrazami, np. Moja główna strona CSS znajduje się w głównym folderze CSS, a następnie jquery-ui znajduje się w tym folderze z własnymi folderami obrazów, więc podaję tylko dwa pakiety, jeden dla mojego bazowy CSS i jeden dla jQuery UI - który może nie jest optymalny pod względem żądań, ale życie jest krótkie. Twoje zdrowie!
Tom W Hall

3
Tak, niestety, dopóki pakiet nie będzie obsługiwał przepisywania osadzonych adresów URL w samym css, potrzebujesz katalogu wirtualnego pakietu css, aby dopasować pliki css przed pakowaniem. Dlatego domyślne pakiety szablonów nie mają adresów URL takich jak ~ / bundles / themes, a zamiast tego wyglądają jak struktura katalogów: ~ / content / theemes / base / css
Hao Kung

27
Jest to teraz obsługiwane przez ItemTransforms, .Include ("~ / Content / css / jquery-ui / *. Css", nowy CssRewriteUrlTransform ())); w wersji 1.1Beta1 powinien rozwiązać ten problem
Hao Kung

2
Czy to naprawiono w Microsoft ASP.NET Web Optimization Framework 1.1.3? Nie znalazłem żadnych informacji o tym, co się w tym zmieniło?
Andrus

13
nowa CssRewriteUrlTransform () jest w porządku, jeśli masz witrynę internetową w IIS. ale jeśli jest to aplikacja lub aplikacja podrzędna, to nie zadziała i musisz uciekać się do zdefiniowania pakietu w tej samej lokalizacji co CSS.
avidenic

34

Rozwiązanie Grinn / ThePirat działa dobrze.

Nie podobało mi się, że nowa metoda zawierała pakiet w pakiecie i że tworzyła pliki tymczasowe w katalogu zawartości. (skończyło się na odprawie, wdrożeniu, usługa się nie uruchomi!)

Dlatego podążając za projektem sprzedaży wiązanej, zdecydowałem się wykonać zasadniczo ten sam kod, ale w implementacji 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);
            }
        }
    }
}

A potem zapakowałem to w pakiet: 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());
    }
}

Przykładowe użycie:

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

Oto moja metoda rozszerzenia dla 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(@"\", "/");
    }

To też wydaje mi się najczystsze. Dzięki. Głosuję za wami wszystkimi, ponieważ wyglądało to na wysiłek zespołu. :)
Josh Mouch

Obecny kod nie działa dla mnie. Próbuję to naprawić, ale pomyślałem, że dam ci znać. Metoda context.HttpContext.RelativeFromAbsolutePath nie istnieje. Ponadto, jeśli ścieżka adresu URL zaczyna się od „/” (co czyni ją absolutną), logika łączenia ścieżki jest wyłączona.
Josh Mouch

2
@AcidPAT świetna robota. Logika nie powiodła się, jeśli adres URL zawierał kwerendę (niektóre biblioteki innych firm ją dodają, na przykład FontAwesome jako odniesienie .woff). Jest to jednak łatwa poprawka. Można dopasować regex lub naprawić relativeToCSSprzed wywołaniem Path.GetFullPath().
sergiopereira

2
@ChrisMarisic wydaje się, że Twój kod nie działa - odpowiedź. Pliki to tablica plików BundleFiles, te obiekty nie mają właściwości takich jak „Exists”, „DirectoryName” itp.
Nick Coad

2
@ChrisMarisic, czy może istnieje przestrzeń nazw, którą powinienem importować, która zapewnia metody rozszerzenia dla klasy BundleFile?
Nick Coad,

20

Lepiej jeszcze (IMHO) zaimplementuj niestandardowy pakiet, który naprawia ścieżki obrazu. Napisałem jeden dla mojej aplikacji.

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

}

Aby z niego skorzystać:

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

...zamiast...

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

To, co robi (gdy nie jest w trybie debugowania), szuka url(<something>)i zamienia je na url(<absolute\path\to\something>). Napisałem tę rzecz około 10 sekund temu, więc może potrzebować trochę poprawek. Wziąłem pod uwagę w pełni kwalifikowane adresy URL i baseURI DataURI, upewniając się, że w ścieżce URL nie ma dwukropków (:). W naszym środowisku obrazy zwykle znajdują się w tym samym folderze, co ich pliki css, ale przetestowałem je zarówno z folderami nadrzędnymi ( url(../someFile.png)), jak i podrzędnymi ( url(someFolder/someFile.png).


To świetne rozwiązanie. Zmodyfikowałem nieco twój Regex, aby działał również z plikami LESS, ale oryginalna koncepcja była dokładnie tym, czego potrzebowałem. Dzięki.
Tim Coulter

Możesz także umieścić inicjalizację wyrażenia regularnego poza pętlą. Być może jako statyczna właściwość tylko do odczytu.
Miha Markic

12

Nie trzeba określać transformacji ani mieć zwariowanych ścieżek podkatalogów. Po wielu problemach wyodrębniłem go z tej „prostej” reguły (czy to błąd?) ...

Jeśli ścieżka pakietu nie zaczyna się od względnego katalogu głównego uwzględnionych elementów, katalog główny aplikacji internetowej nie zostanie uwzględniony.

Brzmi dla mnie bardziej jak błąd, ale w każdym razie tak to naprawiasz w obecnej wersji .NET 4.51. Być może inne odpowiedzi były konieczne na starszych kompilacjach ASP.NET, nie mogę powiedzieć, że nie mam czasu na retrospektywne testowanie tego wszystkiego.

Aby to wyjaśnić, oto przykład:

Mam te pliki ...

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

Następnie skonfiguruj pakiet jak ...

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

I renderuj to jak ...

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

I dostańmy „zachowanie” (błąd), same pliki CSS mają katalog główny aplikacji (np. „Http: // localhost: 1234 / MySite / Content / Site.css”), ale obraz CSS na początku ”/ Content / Images / ... ”lub„ / Images / ... ”w zależności od tego, czy dodam transformację, czy nie.

Próbowałem nawet utworzyć folder „Pakiety”, aby sprawdzić, czy ma to związek z istniejącą ścieżką, czy nie, ale to nic nie zmieniło. Rozwiązaniem problemu jest tak naprawdę wymóg, że nazwa pakietu musi zaczynać się od katalogu głównego ścieżki.

Oznacza to, że ten przykład został naprawiony przez zarejestrowanie i renderowanie ścieżki pakietu jak ...

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

Oczywiście można powiedzieć, że to RTFM, ale jestem pewien, że ja i inni wybraliśmy tę ścieżkę „~ / Bundles / ...” z domyślnego szablonu lub gdzieś w dokumentacji na stronie MSDN lub ASP.NET, lub natknąłem się na to, ponieważ w rzeczywistości jest to dość logiczna nazwa wirtualnej ścieżki i sensowne jest wybranie takich wirtualnych ścieżek, które nie kolidują z prawdziwymi katalogami.

W każdym razie tak właśnie jest. Microsoft nie widzi błędu. Nie zgadzam się z tym, albo powinno działać zgodnie z oczekiwaniami, albo powinien zostać zgłoszony wyjątek, lub dodatkowe zastąpienie dodawania ścieżki pakietu, która decyduje się na włączenie katalogu głównego aplikacji, czy też nie. Nie mogę sobie wyobrazić, dlaczego nikt nie chciałby uwzględniać katalogu głównego aplikacji, gdy taki był (zwykle, chyba że zainstalowałeś witrynę z aliasem DNS / domyślnym katalogiem głównym witryny). Więc tak naprawdę powinno to być ustawienie domyślne.


Wydaje mi się najprostszym „rozwiązaniem”. Inne mogą mieć skutki uboczne, na przykład w przypadku image: data.
Fabrice

@MohamedEmaish to działa, prawdopodobnie coś jest nie tak. Dowiedz się, jak śledzić żądania, np. Użyj narzędzia Fiddler Tool, aby zobaczyć, które adresy URL są żądane przez przeglądarkę. Celem nie jest zakodowanie na stałe całej ścieżki względnej, aby witryna mogła zostać zainstalowana w różnych lokalizacjach (ścieżkach katalogu głównego) na tym samym serwerze lub twój produkt może zmienić domyślny adres URL bez konieczności ponownego pisania dużej części strony (punkt posiadania i główna zmienna aplikacji).
Tony Wall

Poszedłem z tą opcją i działało świetnie. Musiałem upewnić się, że każdy pakiet zawiera tylko elementy z jednego folderu (nie może zawierać elementów z innych folderów lub podfolderów), co jest nieco denerwujące, ale o ile działa, jestem szczęśliwy! Dzięki za post.
hvaughan3

1
Dzięki. Westchnienie. Pewnego dnia chciałbym poświęcić więcej czasu na pisanie kodu niż przeglądanie stosu.
Bruce Pierson,

Miałem podobny problem z niestandardowym jquery-ui, który zagnieżdżał foldery. jak tylko wyrównałem rzeczy jak wyżej, zadziałało. Nie lubi zagnieżdżonych folderów.
Andrei Bazanov

11

Odkryłem, że CssRewriteUrlTransform nie działa, jeśli odwołujesz się do *.csspliku i masz powiązany *.min.cssplik w tym samym folderze.

Aby to naprawić, usuń *.min.cssplik lub odwołaj się bezpośrednio do niego w pakiecie:

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

Po wykonaniu tej czynności adresy URL zostaną poprawnie przekształcone, a obrazy powinny zostać poprawnie rozwiązane.


1
Dziękuję Ci! Po dwóch dniach wyszukiwania w Internecie jest to pierwsza wzmianka o CssRewriteUrlTransform pracującej z plikami * .css, ale nie ze skojarzonym plikiem * .min.css, który jest pobierany, gdy nie uruchamiasz debugowania środowisko. Zdecydowanie wydaje mi się to błędem. Będę musiał ręcznie sprawdzić typ środowiska, aby zdefiniować pakiet z nieuprawnioną wersją do debugowania, ale przynajmniej mam teraz obejście!
Sean

1
To rozwiązało problem. To z pewnością wydaje się błędem. Nie ma sensu ignorować CssRewriteUrlTransform, jeśli znajdzie wcześniej istniejący plik .min.css.
user1751825,

10

Być może jestem stronniczy, ale bardzo podoba mi się moje rozwiązanie, ponieważ nie wykonuje żadnych transformacji, wyrażeń regularnych itp. I ma najmniejszą ilość kodu :)

Działa to w przypadku witryny hostowanej jako katalog wirtualny w witrynie sieci Web IIS i jako witryna główna w usługach IIS

Więc stworzyłem Implentację w postaci IItemTransformenkapsulowanej CssRewriteUrlTransformi użyłem VirtualPathUtilitydo naprawy ścieżki i wywołania istniejącego kodu:

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

Wydaje mi się, że działa dobrze?


1
To jest dla mnie idealnie. doskonałe rozwiązanie. mój głos to +1
imdadhusen

1
To jest poprawna odpowiedź. Podana przez środowisko klasa CssUrlTransformWrapper rozwiązuje problem, z tym że nie działa ona tylko wtedy, gdy aplikacja nie znajduje się w katalogu głównym witryny. To opakowanie zwięźle rozwiązuje ten problem.
Dziewięć ogonów

7

Chociaż odpowiedź Chrisa Baxtera pomaga w rozwiązaniu pierwotnego problemu, w moim przypadku nie działa, gdy aplikacja jest hostowana w katalogu wirtualnym . Po zbadaniu opcji skończyłem z rozwiązaniem DIY.

ProperStyleBundleklasa zawiera kod zapożyczony z oryginału, CssRewriteUrlTransformaby poprawnie przekształcić ścieżki względne w katalogu wirtualnym. Zgłasza także, jeśli plik nie istnieje i zapobiega zmianie kolejności plików w pakiecie (kod pobrany z 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 ) + ")" ) );
            }
        }
    }
}

Używaj go jak 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
Ładne rozwiązanie, ale nadal nie działa (podobnie jak CssRewriteUrlTransform), jeśli masz identyfikator URI danych w swoim CSS (np. „Data: image / png; base64, ...”). Nie należy zmieniać adresu URL zaczynającego się od „data:” w RebaseUrlToAbsolute ().
miles82

1
@ miles82 Oczywiście! Dzięki za zwrócenie na to uwagi. Zmieniłem RebaseUrlToAbsolute ().
nrodic

6

Od wersji 1.1.0-alpha1 (pakiet przedpremierowy) środowisko korzysta z VirtualPathProvider dostępu do plików zamiast dotykać fizycznego systemu plików.

Zaktualizowany transformator można zobaczyć poniżej:

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

W rzeczywistości to robi, jeśli zastępuje względne adresy URL w CSS bezwzględnymi.
Fabrice,

6

Oto transformacja pakietu, która zastąpi adresy URL css adresami URL względem tego pliku css. Po prostu dodaj go do swojego pakietu i powinno to rozwiązać problem.

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

}

Jak go używać ?, Pokazuje mi wyjątek:cannot convert type from BundleFile to FileInfo
Stiger

@Stiger zmień css.FullName.Replace (na css.VirtualFile.VirtualPath.Replace (
lkurylo

Być może używam tego źle, ale czy ten przepis przepisuje wszystkie adresy URL przy każdej iteracji i pozostawia je względem ostatniego pliku css, który widział?
Andyrooger,

4

Inną opcją byłoby użycie modułu IIS URL Rewrite do mapowania folderu obrazu pakietu wirtualnego na folder obrazu fizycznego. Poniżej znajduje się przykład reguły przepisywania, której można użyć dla pakietu o nazwie „~ / bundles / yourpage / styles” - zwróć uwagę na dopasowania wyrażenia regularnego znaków alfanumerycznych, a także łączników, podkreślników i kropek, które są powszechne w nazwach plików obrazów .

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

Takie podejście powoduje dodatkowe obciążenie, ale pozwala mieć większą kontrolę nad nazwami pakietów, a także zmniejsza liczbę pakietów, do których możesz odwoływać się na jednej stronie. Oczywiście, jeśli musisz odwoływać się do wielu plików css innych firm, które zawierają względne odwołania do ścieżki obrazu, nadal nie możesz obejść się przy tworzeniu wielu pakietów.


4

Rozwiązanie Grinn jest świetne.

Jednak nie działa dla mnie, gdy w adresie URL znajdują się odnośniki względne do folderu nadrzędnego. to znaczyurl('../../images/car.png')

Więc nieznacznie zmieniłem Include metodę, aby rozwiązać ścieżki dla każdego dopasowania wyrażenia regularnego, pozwalając na ścieżki względne, a także opcjonalnie osadzić obrazy w css.

Zmieniłem również IF DEBUG, aby sprawdzić BundleTable.EnableOptimizationszamiast 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;
    }

Mam nadzieję, że to pomaga, pozdrawiam.


2

Możesz po prostu dodać kolejny poziom głębi do ścieżki wirtualnego pakietu

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

Jest to bardzo mało zaawansowana technika i rodzaj hackowania, ale działa i nie wymaga wstępnego przetwarzania. Biorąc pod uwagę długość i złożoność niektórych z tych odpowiedzi, wolę robić to w ten sposób.


Nie pomaga to, gdy masz aplikację sieci Web jako aplikację wirtualną w IIS. Mam na myśli, że może działać, ale musisz nazwać swoją wirtualną aplikację IIS tak, jak w kodzie, co nie jest tym, czego chcesz, prawda?
psulek

Mam ten sam problem, gdy aplikacja jest aplikacją wirtualną w IIS. Ta odpowiedź mi pomaga.
BILL

2

Miałem ten problem z pakietami mającymi niepoprawne ścieżki do obrazów i niepoprawne CssRewriteUrlTransformrozwiązywanie względnych ścieżek nadrzędnych ..(wystąpił również problem z zasobami zewnętrznymi, takimi jak strony internetowe). Właśnie dlatego napisałem tę niestandardową transformację (wydaje się, że wszystkie powyższe poprawnie wykonuję):

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

Edycja: Nie zdawałem sobie z tego sprawy, ale użyłem niestandardowych metod rozszerzenia w kodzie. Ich kod źródłowy to:

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

Oczywiście powinno być możliwe do zastąpienia String.StartsWith(char)z String.StartsWith(string).


Nie mam przeciążenia String.Count (), które akceptuje ciąg znaków ( m.Groups[2].Value.Count("..")nie działa.) I Value.StartsWith('/')nie działa, ponieważ StartsWith oczekuje ciągu zamiast znaku.
jao

@jao my bad Włączyłem do kodu własne metody rozszerzenia, nie zdając sobie z tego sprawy.
jahu

1
@jao dodał do odpowiedzi kod źródłowy tych metod rozszerzenia.
jahu

1

Po krótkim dochodzeniu doszedłem do następujących wniosków: Masz 2 opcje:

  1. iść z transformacjami. Bardzo przydatny pakiet do tego: https://bundletransformer.codeplex.com/ potrzebujesz każdej transformacji dla każdego problematycznego pakietu:

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

Zalety: w tym rozwiązaniu możesz nazwać swój pakiet, jak chcesz => możesz łączyć pliki css w jeden pakiet z różnych katalogów. Wady: Musisz przekształcić każdy problematyczny pakiet

  1. Użyj tego samego względnego katalogu głównego dla nazwy pakietu, tak jak w przypadku lokalizacji pliku css. Zalety: nie ma potrzeby transformacji. Wady: masz ograniczenia w łączeniu arkuszy css z różnych katalogów w jeden pakiet.

0

CssRewriteUrlTransformnaprawiono mój problem.
Jeśli po użyciu kod nadal nie ładuje obrazów CssRewriteUrlTransform, zmień nazwę pliku css z:

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

Do:

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

W jakiś sposób. (Kropki) nie są rozpoznawane w adresie URL.


0

Pamiętaj tylko, aby naprawić wiele wtrąceń CSS w pakiecie, takich jak:

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

Nie można po prostu dodać new CssRewriteUrlTransform()do końca, tak jak można to zrobić za pomocą jednego pliku CSS, ponieważ metoda go nie obsługuje, dlatego należy użyć Includewiele razy :

bundles.Add(new StyleBundle("~/Content/styles/jquery-ui")
    .Include("~/Content/css/path1/somestyle1.css", new CssRewriteUrlTransform())
    .Include("~/Content/css/path2/somestyle2.css", new CssRewriteUrlTransform()));
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.