MVC4 StyleBundle не разрешает изображения


293

Мой вопрос похож на это:

ASP.NET MVC 4 Минификация и фоновые изображения

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

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

Насколько я понимаю, когда я указываю, 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
После того, как я снова и снова разочаровывался в новой части Bundling and Minification, я перешел к Кассете, ведьма теперь свободна и работает намного лучше!
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 UI - который может быть неоптимальным с точки зрения запросов, но жизнь коротка. Ура!
Том Зал

3
Да, к сожалению, пока в комплекте нет поддержки перезаписи встроенных URL-адресов внутри самого css, вам нужен виртуальный каталог пакета css, соответствующий файлам css перед объединением. Вот почему шаблоны по умолчанию не имеют URL-адресов, таких как ~ / bundles / themes, и вместо этого выглядят как структура каталогов: ~ / content / theemes / base / css
Hao Kung

27
Теперь это поддерживается с помощью ItemTransforms, .Include ("~ / Content / css / jquery-ui / *. Css", new CssRewriteUrlTransform ())); в 1.1Beta1 должен исправить эту проблему
Хао Кунг

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

13
new CssRewriteUrlTransform () хорошо, если у вас есть сайт в IIS. но если это приложение или вспомогательное приложение, это не будет работать, и вам придется прибегнуть к определению вашего пакета в том же месте, что и ваш CSS.
avidenic

34

Решение Grinn / ThePirat работает хорошо.

Мне не понравилось, что он new'd метод Include в комплекте и что он создает временные файлы в каталоге содержимого. (они закончили тем, что зарегистрировались, развернули, тогда служба не запустилась!)

Таким образом, чтобы следовать схеме 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);
            }
        }
    }
}

А затем обернул это в реализацию Bundle:

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

Мне кажется, это тоже самое чистое. Спасибо. Я голосую за всех троих, потому что это выглядело как командная работа. :)
Джош Моуч

Код, который у вас есть сейчас, не работает для меня. Я пытаюсь это исправить, но подумала, что дам тебе знать. Метод context.HttpContext.RelativeFromAbsolutePath не существует. Кроме того, если путь URL начинается с «/» (что делает его абсолютным), ваша логика объединения путей отключена.
Джош Моуч

2
@AcidPAT отличная работа. Логика не сработала, если в URL была строка запроса (некоторые сторонние библиотеки добавляют ее, например, FontAwesome для ссылки .woff.) Это легко исправить. Можно настроить регулярное выражение или исправить relativeToCSSперед вызовом Path.GetFullPath().
sergiopereira

2
@ChrisMarisic, кажется, ваш код не работает - response.Files - это массив BundleFiles, у этих объектов нет таких свойств, как «Exists», «DirectoryName» и т. Д.
Nick Coad

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

20

Еще лучше (IMHO) реализовать собственный Bundle, который исправляет пути к изображениям. Я написал один для моего приложения.

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-адреса и BaseUR DataURI, убедившись, что в пути URL нет двоеточий (:). В нашей среде изображения обычно находятся в той же папке, что и их CSS-файлы, но я проверил это как с родительскими папками ( url(../someFile.png)), так и с дочерними папками ( url(someFolder/someFile.png).


Это отличное решение. Я немного изменил ваш Regex, чтобы он также работал с файлами LESS, но оригинальная концепция была именно тем, что мне было нужно. Спасибо.
Тим Коултер

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

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, но я вполне уверен, что я и другие взяли этот путь "~ / Bundles / ..." из шаблона по умолчанию или где-то в документации на веб-сайте MSDN или ASP.NET, или просто наткнулся на него, потому что на самом деле это вполне логичное имя для виртуального пути и имеет смысл выбирать такие виртуальные пути, которые не конфликтуют с реальными каталогами.

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


Мне кажется самое простое «решение». Другие могут иметь побочные эффекты, как с изображением: данные.
Фабрис

@ MohamedEmaish это работает, вы, вероятно, что-то не так. Узнайте, как отслеживать запросы, например, используйте Fiddler Tool, чтобы увидеть, какие URL запрашиваются браузером. Цель состоит не в том, чтобы жестко кодировать весь относительный путь, чтобы ваш веб-сайт мог быть установлен в разных местах (корневых путях) на одном сервере, или ваш продукт может изменить URL-адрес по умолчанию без необходимости переписывать большую часть веб-сайта. (точка наличия и корневая переменная приложения).
Тони Уолл

Пошел с этим вариантом, и он работал отлично. Нужно было убедиться, что у каждого пакета были только элементы из одной папки (не может включать элементы из других папок или подпапок), что немного раздражает, но пока это работает, я счастлив! Спасибо за пост.
hvaughan3

1
Спасибо. Вздох. Однажды я хотел бы потратить больше времени на написание кода, чем на просмотр стека.
Брюс Пирсон

У меня была похожая проблема, когда пользовательский jquery-интерфейс, который имел вложенные папки. как только я выровнял вещи, как указано выше, это сработало. Не любит вложенные папки.
Андрей Базанов

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

10

Может быть, я предвзят, но мне очень нравится мое решение, так как оно не выполняет никаких преобразований, регулярных выражений и т. Д., И в нем меньше всего кода :)

Это работает для сайта, размещенного в качестве виртуального каталога на веб-сайте IIS и корневого веб-сайта в IIS.

Поэтому я создал Implentation 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

Хотя ответ Криса Бакстера помогает с оригинальной проблемой, он не работает в моем случае, когда приложение размещено в виртуальном каталоге . Изучив варианты, я закончил с DIY решением.

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

1
@ miles82 Конечно! Спасибо за указание на это. Я изменил RebaseUrlToAbsolute ().
Nrodic

6

Начиная с версии 1.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

Вот преобразование Bundle, которое заменит URL 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 мой плохой Я включил свои собственные методы расширения в код, не осознавая этого.
Джаху

1
@jao добавил в ответ исходный код этих методов расширения.
Джаху

1

После небольшого расследования я пришел к выводу следующее: у вас есть 2 варианта:

  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.