MVC4 StyleBundle tidak menyelesaikan gambar


293

Pertanyaan saya mirip dengan ini:

ASP.NET MVC 4 Minifikasi & Gambar Latar Belakang

Kecuali saya ingin tetap menggunakan bundling MVC sendiri jika saya bisa. Saya mengalami crash otak mencoba mencari tahu apa pola yang benar untuk menentukan bundel gaya sehingga css mandiri dan set gambar seperti pekerjaan jQuery UI.

Saya memiliki struktur situs MVC khas /Content/css/yang berisi CSS basis saya seperti styles.css. Di dalam folder css itu saya juga memiliki subfolder seperti /jquery-uiyang berisi file CSS plus /imagesfolder. Jalur gambar di jQuery UI CSS relatif terhadap folder itu dan saya tidak ingin mengacaukannya.

Seperti yang saya mengerti, ketika saya menentukan StyleBundlesaya perlu menentukan jalur virtual yang juga tidak cocok dengan jalur konten nyata, karena (dengan asumsi saya mengabaikan rute ke Konten) IIS kemudian akan mencoba untuk menyelesaikan jalur itu sebagai file fisik. Jadi saya menetapkan:

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

dirender menggunakan:

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

Saya bisa melihat permintaan keluar ke:

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

Ini mengembalikan respons CSS yang benar dan diperkecil. Tetapi kemudian browser mengirimkan permintaan untuk gambar yang relatif terhubung sebagai:

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

Yang merupakan 404.

Saya mengerti bahwa bagian terakhir dari URL saya jquery-uiadalah URL tanpa ekstensi, penangan untuk bundel saya, jadi saya bisa melihat mengapa permintaan relatif untuk gambar itu sederhana /styles/images/.

Jadi pertanyaan saya adalah apa cara yang benar dalam menangani situasi ini?


9
setelah merasa frustrasi berulang kali dengan bagian Bundling dan Minifikasi yang baru, saya pindah ke Cassete penyihir sekarang bebas dan bekerja lebih baik!
balexandre

3
Terima kasih atas tautannya, Cassette terlihat bagus dan saya pasti akan memeriksanya. Tapi saya ingin tetap menggunakan pendekatan yang disediakan jika memungkinkan, tentunya ini harus dimungkinkan tanpa mengacaukan jalur gambar di file CSS pihak ke-3 setiap kali versi baru dirilis. untuk saat ini saya telah menyimpan ScriptBundles saya (yang berfungsi dengan baik) tetapi dikembalikan ke tautan CSS biasa hingga saya mendapatkan resolusi. Bersulang.
Tom W Hall

Menambahkan kemungkinan kesalahan untuk alasan SEO: Pengontrol untuk path '/bundles/images/blah.jpg' tidak ditemukan atau tidak menerapkan IController.
Luke Puplett

Jawaban:


361

Menurut utas ini pada bundel MVC4 dan referensi gambar , jika Anda mendefinisikan bundel Anda sebagai:

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

Di mana Anda menentukan bundel di jalur yang sama dengan file sumber yang membentuk bundel, jalur gambar relatif masih akan berfungsi. Bagian terakhir dari jalur bundel sebenarnya adalah file nameuntuk bundel spesifik tersebut (yaitu, /bundlebisa berupa nama apa pun yang Anda suka).

Ini hanya akan berfungsi jika Anda menggabungkan CSS dari folder yang sama (yang menurut saya masuk akal dari perspektif bundling).

Memperbarui

Sesuai komentar di bawah ini oleh @ Hua Kung, atau ini sekarang dapat dicapai dengan menerapkan CssRewriteUrlTransformation( Ubah referensi URL relatif ke file CSS saat dibundel ).

CATATAN: Saya belum mengonfirmasi komentar mengenai masalah penulisan ulang ke jalur absolut dalam direktori virtual, jadi ini mungkin tidak berfungsi untuk semua orang (?).

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

1
Legenda! Yap, itu bekerja dengan sempurna. Saya memiliki CSS pada level yang berbeda tetapi masing-masing memiliki folder gambar sendiri, mis. CSS situs utama saya ada di folder CSS root dan kemudian jquery-ui ada di dalamnya dengan folder gambar sendiri, jadi saya hanya menentukan 2 bundel, satu untuk saya CSS dasar dan satu untuk jQuery UI - yang mungkin tidak optimal dalam hal permintaan, tetapi umurnya pendek. Bersulang!
Tom W Hall

3
Ya sayangnya sampai bundling memiliki dukungan untuk menulis ulang url tertanam di dalam css itu sendiri, Anda memerlukan direktori virtual bundel css untuk mencocokkan file css sebelum bundling. Inilah mengapa bundel templat default tidak memiliki url seperti ~ / bundel / tema, dan sebaliknya terlihat seperti struktur direktori: ~ / content / theemes / base / css
Hao Kung

27
Ini sekarang didukung melalui ItemTransforms, .Include ("~ / Content / css / jquery-ui / *. Css", CssRewriteUrlTransform ()) baru; di 1.1Beta1 harus memperbaiki masalah ini
Hao Kung

2
Apakah ini diperbaiki di Microsoft ASP.NET Web Optimization Framework 1.1.3? Saya belum menemukan informasi tentang apa yang diubah dalam hal ini?
Andrus

13
CssRewriteUrlTransform () baru baik-baik saja jika Anda memiliki situs web di IIS. tetapi jika ini merupakan aplikasi atau sub aplikasi, ini tidak akan berfungsi, dan Anda harus menggunakan bundel di lokasi yang sama dengan CSS Anda.
Avidenic

34

Solusi Grinn / ThePirat bekerja dengan baik.

Saya tidak suka bahwa itu baru metode Sertakan pada bundel, dan itu membuat file sementara di direktori konten. (mereka akhirnya diperiksa, dikerahkan, maka layanan tidak akan dimulai!)

Jadi untuk mengikuti desain Bundling, saya memilih untuk melakukan kode yang pada dasarnya sama, tetapi dalam implementasi 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);
            }
        }
    }
}

Dan kemudian membungkusnya dalam 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());
    }
}

Penggunaan sampel:

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

Inilah metode ekstensi saya untuk 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(@"\", "/");
    }

Ini tampaknya terbersih untuk saya juga. Terima kasih. Saya memilih kalian bertiga karena ini terlihat sebagai upaya tim. :)
Josh Mouch

Kode yang Anda miliki sekarang tidak berfungsi untuk saya. Saya mencoba memperbaikinya, tetapi saya pikir saya akan memberi tahu Anda. Metode context.HttpContext.RelativeFromAbsolutePath tidak ada. Juga, jika jalur url dimulai dengan "/" (menjadikannya mutlak), jalur Anda yang menggabungkan logika tidak aktif.
Josh Mouch

2
@AcidPAT kerja bagus. Logikanya gagal jika url memiliki querystring (beberapa perpustakaan pihak ke-3 menambahkannya, seperti FontAwesome untuk referensi .woff.) Namun, ini adalah perbaikan yang mudah. Seseorang dapat menyesuaikan Regex atau memperbaiki relativeToCSSsebelum memanggil Path.GetFullPath().
sergiopereira

2
@ChrisMarisic kode Anda sepertinya tidak berfungsi - response.Files adalah array dari BundleFiles, objek ini tidak memiliki properti seperti "Ada", "DirectoryName" dll.
Nick Coad

2
@ ChrisMarisic apakah mungkin ada namespace yang harus saya impor yang menyediakan metode ekstensi untuk kelas BundleFile?
Nick Coad

20

Lebih baik lagi (IMHO) menerapkan Bundel kustom yang memperbaiki jalur gambar. Saya menulis satu untuk aplikasi saya.

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

}

Untuk menggunakannya, lakukan:

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

...dari pada...

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

Apa yang dilakukannya adalah (ketika tidak dalam mode debug) mencari url(<something>)dan menggantinya dengan url(<absolute\path\to\something>). Saya menulisnya sekitar 10 detik yang lalu jadi mungkin perlu sedikit penyesuaian. Saya telah memperhitungkan URL dan data base64 yang sepenuhnya berkualifikasi dengan memastikan tidak ada titik dua (:) di jalur URL. Di lingkungan kami, gambar biasanya berada di folder yang sama dengan file css mereka, tetapi saya telah mengujinya dengan folder induk ( url(../someFile.png)) dan folder anak ( url(someFolder/someFile.png).


Ini solusi hebat. Saya memodifikasi Regex Anda sedikit sehingga itu juga akan berfungsi dengan file KURANG, tetapi konsep asli persis apa yang saya butuhkan. Terima kasih.
Tim Coulter

Anda mungkin meletakkan inisialisasi regex di luar loop juga. Mungkin sebagai properti hanya baca statis.
Miha Markic

12

Tidak perlu menentukan transformasi atau memiliki jalur subdirektori gila. Setelah banyak pemecahan masalah saya mengisolasinya ke aturan "sederhana" ini (apakah ini bug?) ...

Jika jalur bundel Anda tidak dimulai dengan root relatif dari item yang dimasukkan, maka root aplikasi web tidak akan diperhitungkan.

Kedengarannya seperti lebih banyak bug bagi saya, tapi toh itu cara Anda memperbaikinya dengan versi .NET 4.51 saat ini. Mungkin jawaban lain diperlukan pada ASP.NET build yang lebih lama, tidak dapat mengatakan tidak punya waktu untuk menguji semua itu secara retrospektif.

Untuk memperjelas, berikut adalah contohnya:

Saya punya file-file ini ...

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

Kemudian atur bundel seperti ...

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

Dan membuatnya seperti ...

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

Dan dapatkan "perilaku" (bug), file CSS itu sendiri memiliki root aplikasi (mis. "Http: // localhost: 1234 / MySite / Content / Site.css") tetapi gambar CSS dalam semua mulai "/ Konten / Gambar / ... "atau" / Gambar / ... "tergantung pada apakah saya menambahkan transformasi atau tidak.

Bahkan mencoba membuat folder "Bundel" untuk melihat apakah itu ada hubungannya dengan path yang ada atau tidak, tetapi itu tidak mengubah apa pun. Solusi untuk masalah ini adalah persyaratan bahwa nama bundel harus dimulai dengan path root.

Berarti contoh ini diperbaiki dengan mendaftarkan dan merender bundle path seperti ..

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

Jadi tentu saja Anda bisa mengatakan ini adalah RTFM, tetapi saya cukup yakin saya dan orang lain mengambil jalur "~ / Bundel / ..." ini dari templat default atau di suatu tempat dalam dokumentasi di situs web MSDN atau ASP.NET, atau hanya menemukan itu karena sebenarnya itu adalah nama yang cukup logis untuk jalur virtual dan masuk akal untuk memilih jalur virtual seperti itu yang tidak bertentangan dengan direktori nyata.

Bagaimanapun, begitulah adanya. Microsoft tidak melihat bug. Saya tidak setuju dengan ini, apakah itu harus berfungsi seperti yang diharapkan atau beberapa pengecualian harus dilemparkan, atau tambahan tambahan untuk menambahkan jalur bundel yang memilih untuk menyertakan root aplikasi atau tidak. Saya tidak bisa membayangkan mengapa ada orang yang tidak ingin root aplikasi dimasukkan ketika ada (biasanya kecuali Anda menginstal situs web Anda dengan alias DNS / root situs web default). Jadi sebenarnya itu yang seharusnya menjadi standar.


Menurut saya "solusi" paling sederhana. Yang lain mungkin memiliki efek samping, seperti dengan gambar: data.
Fabrice

@MohamedEmaish tidak berfungsi, Anda mungkin salah. Pelajari cara melacak permintaan, misalnya menggunakan Alat Fiddler untuk melihat URL apa yang diminta oleh browser. Tujuannya bukan untuk membuat hard-code seluruh jalur relatif sehingga situs web Anda dapat diinstal di lokasi yang berbeda (jalur root) pada server yang sama atau produk Anda dapat mengubah URL default tanpa harus menulis ulang banyak situs web (titik memiliki dan variabel root aplikasi).
Tony Wall

Pergi dengan opsi ini dan itu bekerja dengan baik. Harus memastikan setiap bundel hanya memiliki item dari satu folder (tidak dapat menyertakan item dari folder lain atau subfolder), yang sedikit mengganggu tetapi selama itu berfungsi saya senang! Terima kasih untuk kirimannya.
hvaughan3

1
Terima kasih. Mendesah. Suatu hari saya ingin menghabiskan lebih banyak waktu untuk benar-benar menulis kode daripada menjelajahi Stack.
Bruce Pierson

Saya punya masalah serupa di mana custom jquery-ui yang memiliki folder bersarang. segera setelah saya naik level seperti di atas, itu berhasil. Itu tidak suka folder bersarang.
Andrei Bazanov

11

Saya menemukan bahwa CssRewriteUrlTransform gagal dijalankan jika Anda mereferensikan *.cssfile dan Anda memiliki *.min.cssfile terkait di folder yang sama.

Untuk memperbaikinya, hapus *.min.cssfile atau rujuk langsung dalam bundel Anda:

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

Setelah itu, URL Anda akan ditransformasikan dengan benar dan gambar Anda harus diselesaikan dengan benar.


1
Terima kasih! Setelah dua hari mencari online, ini adalah penyebutan pertama yang saya lihat di mana saja dari CssRewriteUrlTransform bekerja dengan file * .css, tetapi tidak dengan file * .min.css terkait yang ditarik ketika Anda tidak menjalankan debug lingkungan Hidup. Jelas bagaikan bug bagi saya. Harus secara manual memeriksa jenis lingkungan untuk menentukan bundel dengan versi yang belum ditentukan untuk debugging, tapi setidaknya saya punya solusi sekarang!
Sean

1
Ini memperbaiki masalah bagi saya. Ini sepertinya seperti bug. Tidak masuk akal bahwa ia harus mengabaikan CssRewriteUrlTransform jika menemukan file .min.css yang sudah ada sebelumnya.
user1751825

10

Mungkin saya bias, tapi saya cukup suka solusi saya karena tidak melakukan transformasi, dll regex dan memiliki jumlah kode paling sedikit :)

Ini berfungsi untuk situs yang dihosting sebagai Direktori Virtual di Situs Web IIS dan sebagai situs web root di IIS

Jadi saya membuat Implentasi dari IItemTransformenkapsulasi CssRewriteUrlTransformdan digunakan VirtualPathUtilityuntuk memperbaiki path dan memanggil kode yang ada:

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

Tampaknya bekerja dengan baik untuk saya?


1
Ini sangat cocok untuk saya. solusi terbaik suara saya +1
imdadhusen

1
Ini jawaban yang benar. Kelas CssUrlTransformWrapper yang disediakan oleh framework mengatasi masalah, kecuali itu tidak bekerja hanya ketika aplikasi tidak di root situs web. Pembungkus ini dengan singkat membahas kekurangan itu.
Sembilan Ekor

7

Meskipun jawaban Chris Baxter membantu dengan masalah asli, itu tidak berfungsi dalam kasus saya ketika aplikasi di-host di direktori virtual . Setelah menyelidiki opsi, saya selesai dengan solusi DIY.

ProperStyleBundlekelas termasuk kode yang dipinjam dari aslinya CssRewriteUrlTransformuntuk benar mengubah jalur relatif dalam direktori virtual. Itu juga melempar jika file tidak ada dan mencegah pemesanan ulang file dalam bundel (kode diambil dari 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 ) + ")" ) );
            }
        }
    }
}

Gunakan seperti 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
Solusi yang bagus, tetapi masih gagal (seperti CssRewriteUrlTransform) jika Anda memiliki data URI di CSS Anda (mis. "Data: image / png; base64, ..."). Anda seharusnya tidak mengubah url yang dimulai dengan "data:" di RebaseUrlToAbsolute ().
miles82

1
@ miles82 Tentu saja! Terima kasih telah menunjukkan ini. Saya telah mengubah RebaseUrlToAbsolute ().
nrodic

6

Pada v1.1.0-alpha1 (paket pra rilis) kerangka kerja ini menggunakan VirtualPathProvideruntuk mengakses file daripada menyentuh sistem file fisik.

Transformator yang diperbarui dapat dilihat di bawah:

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

Sebenarnya, apa yang dilakukan jika mengganti URL relatif di CSS dengan yang absolut.
Fabrice

6

Berikut adalah Bundel Transform yang akan mengganti url css dengan url relatif terhadap file css. Cukup tambahkan ke bundel Anda dan itu akan memperbaiki masalah.

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

}

Cara menggunakannya?, Ini menunjukkan kepada saya pengecualian:cannot convert type from BundleFile to FileInfo
Stiger

@Stiger ubah css.FullName.Replace (ke css.VirtualFile.VirtualPath.Replace (
lkurylo

Saya mungkin menggunakan ini salah, tetapi apakah itu terlebih dahulu menulis ulang semua url pada setiap iterasi dan membiarkannya relatif terhadap file css terakhir yang dilihatnya?
Andyrooger

4

Pilihan lain adalah menggunakan modul Penulisan Ulang URL IIS untuk memetakan folder gambar bundel virtual ke folder gambar fisik. Di bawah ini adalah contoh aturan penulisan ulang yang dapat Anda gunakan untuk bundel yang disebut "~ / bundel / halaman Anda / gaya" - perhatikan pencocokan regex pada karakter alfanumerik serta tanda hubung, garis bawah dan periode, yang umum dalam nama file gambar .

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

Pendekatan ini menciptakan sedikit overhead tambahan, tetapi memungkinkan Anda untuk memiliki kontrol lebih besar atas nama bundel Anda, dan juga mengurangi jumlah bundel yang mungkin harus Anda rujuk pada satu halaman. Tentu saja, jika Anda harus mereferensi banyak file css pihak ke-3 yang berisi referensi path gambar relatif, Anda masih tidak bisa membuat banyak bundel.


4

Solusi Grinn sangat bagus.

Namun itu tidak berfungsi untuk saya ketika ada referensi relatif folder induk di url. yaituurl('../../images/car.png')

Jadi, saya sedikit mengubah Includemetode untuk menyelesaikan jalur untuk setiap pertandingan regex, memungkinkan jalur relatif dan juga secara opsional menanamkan gambar dalam css.

Saya juga mengubah IF DEBUG untuk memeriksa BundleTable.EnableOptimizationsbukan 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;
    }

Semoga ini bisa membantu, salam.


2

Anda bisa menambahkan level kedalaman lain ke jalur bundel virtual Anda

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

Ini adalah jawaban yang sangat berteknologi rendah dan jenis peretasan tetapi berhasil dan tidak memerlukan pra-pemrosesan. Mengingat panjang dan rumitnya beberapa jawaban ini saya lebih suka melakukannya dengan cara ini.


Ini tidak membantu ketika Anda memiliki aplikasi web Anda sebagai aplikasi virtual di IIS. Maksud saya itu bisa bekerja tetapi Anda harus memberi nama aplikasi virtual IIS Anda seperti dalam kode Anda, yang bukan yang Anda inginkan, bukan?
psulek

Saya memiliki masalah yang sama ketika aplikasi adalah aplikasi virtual di IIS. Jawaban ini membantu saya.
BILL

2

Saya punya masalah dengan bundel yang memiliki jalur yang salah ke gambar dan CssRewriteUrlTransformtidak menyelesaikan jalur induk relatif ..dengan benar (ada juga masalah dengan sumber daya eksternal seperti webfonts). Itulah sebabnya saya menulis transformasi ubahsuaian ini (tampaknya melakukan semua hal di atas dengan benar):

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

Sunting: Saya tidak menyadarinya, tetapi saya menggunakan beberapa metode ekstensi khusus dalam kode. Kode sumbernya adalah:

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

Tentu saja itu harus mungkin untuk menggantikan String.StartsWith(char)dengan String.StartsWith(string).


Saya tidak memiliki kelebihan String.Count () yang menerima string ( m.Groups[2].Value.Count("..")tidak berfungsi.) Dan Value.StartsWith('/')tidak berfungsi karena StartsWith mengharapkan string, bukan char.
jao

@ Joo salahku saya memasukkan metode ekstensi saya sendiri dalam kode tanpa menyadarinya.
Jahu

1
@jao menambahkan kode sumber metode ekstensi itu ke jawabannya.
Jahu

1

Setelah sedikit penyelidikan saya menyimpulkan yang berikut: Anda memiliki 2 pilihan:

  1. pergi dengan transformasi. Paket yang sangat berguna untuk ini: https://bundletransformer.codeplex.com/ Anda perlu transformasi berikut untuk setiap bundel yang bermasalah:

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

Keuntungan: dari solusi ini, Anda dapat memberi nama bundel Anda apa pun yang Anda inginkan => Anda dapat menggabungkan file css menjadi satu bundel dari direktori yang berbeda. Kekurangan: Anda perlu mengubah setiap bundel yang bermasalah

  1. Gunakan root relatif yang sama untuk nama bundel seperti di mana file css berada. Keuntungan: tidak perlu transformasi. Kekurangan: Anda memiliki keterbatasan dalam menggabungkan lembaran css dari direktori yang berbeda menjadi satu bundel.

0

CssRewriteUrlTransformmemperbaiki masalah saya.
Jika kode Anda masih tidak memuat gambar setelah menggunakan CssRewriteUrlTransform, maka ubah nama file css Anda dari:

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

Untuk:

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

Someway. (Titik) tidak mengenali di url.


0

Ingatlah untuk memperbaiki beberapa inklusi CSS dalam satu bundel seperti:

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

Anda tidak bisa hanya menambahkan new CssRewriteUrlTransform()sampai akhir seperti yang Anda bisa dengan satu file CSS karena metode ini tidak mendukungnya, jadi Anda harus menggunakan Includebeberapa kali :

bundles.Add(new StyleBundle("~/Content/styles/jquery-ui")
    .Include("~/Content/css/path1/somestyle1.css", new CssRewriteUrlTransform())
    .Include("~/Content/css/path2/somestyle2.css", new CssRewriteUrlTransform()));
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.