MVC4 StyleBundle не розділяє зображення


293

Моє запитання подібне до цього:

ASP.NET MVC 4 Мінімізація та фонові зображення

За винятком того, що я хочу дотримуватися власних пакетів MVC, якщо зможу. У мене відбувається збій мозку, намагаючись розібратися, що є правильною схемою для вказівки наборів стилів, таких як окремі набори css та зображень, такі як jQuery UI.

У мене є типова структура сайту MVC, в /Content/css/якій міститься моя базова CSS, наприклад styles.css. У цій папці css у мене також є такі підпапки, /jquery-uiякі містять його CSS-файл плюс /imagesпапку. Шляхи зображення в CSS інтерфейсу jQuery відносяться до цієї папки, і я не хочу з ними возитися.

Як я розумію, коли я вказую, StyleBundleмені потрібно вказати віртуальний шлях, який також не відповідає реальному контенту, тому що (якщо я ігнорую маршрути до Вмісту) IIS намагатиметься вирішити цей шлях як фізичний файл. Тому я уточнюю:

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

надано за допомогою:

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

Я бачу, як запит виходить на:

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

Це повернення правильної, мінімізованої відповіді CSS. Але потім браузер надсилає запит на відносно пов'язане зображення у вигляді:

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

Який а 404.

Я розумію, що остання частина моєї URL-адреси jquery-ui- це URL- адреса без розширення, обробник мого пакету, тому я можу зрозуміти, чому відносний запит на зображення просто /styles/images/.

Тож моє запитання - який правильний спосіб вирішення цієї ситуації?


9
після того, як був розчарований знову і знову новою частиною розшарування та мінімізації, я перейшов до Кассети відьма тепер безкоштовна і працює набагато краще!
balexandre

3
Дякую за посилання, Касета виглядає приємно, і я обов'язково її перевіряю. Але я хочу дотримуватися наданого підходу, якщо це можливо, безумовно, це повинно бути можливим, не псуючи шляхи зображення у сторонніх CSS-файлах щоразу, коли виходить нова версія. поки що я зберігав свої ScriptBundles (які непогано працюють), але повертався до звичайних посилань CSS, поки не отримав дозвіл. Ура.
Том У Хол

Додавання ймовірної помилки з причин SEO: Контролер для шляху '/bundles/images/blah.jpg' не знайдено або не реалізує IController.
Люк Пуплетт

Відповіді:


361

Відповідно до цього потоку в пакеті css MVC4 та посиланнях на зображення , якщо ви визначаєте свій пакет як:

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

Там, де ви визначаєте пакет на тому ж шляху, що і вихідні файли, що складали пакет, відносні шляхи зображення все ще працюватимуть. Остання частина контуру пакету дійсно є file nameдля цього певного пакету (тобто /bundleможе бути будь-яке ім’я).

Це буде працювати лише в тому випадку, якщо ви об'єднуєте CSS з однієї папки (що, на мою думку, має сенс з точки зору пакетування).

Оновлення

Відповідно до коментаря, поданого нижче @Hao Kung, альтернативно, це зараз може бути досягнуто, застосувавши CssRewriteUrlTransformation( Змінити відносні посилання URL-файлів на CSS-файли при комплектації ).

ПРИМІТКА. Я не підтвердив коментарі щодо проблем із переписуванням на абсолютні шляхи у віртуальному каталозі, тому це може не працювати для всіх (?).

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

1
Легенда! Так, це прекрасно працює. У мене є CSS на різних рівнях, але в них є свої папки із зображеннями, наприклад, мій основний CSS-сайт знаходиться в кореневій папці CSS, а потім jquery-ui знаходиться в цій папці із власними папками зображень, тому я просто вказую 2 пакети, один для мого базовий CSS та один для користувальницького інтерфейсу jQuery - це може бути не оптимальним для uber щодо запитів, але життя коротке. Ура!
Том У Хол

3
Так, до тих пір, поки пакет не підтримує перезапис вбудованих URL-адрес всередині самого CSS, вам потрібна віртуальна директорія пакету css, щоб відповідати файлам css перед пакетом. Ось чому пакети шаблонів за замовчуванням не мають URL-адрес на зразок ~ / bundles / themes, а натомість виглядають як структура каталогу: ~ / content / theemes / base / css
Hao Kung

27
Тепер це підтримується через ItemTransforms, .Include ("~ / Content / css / jquery-ui / *. Css", новий CssRewriteUrlTransform ())); в 1.1Beta1 слід виправити це питання
Хао Кунг

2
Чи це виправлено в Microsoft ASP.NET Web Optimization Framework 1.1.3? Я знайшов будь-яку інформацію про те, що в цьому змінено?
Андрус

13
новий CssRewriteUrlTransform () чудово, якщо у вас є веб-сайт в IIS. але якщо його додаток або додатк, це не працюватиме, і вам доведеться вдатися до визначення свого пакета в тому самому місці, що і CSS.
avidenic

34

Рішення Grinn / ThePirat добре працює.

Мені не сподобалось, що це новий метод вбудованого пакету і що він створював тимчасові файли в каталозі вмісту. (в кінцевому підсумку вони були зареєстровані, розгорнуті, тоді служба не запустилася!)

Отже, щоб слідувати дизайну Bundling, я вирішив виконати той самий код, але в реалізації 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);
            }
        }
    }
}

А потім завершив це в реалізацію пакета:

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

Використання зразка:

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

Ось мій метод розширення для 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(@"\", "/");
    }

Це теж видається мені найчистішим. Дякую. Я голосую за всіх вас трьох, бо, здається, це було зусиллям команди. :)
Джош Мауч

Код, як у вас зараз, для мене не працює. Я намагаюся це виправити, але думав, що дам вам знати. Метод контексту.HttpContext.RelativeFromAbsolutePath не існує. Крім того, якщо шлях URL-адреси починається з "/" (що робить його абсолютним), ваш шлях поєднання логіки вимкнено.
Джош Мауч

2
@AcidPAT чудова робота. Логіка не вдалася, якщо в URL-адресі був рядок запитів (деякі бібліотеки сторонніх сторін додають його, як FontAwesome за його довідкою .woff.) Хоча це легко виправити. Ви можете налаштувати Regex або виправити relativeToCSSперед викликом Path.GetFullPath().
sergiopereira

2
@ChrisMarisic ваш код, здається, не працює - response.Files - це масив BundleFiles, у цього об’єкта немає властивостей, таких як "Існує", "DirectoryName" тощо.
Nick Coad

2
@ChrisMarisic чи можливо імпортується простір імен, який забезпечує методи розширення для класу BundleFile?
Нік Код

20

Ще краще (IMHO) реалізувати спеціальний пакет, який фіксує шляхи зображення. Я написав один для свого додатка.

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

}

Щоб використовувати його, виконайте:

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

...замість...

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

Що він робить, це (коли він не знаходиться в режимі налагодження) шукає url(<something>)і замінює його url(<absolute\path\to\something>). Я написав річ близько 10 секунд тому, тому це може знадобитися трохи переробити. Я взяв до уваги повнокваліфіковані URL-адреси та base64 DataURI, переконавшись, що в шляху до URL-адреси немає колонок (:). У нашому середовищі зображення зазвичай знаходяться в тій самій папці, що і їх файли css, але я тестував їх як з батьківськими папками ( url(../someFile.png)), так і з дочірніми папками ( url(someFolder/someFile.png).


Це чудове рішення. Я трохи змінив ваш Regex, щоб він також працював з меншими файлами, але оригінальна концепція була саме тим, що мені потрібно. Дякую.
Тім Култер

Ви також можете поставити ініціалізацію регулярних виражень і поза циклом. Можливо, як статична властивість лише для читання.
Михай Маркич

12

Не потрібно вказувати перетворення або мати шалені підкаталоги. Після довгого усунення несправностей я виділив це це "просте" правило (це помилка?) ...

Якщо ваш шлях зв’язку не починається з відносного кореня елементів, що включаються, корінь веб-додатків не враховуватиметься.

Здається, це більше помилка для мене, але все одно ось як ви виправите це за допомогою поточної версії .NET 4.51. Можливо, інші відповіді були потрібні для старих збірок ASP.NET, не можу сказати, що не встигли ретроспективно перевірити все це.

Для уточнення, ось приклад:

У мене є ці файли ...

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

Потім встановіть пакет, як ...

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

І надайте це як ...

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

І отримайте "поведінку" (помилка), самі файли CSS мають корінь програми (наприклад, "http: // localhost: 1234 / MySite / Content / Site.css"), але зображення CSS у всіх запуску "/ Content / Images / ... "або" / Images / ... "залежно від того, додаю я перетворення чи ні.

Навіть спробував створити папку "Пачки", щоб перевірити, чи це стосується існуючого шляху чи ні, але це нічого не змінило. Рішення проблеми - це насправді вимога, щоб назва пакета починалася з кореня шляху.

Значення цього прикладу виправлено за допомогою реєстрації та надання вікна шляху зв’язку на зразок ..

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

Отже, ви, звичайно, можете сказати, що це RTFM, але я впевнений, що я та інші підбирали цей шлях "~ / Пачки / ..." із шаблону за замовчуванням або десь у документації на веб-сайті MSDN або ASP.NET, або просто натрапили на нього, бо насправді це цілком логічна назва віртуального шляху і має сенс вибирати такі віртуальні шляхи, які не суперечать реальним каталогів.

У всякому разі, це так і є. Microsoft не бачить помилок. Я не згоден з цим: або він повинен працювати, як очікувалося, або якийсь виняток повинен бути викинутий, або додаткове переосмислення додавання контуру пакету, який вирішує включити корінь програми чи ні. Я не можу уявити, чому хтось не хотів би, щоб корінь програми був включений, коли він був (як правило, якщо ви не встановили свій веб-сайт з псевдонімом DNS / root веб-сайту за замовчуванням). Тому насправді це повинно бути за замовчуванням у будь-якому випадку.


Мені здається найпростішим "рішенням". Інші можуть мати побічні ефекти, як, наприклад, зображення: data.
Фабрис

@MohamedEmaish це працює, ви, мабуть, щось не так. Дізнайтеся, як відстежувати запити, наприклад, використовуйте інструмент Fiddler, щоб побачити, які URL-адреси запитує браузер. Метою є не жорстке кодування всього відносного шляху, щоб ваш веб-сайт міг бути встановлений у різних місцях (кореневі шляхи) на одному сервері, або ваш продукт може змінити URL-адресу за замовчуванням без необхідності переписувати багато веб-сайту (точка наявності та кореневої змінної програми).
Tony Wall

Пішов з цим варіантом, і він спрацював чудово. Довелося переконатися, що кожен пакет містить лише елементи з однієї папки (не можна включати елементи з інших папок або підпапок), що трохи дратує, але поки він працює, я задоволений! Дякую за пост.
hvaughan3

1
Дякую. Зітхнути. Якось я хотів би більше часу витратити на написання коду, ніж на перегляд стека.
Брюс Пірсон

У мене була аналогічна проблема, де користувацький jquery-ui, який вклав папки. як тільки я вирівняв речі, як вище, це спрацювало. Це не любить вкладені папки.
Андрій Базанов

11

Я виявив, що CssRewriteUrlTransform не запускається, якщо ви посилаєтесь на *.cssфайл і у вас є пов'язаний *.min.cssфайл у тій же папці.

Щоб виправити це, видаліть *.min.cssфайл або посилайте його безпосередньо у своєму пакеті:

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

Після цього ви зробите, що ваші URL-адреси будуть правильно перетворені, а ваші зображення мають бути правильно вирішені.


1
Дякую! Після двох днів пошуку в Інтернеті, це перша згадка, яку я бачив де-небудь із CssRewriteUrlTransform, що працює з файлами * .css, але не з пов'язаним файлом * .min.css, який витягується, коли ви не працюєте в налагодженні. середовище. Однозначно здається мені помилкою. Доведеться вручну перевірити тип середовища, щоб визначити пакет із незміненою версією для налагодження, але, принаймні, у мене є вирішення зараз!
Шон

1
Це вирішило для мене проблему. Це звичайно здається помилкою. Немає сенсу, що він повинен ігнорувати CssRewriteUrlTransform, якщо він знайде попередньо існуючий файл .min.css.
користувач1751825

10

Можливо, я упереджений, але мені дуже подобається моє рішення, оскільки воно не робить ніяких перетворень, регексів тощо, і він має найменшу кількість коду :)

Це працює для веб-сайту, розміщеного як віртуальна директорія на веб-сайті IIS та як кореневий веб-сайт на IIS

Тому я створив Реалізацію IItemTransformінкапсульованого CssRewriteUrlTransformі використовував 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()));
}

Здається, мені це добре працює?


1
Це ідеально підходить для мене. відмінне рішення. мій голос - +1
imdadhusen

1
Це правильна відповідь. Клас CssUrlTransformWrapper, наданий рамкою, вирішує проблему, за винятком того, що він не працює лише тоді, коли програма не знаходиться в корені веб-сайту. Ця обгортка лаконічно вирішує цей недолік.
Дев’ять хвостів

7

Хоча відповідь Кріса Бакстера допомагає з оригінальною проблемою, вона не працює в моєму випадку, коли програма розміщується у віртуальному каталозі . Дослідивши варіанти, я закінчив із самостійним рішенням.

ProperStyleBundleклас включає код, запозичений з оригіналу, CssRewriteUrlTransformщоб правильно трансформувати відносні шляхи у віртуальному каталозі. Він також викидає, якщо файл не існує, і запобігає переупорядкуванню файлів у пакеті (код, узятий з 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 ) + ")" ) );
            }
        }
    }
}

Використовуйте його так 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
Приємне рішення, але все ж не вдається (як і у CssRewriteUrlTransform), якщо у вашому CSS є URI даних (наприклад, "data: image / png; base64, ..."). Не слід змінювати URL-адреси, починаючи з "data:" в RebaseUrlToAbsolute ().
милі82

1
@ miles82 Звичайно! Дякуємо, що вказали на це. Я змінив RebaseUrlToAbsolute ().
nrodic

6

Станом на v1.1.0-alpha1 (пакет попереднього випуску), фреймворк використовує VirtualPathProvider для доступу до файлів, а не торкаючись фізичної файлової системи.

Оновлений трансформатор можна побачити нижче:

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

Насправді, що це робить, якщо замінити відносні URL-адреси в CSS на абсолютні.
Фабрис

6

Ось пакетна трансформація, яка замінить CSS-адреси URL-адресами щодо цього файлу css. Просто додайте його у свій пакет, і це має вирішити проблему.

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

}

Як ним користуватися ?, Це показує мені виняток:cannot convert type from BundleFile to FileInfo
Stiger

@Stiger змінити css.FullName.Replace (на css.VirtualFile.VirtualPath.Replace (
lkurylo

Я можу використовувати це неправильно, але чи може цей foreach переписати всі URL-адреси на кожній ітерації та залишити їх відносно останнього файлу css, який він побачив?
Andyrooger

4

Іншим варіантом буде використання модуля перезапису URL-адреси IIS для зіставлення віртуальної папки зображень у папці фізичних зображень. Нижче наводиться приклад правила перезапису, яке ви можете використовувати для пакету під назвою "~ / bundles / yourpage / styles" - відзначте відповідність регулярних виразів на буквено-цифрових символах, а також дефісах, підкресленнях та періодах, які є загальними у назвах файлів зображень .

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

Такий підхід створює невеликі додаткові накладні витрати, але дозволяє мати більше контролю над іменами пакунків, а також зменшує кількість пакетів, на які, можливо, доведеться посилатися на одній сторінці. Звичайно, якщо вам доведеться посилатися на декілька сторонніх файлів css, які містять відносні посилання на шлях зображення, ви все одно не можете обійти створення декількох пакетів.


4

Рішення Грінн - чудове.

Однак це не працює для мене, коли в URL-адресі є відносні посилання батьківської папки. тобтоurl('../../images/car.png')

Отже, я трохи змінив Includeметод, щоб вирішити шляхи для кожного збігу регулярних виразів, дозволяючи відносні шляхи, а також додатково вбудовувати зображення в css.

Я також змінив IF DEBUG, щоб перевірити BundleTable.EnableOptimizationsзамість 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;
    }

Сподіваюся, це допоможе, з повагою.


2

Ви можете просто додати ще один рівень глибини до вашого віртуального шляху пучка

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

Це надзвичайно низькотехнологічна відповідь і такий вид зламання, але він працює і не потребує попередньої обробки. Враховуючи тривалість і складність деяких з цих відповідей, я вважаю за краще це робити так.


Це не допомагає, коли ваш веб-додаток є віртуальною програмою в IIS. Я маю на увазі, що це може працювати, але ви повинні назвати свій віртуальний додаток IIS так, як у своєму коді. Це не те, що ви хочете, правда?
psulek

У мене така ж проблема, коли додаток є віртуальним додатком у IIS. Ця відповідь мені допомагає.
БЕЗКОШТОВНО

2

У мене була ця проблема з пакетами, які мають неправильні шляхи до зображень і CssRewriteUrlTransformнеправильно вирішували відносні батьківські шляхи ..(також була проблема із зовнішніми ресурсами, такими як веб-шрифти). Ось чому я написав цю власну трансформацію (здається, що все вищезазначене правильно виконано):

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

Редагувати: я цього не усвідомлював, але я використав деякі методи розширення в коді. Вихідний код з них:

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

Звичайно , це має бути можливо замінити String.StartsWith(char)з String.StartsWith(string).


У мене немає перевантаження String.Count (), яка приймає рядок ( m.Groups[2].Value.Count("..")не працює.) І Value.StartsWith('/')не працює, тому що StartsWith очікує рядок замість знака.
jao

@jao моя погана Я включив власні методи розширення в код, не усвідомлюючи цього.
jahu

1
@jao додав у відповідь вихідний код цих методів розширення.
jahu

1

Після невеликого розслідування я завершив наступне: У вас є два варіанти:

  1. йти з перетвореннями. Дуже корисний пакет для цього: https://bundletransformer.codeplex.com/ вам потрібно наступне перетворення для кожного проблемного пакету:

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

Переваги: ​​цього рішення ви можете назвати свій пакет, що вам завгодно => ви можете об'єднати файли css в один пакет з різних каталогів. Недоліки: Вам потрібно перетворити кожен проблемний пакет

  1. Використовуйте той самий відносний корінь для імені групи, як і файл css. Переваги: ​​немає необхідності в трансформації. Недоліки: у вас є обмеження на комбінування css-аркушів з різних каталогів в один пакет.

0

CssRewriteUrlTransformвиправили мою проблему.
Якщо ваш код все ще не завантажує зображення після використання CssRewriteUrlTransform, змініть ім'я файлу css на:

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

До:

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

Так чи інакше. (Крапки) не розпізнаються в URL-адресі.


0

Просто пам’ятайте, щоб виправити декілька включень CSS у пакеті, таких як:

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

Ви не можете просто додати new CssRewriteUrlTransform()до кінця, як можна, за допомогою одного файлу CSS, оскільки метод не підтримує його, тому вам доведеться використовувати Includeкілька разів :

bundles.Add(new StyleBundle("~/Content/styles/jquery-ui")
    .Include("~/Content/css/path1/somestyle1.css", new CssRewriteUrlTransform())
    .Include("~/Content/css/path2/somestyle2.css", new CssRewriteUrlTransform()));
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.