MVC4 StyleBundle görüntüleri çözmüyor


293

Benim sorum şuna benzer:

ASP.NET MVC 4 Minyatür ve Arka Plan Resimleri

Bunun dışında MVC'nin kendi paketlemesine bağlı kalmak istiyorum. Bağımsız css ve jQuery UI çalışma gibi görüntü kümeleri gibi stil demetleri belirtmek için doğru desen ne olduğunu anlamaya çalışan bir beyin kazası yaşıyorum.

Benim /Content/css/gibi temel CSS içeren tipik bir MVC site yapısı var styles.css. Bu css klasörü içinde ayrıca /jquery-uiCSS dosyasını artı bir/images klasörü . JQuery UI CSS görüntü yolları bu klasöre göreli ve onlarla karıştırmak istemiyorum.

Anladığım kadarıyla, StyleBundlebir belirlediğimde, gerçek bir içerik yoluyla da eşleşmeyen bir sanal yol belirtmem gerekiyor, çünkü (İçeriğe giden yolları görmezden geldiğimi varsayarak) IIS bu yolu fiziksel bir dosya olarak çözmeye çalışacaktır. Bu yüzden şunu belirtiyorum:

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

kullanılarak render:

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

İsteğin şu adrese gittiğini görebiliyorum:

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

Bu doğru, küçültülmüş CSS yanıtı döndürüyor. Ancak tarayıcı, nispeten bağlantılı bir resim için şu şekilde bir istek gönderir:

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

Hangi bir 404.

URL'imin son bölümünün jquery-uiuzantısız bir URL, paketim için bir işleyici olduğunu anlıyorum, böylece resmin göreli isteğinin neden basit olduğunu anlayabiliyorum /styles/images/.

Benim sorum şu , bu durumla başa çıkmanın doğru yolu nedir?


9
sonra hayal kırıklığına uğramış tekrar tekrar ve yeni Bundling ve Minification bölümüyle, ben geçti cassete cadı şimdi ücretsiz ve daha iyi şekilde çalışır!
balexandre

3
Bağlantı için teşekkürler, Kaset güzel görünüyor ve kesinlikle kontrol edeceğim. Ancak mümkünse sağlanan yaklaşıma bağlı kalmak istiyorum, yeni bir sürüm her yayınlandığında 3. taraf CSS dosyalarındaki görüntü yollarıyla uğraşmadan mutlaka mümkün olmalıdır. Şimdilik ScriptBundles'ımı (güzel çalışıyor) tuttum ama bir çözüm alana kadar düz CSS bağlantılarına geri döndüm. Şerefe.
Tom W Hall

SEO nedeniyle olası hatayı ekleme: '/bundles/images/blah.jpg' yolu için denetleyici bulunamadı veya IController'ı uygulamaz.
Luke Puplett

Yanıtlar:


361

MVC4 css paketlemesi ve görüntü referanslarındaki bu konuya göre , paketinizi şöyle tanımlarsanız:

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

Paketi, paketi oluşturan kaynak dosyalarla aynı yolda tanımladığınızda, göreli görüntü yolları çalışmaya devam eder. Paket yolunun son kısmı, söz konusu paket için gerçekten file name(yani, /bundleistediğiniz herhangi bir ad olabilir).

Bu, yalnızca aynı klasörden CSS'yi birlikte paketliyorsanız işe yarayacaktır (bence bir demetleme perspektifinden mantıklıdır).

Güncelleme

@Hao Kung tarafından yapılan aşağıdaki açıklamaya göre, alternatif olarak bu şimdi CssRewriteUrlTransformation( Paketlendiğinde CSS dosyalarına göreli URL referanslarını değiştir ) uygulanarak gerçekleştirilebilir.

NOT: Sanal bir dizin içindeki mutlak yollara yeniden yazma ile ilgili sorunlar hakkında yorum onaylamadım, bu yüzden bu herkes için çalışmayabilir (?).

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

1
Efsane! Evet, mükemmel çalışıyor. Farklı düzeylerde CSS var ama her birinin kendi görüntü klasörleri var, örneğin ana sitem CSS kök CSS klasöründe ve sonra jquery-ui kendi görüntüler klasörüyle içinde, bu yüzden sadece 2 paket belirtiyorum, biri için baz CSS ve biri jQuery UI için - bu, istekler açısından uber-optimal olmayabilir, ancak ömür kısadır. Şerefe!
Tom W Hall

3
Evet maalesef paketleme, css'in içindeki gömülü URL'leri yeniden yazma desteğine sahip olana kadar, paketlemeden önce css dosyalarıyla eşleşmesi için css paketinin sanal dizinine ihtiyacınız vardır. Bu yüzden varsayılan şablon paketlerinde ~ / bundles / temalar gibi URL'ler yoktur ve bunun yerine dizin yapısına benzer: ~ / content / theemes / base / css
Hao Kung

27
Bu artık ItemTransforms, .Include ("~ / Content / css / jquery-ui / *. Css", yeni CssRewriteUrlTransform ())); 1.Beta1 bu sorunu düzeltmelidir
Hao Kung

2
Bu Microsoft ASP.NET Web Optimization Framework 1.1.3'te düzeltildi mi? Bu konuda neler değiştiği hakkında herhangi bir bilgi buldum?
Andrus

13
IIS'de bir web siteniz varsa yeni CssRewriteUrlTransform () iyi. ama onun bir uygulama veya alt uygulama bu işe yaramazsa, ve sizin CSS ile aynı yerde paket tanımlamak için başvurmak zorunda.
avidenic

34

Grinn / ThePirat çözümü iyi çalışıyor.

Paket üzerindeki Include yöntemini yeni olmasını ve içerik dizininde geçici dosyalar oluşturmasını sevmedim. (check-in yaptılar, konuşlandırıldı, sonra hizmet başlamazdı!)

Bu yüzden Bundling'in tasarımını takip etmek için esasen aynı kodu seçmeyi seçtim, ancak bir IBundleTransform uygulamasında ::

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

Ve sonra bunu bir Paket Uygulamasında tamamladı:

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

Örnek Kullanımı:

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

İşte RelativeFromAbsolutePath için benim uzantısı yöntemi:

   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(@"\", "/");
    }

Bu benim için de en temiz görünüyor. Teşekkürler. Üçünüze de oy veriyorum çünkü takım çalışması gibi görünüyordu. :)
Josh Mouch

Şimdi sahip olduğunuz kod benim için çalışmıyor. Düzeltmeye çalışıyorum, ama sana bildireceğimi düşündüm. Context.HttpContext.RelativeFromAbsolutePath yöntemi mevcut değil. Ayrıca, url yolu bir "/" ile başlıyorsa (mutlak yapar), mantığı birleştiren yolunuz kapalıdır.
Josh Mouch

2
@AcidPAT harika bir iş. Url'de bir sorgu dizesi varsa mantık başarısız oldu (bazı üçüncü taraf kütüphaneleri .woff referansı için FontAwesome gibi ekliyor.) Yine de kolay bir düzeltme. Aramadan relativeToCSSönce Regex'i ayarlayabilir veya düzeltebilirsiniz Path.GetFullPath().
sergiopereira

2
@ChrisMarisic kodunuz çalışmıyor gibi görünüyor - response.Files bir BundleFiles dizisidir, bu nesnenin "Var", "DirectoryName" vb. Özellikleri yoktur
Nick Coad

2
@ChrisMarisic BundleFile sınıfı için uzantı yöntemleri sağlayan belki de almalıyım bir ad alanı var mı?
Nick Coad

20

Daha da iyisi (IMHO), görüntü yollarını düzelten özel bir Paket uygular. Uygulamam için bir tane yazdım.

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

}

Kullanmak için şunları yapın:

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

...onun yerine...

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

Yaptığı şey (hata ayıklama modunda değilken) arar url(<something>)ve onunla değiştirir url(<absolute\path\to\something>). Bir şeyi 10 saniye önce yazdım, bu yüzden biraz tweaking gerekebilir. URL yolunda iki nokta üst üste (:) bulunmadığından emin olarak tam nitelikli URL'leri ve base64 DataURI'leri dikkate aldım. Ortamımızda, görüntüler normalde css dosyalarıyla aynı klasörde bulunur, ancak hem üst klasörler ( url(../someFile.png)) hem de alt klasörler ( ) ile test ettim url(someFolder/someFile.png.


Bu harika bir çözüm. Regex'inizi LESS dosyalarıyla da çalışacak şekilde biraz değiştirdim, ancak orijinal konsept tam olarak ihtiyacım olan şeydi. Teşekkürler.
Tim Coulter

1
Normal ifade başlatmayı döngü dışına da koyabilirsiniz. Belki de statik bir salt okunur özellik olarak.
Miha Markic

12

Bir dönüşüm belirtmek veya çılgın alt dizin yollarına sahip olmak gerekli değildir. Çok sorun giderme sonra ben bu "basit" kural izole (bir hata mı?) ...

Paket yolunuz, dahil edilen öğelerin göreli köküyle başlamazsa, web uygulaması kökü dikkate alınmaz.

Bana bir hata gibi geliyor, ama yine de mevcut .NET 4.51 sürümü ile bu şekilde düzeltir. Belki de eski ASP.NET derlemelerinde diğer yanıtlar gerekliydi, tüm bunları geriye dönük olarak test etmek için zamanınız olmadığını söyleyemeyiz.

Açıklığa kavuşturmak için bir örnek:

Bu dosyalarım var ...

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

Sonra paketi şu şekilde ayarlayın ...

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

Ve şu şekilde render ...

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

Ve "davranış" (hata) olsun, CSS dosyalarının kendileri uygulama köküne sahiptir (örneğin "http: // localhost: 1234 / MySite / Content / Site.css"), ancak tüm içindeki CSS görüntüsü "/ Content / Images / ... "veya" / Images / ... "dönüştürmeyi ekleyip eklemediğime bağlı olarak.

Hatta, varolan yolla ilgili olup olmadığını görmek için "Bundles" klasörünü oluşturmaya çalıştı, ancak bu hiçbir şey değiştirmedi. Sorunun çözümü gerçekten paket adının yol kökünden başlaması gerektiğidir.

Yani bu örnek, paket yolunun kaydedilmesi ve oluşturulmasıyla giderildi.

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

Tabii ki bunun RTFM olduğunu söyleyebilirim, ancak ben ve diğerlerinin bu "~ / Bundles / ..." yolunu varsayılan şablondan veya MSDN veya ASP.NET web sitesindeki belgelerde bir yerden aldığından eminim veya sadece sanal bir yol için oldukça mantıklı bir isim ve gerçek dizinlerle çelişmeyen bu tür sanal yolları seçmek mantıklı çünkü tökezledi.

Her neyse, işte böyle. Microsoft hata görmüyor. Bunu kabul etmiyorum, ya beklendiği gibi çalışmalı ya da bir istisna atılmalı ya da uygulama kökünü eklemeyi ya da eklememeyi seçen paket yolunu eklemek için ek bir geçersiz kılma. Neden kimse (ne zaman web sitenizi bir DNS diğer adı / varsayılan web sitesi kökü ile yüklemediyseniz) uygulama kök neden dahil istemiyorum hayal bile edemiyorum. Yani aslında bu zaten varsayılan olmalı.


Bana en basit "çözüm" gibi geliyor. Diğerleri, görüntü: veriler gibi yan etkilere sahip olabilir.
Fabrice

@MohamedEmaish işe yarıyor, muhtemelen yanlış bir şey var. İsteklerin nasıl izleneceğini öğrenin, örneğin tarayıcı tarafından hangi URL'lerin istendiğini görmek için Fiddler Tool'u kullanın. Amaç, göreli yolu tümüyle kodlamak değildir, böylece web siteniz aynı sunucuda farklı konumlara (kök yollar) yüklenebilir veya ürününüz, web sitesinin çoğunu yeniden yazmak zorunda kalmadan varsayılan URL'yi değiştirebilir (sahip olunan nokta ve uygulama kök değişkeni).
Tony Wall

Bu seçenek ile gitti ve harika çalıştı. Her paketin sadece tek bir klasörden (diğer klasörlerden veya alt klasörlerden öğeler içeremez), biraz can sıkıcı ama çalıştığı sürece mutlu olduğumdan emin olmak zorunda kaldım! Gönderi için teşekkürler.
hvaughan3

1
Teşekkürler. İç çekmek. Bir gün aslında Stack'a göz atmak yerine kod yazmak için daha fazla zaman harcamak istiyorum.
Bruce Pierson

Klasörler iç içe olan özel bir jquery-ui benzer bir sorun vardı. Yukarıdaki gibi şeyleri düzleştirdiğim anda işe yaradı. İç içe klasörleri sevmez.
Andrei Bazanov

11

Bir *.cssdosyaya başvuruyorsanız *.min.cssve aynı klasörde ilişkili dosyaya sahipseniz CssRewriteUrlTransform'un çalışmadığını gördüm .

Bunu düzeltmek için *.min.cssdosyayı silin veya doğrudan paketinizde referans alın:

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

Bundan sonra, URL'leriniz doğru bir şekilde dönüştürülecek ve resimleriniz doğru bir şekilde çözülmelidir.


1
Teşekkür ederim! İki günlük çevrimiçi arama yaptıktan sonra, CssRewriteUrlTransform'un * .css dosyalarıyla çalıştığını, ancak hata ayıklamada çalışmadığınızda çekilen ilişkili * .min.css dosyasıyla çalışmadığımı gördüğüm ilk söz budur. ortamı. Kesinlikle benim için bir hata gibi görünüyor. Hata ayıklama için sonlandırılmamış sürümü olan bir paket tanımlamak için ortam türünü el ile kontrol etmek zorunda kalacak, ancak en azından şimdi bir geçici çözümüm var!
Sean

1
Bu benim için sorunu düzeltti. Bu kesinlikle bir hata gibi görünüyor. Önceden varolan bir .min.css dosyası bulursa, CssRewriteUrlTransform'u yoksayması mantıklı değildir.
user1751825

10

Belki önyargılıyım, ancak herhangi bir dönüşüm, regex vb.Yapmadığı için çözümümü oldukça seviyorum ve en az kod var :)

Bu , IIS Web Sitesinde Sanal Dizin olarak ve IIS'de kök web sitesi olarak barındırılan bir site için çalışır

Bu yüzden IItemTransformkapsüllenmiş bir Implentation oluşturduk ve yolu düzeltmek ve mevcut kodu çağırmak için CssRewriteUrlTransformkullanılır VirtualPathUtility:

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

Benim için iyi çalışıyor gibi görünüyor?


1
Bu benim için mükemmel bir pakettir. mükemmel çözüm. benim
oyum

1
Bu doğru cevap. Çerçeve tarafından sağlanan CssUrlTransformWrapper sınıfı, yalnızca uygulama web sitesi kökünde olmadığında çalışmadığı sürece, sorunu giderir. Bu sarıcı bu eksikliğe kısa sürede cevap veriyor.
Dokuz Kuyruklar

7

Chris Baxter'in cevabı orijinal soruna yardımcı olsa da , uygulama sanal dizinde barındırıldığında benim durumumda çalışmıyor . Seçenekleri araştırdıktan sonra DIY çözümü ile bitirdim.

ProperStyleBundlesınıfı, CssRewriteUrlTransformsanal dizin içindeki göreli yolları düzgün bir şekilde dönüştürmek için orijinalinden ödünç alınan kodu içerir . Dosya yoksa, paketteki dosyaların yeniden sıralanmasını önler (kod alınır 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 ) + ")" ) );
            }
        }
    }
}

Gibi kullanın 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
CSS'nizde bir veri URI'si varsa (örneğin "data: image / png; base64, ...") güzel bir çözümdür, ancak yine de başarısız olur (tıpkı CssRewriteUrlTransform gibi). URL'nin RebaseUrlToAbsolute () içindeki "data:" ile başlamasını değiştirmemelisiniz.
miles82

1
@ miles82 Tabii ki! Bunu işaret ettiğiniz için teşekkürler. RebaseUrlToAbsolute () değiştirdim.
nrodic

6

V1.1.0-alfa1 (ön sürüm paketi) itibariyle çerçeve, VirtualPathProvider fiziksel dosya sistemine dokunmak yerine dosyalara erişmek için .

Güncellenmiş transformatör aşağıda görülebilir:

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

Aslında, CSS'deki göreli URL'leri mutlak olanlarla değiştirirse bunun ne yaptığı.
Fabrice

6

İşte css url'lerini o css dosyasına göre url'lerle değiştirecek bir Paket Dönüşümü. Sadece paketinize ekleyin ve sorunu çözmesi gerekir.

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

}

Nasıl kullanılır ?, Bana bir istisna gösteriyor:cannot convert type from BundleFile to FileInfo
Stiger

@Stiger değişikliği css.FullName.Replace (css.VirtualFile.VirtualPath.Replace için (
lkurylo

Bu yanlış kullanıyor olabilir, ama bu foreach her yineleme tüm URL'leri yeniden yazmak ve gördüğü son css dosyasına göre bırakın?
Andyrooger

4

Başka bir seçenek de sanal paket görüntü klasörünü fiziksel görüntü klasörüne eşlemek için IIS URL Yeniden Yazma modülünü kullanmak olacaktır. Aşağıda, "~ / bundles / yourpage / styles" adlı bir paket için kullanabileceğiniz bir yeniden yazma kuralı örneği verilmiştir - resim dosyası adlarında yaygın olarak kullanılan alfasayısal karakterler ile tire, alt çizgi ve noktalardaki normal ifadeye dikkat edin .

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

Bu yaklaşım biraz fazladan ek yük oluşturur, ancak paket adlarınız üzerinde daha fazla denetime sahip olmanızı sağlar ve ayrıca bir sayfada başvuruda bulunmanız gereken paket sayısını azaltır. Tabii ki, göreli görüntü yolu referansları içeren birden fazla 3. taraf css dosyasına başvurmanız gerekiyorsa, yine de birden fazla paket oluşturmayı başaramazsınız.


4

Grinn çözümü harika.

Ancak, url'de üst klasör göreli referansları olduğunda benim için çalışmaz. yaniurl('../../images/car.png')

Bu nedenle, Includeher regex eşleşmesinin yollarını çözmek için göreceli yollara izin vermek ve ayrıca isteğe bağlı olarak görüntüleri css'e gömmek için yöntemi biraz değiştirdim .

Ayrıca IF DEBUG kontrol etmek değişti BundleTable.EnableOptimizationsyerine 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;
    }

Umarım yardımcı olur, saygılar.


2

Sanal paket yolunuza başka bir derinlik seviyesi daha ekleyebilirsiniz

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

Bu süper düşük teknolojili bir cevap ve bir tür hack'tir, ancak çalışır ve herhangi bir ön işlem gerektirmez. Bu cevapların bazılarının uzunluğu ve karmaşıklığı göz önüne alındığında, bu şekilde yapmayı tercih ederim.


Bu, web uygulamanızın IIS'de sanal uygulama olarak bulunması durumunda yardımcı olmaz. Yani çalışabilir, ancak IIS sanal uygulamanızı kodunuzda olduğu gibi adlandırmanız gerekir, bu istediğiniz şey değildir, değil mi?
psulek

Uygulama IIS'de sanal uygulama olduğunda da aynı sorunu yaşıyorum. Bu cevap bana yardımcı oluyor.
BILL

2

Ben görüntüleri yanlış yolu olan ve CssRewriteUrlTransformgöreceli üst yolları ..doğru çözümlemiyor demetleri ile bu sorun vardı (ayrıca webfonts gibi dış kaynaklarda sorun vardı). Bu yüzden bu özel dönüşümü yazdım (yukarıdakilerin tümünü doğru yapıyor gibi görünüyor):

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

Düzenleme: Fark etmedim, ancak kodda bazı özel uzantı yöntemleri kullandım. Bunların kaynak kodu:

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

Tabii ki değiştirmek mümkün olmalıdır String.StartsWith(char)ile String.StartsWith(string).


Ben ( m.Groups[2].Value.Count("..")çalışmıyor) bir dize kabul bir String.Count () aşırı yok ve Value.StartsWith('/')StartsWith bir karakter yerine bir dize beklediği için ya da çalışmıyor.
jao

@jao benim kötü fark etmeden kendi uzantısı yöntemleri koda dahil.
jahu

1
@jao bu uzantı yöntemlerinin kaynak kodunu cevaba ekledi.
jahu

1

Küçük bir araştırmadan sonra aşağıdakileri tamamladım: 2 seçeneğiniz var:

  1. dönüşümler ile gitmek. Bunun için çok yararlı bir paket: https://bundletransformer.codeplex.com/ her sorunlu paket için aşağıdaki dönüşüme ihtiyacınız var:

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

Avantajları: Bu çözümden, paketinizi istediğiniz gibi adlandırabilirsiniz => css dosyalarını farklı dizinlerden tek bir pakette birleştirebilirsiniz. Dezavantajları: Her sorunlu paketi dönüştürmeniz gerekir

  1. Paketin adı için, css dosyasının bulunduğu yerde olduğu gibi aynı göreceli kökü kullanın. Avantajları: dönüşüme gerek yoktur. Dezavantajları: Farklı dizinlerden css sayfalarını tek bir pakette birleştirme konusunda sınırınız vardır.

0

CssRewriteUrlTransformsorunumu çözdü.
Kodunuz kullandıktan sonra hala resim CssRewriteUrlTransformyüklemiyorsa, css dosya adınızı şuradan değiştirin:

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

Kime:

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

Someway. (Noktalar) url'de tanımıyor.


0

Bir pakette birden fazla CSS kapanımını düzeltmeyi unutmayın :

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

new CssRewriteUrlTransform()Metod desteklemediğinden, tek bir CSS dosyasıyla yapabileceğiniz gibi sonuna kadar ekleyemezsiniz , bu nedenle birden çok kez kullanmanızInclude gerekir :

bundles.Add(new StyleBundle("~/Content/styles/jquery-ui")
    .Include("~/Content/css/path1/somestyle1.css", new CssRewriteUrlTransform())
    .Include("~/Content/css/path2/somestyle2.css", new CssRewriteUrlTransform()));
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.