MVC4 StyleBundleが画像を解決しない


293

私の質問はこれに似ています:

ASP.NET MVC 4の縮小と背景画像

できればMVC独自のバンドルに固執したいという点を除けば。スタンドアロンのCSSやjQuery UIなどの画像セットが機能するように、スタイルバンドルを指定するための正しいパターンを理解しようとすると、頭がおかしくなります。

/Content/css/ような基本的なCSSを含む典型的なMVCサイト構造があります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
イライラされた後、何度も何度も新しいバンドルおよび縮小化部分と、私が上に移動しCasseteの今無料で、より良い方法を動作します魔女!
balexandre 2012

3
リンクをありがとう、カセットは素晴らしく見え、私は間違いなくそれをチェックします。しかし、可能な場合は提供されているアプローチを使い続けたいと思います。新しいバージョンがリリースされるたびに、サードパーティのCSSファイルの画像パスをいじることなく確実にこれが可能でなければなりません。今のところ私はScriptBundles(これはうまく機能します)を保持していますが、解決策が得られるまでプレーンCSSリンクに戻しました。乾杯。
トムWホール

SEOの理由で起こりそうなエラーを追加:パス '/bundles/images/blah.jpg'のコントローラーが見つからなかったか、IControllerを実装していません。
ルークプレット2013年

回答:


361

バンドルを次のように定義した場合、MVC4 cssバンドルとイメージ参照に関するこのスレッドによると、

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

バンドルを構成するソースファイルと同じパスでバンドルを定義する場合、相対イメージパスは引き続き機能します。バンドルパスの最後の部分は、実際にfile nameはその特定のバンドルのです(つまり、/bundle任意の名前にすることができます)。

これは、同じフォルダーからCSSをバンドルする場合にのみ機能します(バンドルの観点からは、これは理にかなっていると思います)。

更新

@Hao Kungによる以下のコメントのように、別の方法として、CssRewriteUrlTransformationバンドルされている場合はCSSファイルへの相対URL参照を変更する)を適用することでこれを実現できます

注:仮想ディレクトリ内の絶対パスへの書き換えに関する問題についてのコメントは確認していません。そのため、これが全員(?)で機能しない可能性があります。

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用のCSS-リクエストに関しては最適ではないかもしれませんが、寿命は短いです。乾杯!
トムWホール

3
残念ながら、バンドルがcss自体の内部に埋め込まれたURLの書き換えをサポートするまで、バンドルする前にcssバンドルの仮想ディレクトリがcssファイルと一致する必要があります。これが、デフォルトのテンプレートバンドルに〜/ bundles / themesのようなURLがなく、代わりにディレクトリ構造のように見える理由です:〜/ content / theemes / base / css
Hao Kung

27
これは、ItemTransforms、.Include( "〜/ Content / css / jquery-ui / *。css"、new CssRewriteUrlTransform()));によってサポートされるようになりました。1.1Beta1ではこの問題を修正する必要があります
Hao Kung

2
これはMicrosoft ASP.NET Web Optimization Framework 1.1.3で修正されていますか?これの変更点に関する情報を見つけましたか?
Andrus

13
IISにWebサイトがある場合は、新しいCssRewriteUrlTransform()で問題ありません。しかし、そのアプリケーションまたはサブアプリケーションの場合、これは機能せず、CSSと同じ場所でバンドルを定義する必要があります。
アビデニック2014

34

グリン/ ThePiratソリューションはうまく機能します。

バンドルの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);
            }
        }
    }
}

そして、これをバンドル実装にラップしました:

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

これも私にとって最もきれいなようです。ありがとう。チームの努力のように見えたので、3人全員に投票します。:)
Josh Mouch

現在のコードは私には機能しません。修正しようとしていますが、お知らせします。context.HttpContext.RelativeFromAbsolutePathメソッドは存在しません。また、URLパスが「/」で始まる(絶対パスにする)場合、パス結合ロジックはオフになります。
Josh Mouch

2
@AcidPAT素晴らしい仕事です。URLにクエリ文字列がある場合、ロジックは失敗しました(.woff参照用のFontAwesomeなど、一部のサードパーティライブラリが追加します)。これは簡単な修正です。relativeToCSS呼び出す前に、正規表現を調整したり修正したりできPath.GetFullPath()ます。
セルジオペレイラ2013

2
@ChrisMarisicあなたのコードが動作するようには思えない- response.Files、BundleFilesの配列、これらのオブジェクトは、このような「存在」としての性質を持っていないです「DirectoryNameの」など
ニック・COAD

2
@ChrisMarisicは、BundleFileクラスの拡張メソッドを提供する、インポートする必要のあるネームスペースはありますか?
Nick Coad 14

20

より良い(IMHO)は、画像パスを修正するカスタムバンドルを実装します。アプリ用に1つ作成しました。

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パスにコロン(:)がないことを確認して、完全修飾URLとbase64 DataURIを考慮しました。私たちの環境では、画像は通常cssファイルと同じフォルダーにありますが、親フォルダー(url(../someFile.png))と子フォルダー(url(someFolder/someFile.png)の両方でテストしました。


これは素晴らしいソリューションです。RegexをLESSファイルでも機能するように少し変更しましたが、元のコンセプトはまさに私が必要としていたものです。ありがとう。
Tim Coulter

正規表現の初期化をループの外に置くこともできます。おそらく静的な読み取り専用プロパティとして。
Miha Markic

12

トランスフォームを指定したり、クレイジーなサブディレクトリパスを指定したりする必要はありません。多くのトラブルシューティングの後、私はそれをこの「単純な」ルールに分離しました(バグですか?)...

バンドルパスが含まれているアイテムの相対ルートで始まっていない場合、Webアプリケーションルートは考慮されません。

私にはもっとバグのように聞こえますが、とにかくそれが現在の.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 Webサイトのドキュメントのどこかからこの「〜/バンドル/ ...」パスを選択したと確信しています。実際にそれは仮想パスのかなり論理的な名前であり、実際のディレクトリと競合しないそのような仮想パスを選択するのは理にかなっているので、それを偶然見つけました。

とにかく、それがそうです。マイクロソフトにはバグはありません。私はこれに同意しません。期待どおりに機能するか、いくつかの例外がスローされるか、またはアプリケーションルートを含めるかどうかを選択するバンドルパスを追加するための追加のオーバーライドです。アプリケーションルートが存在するときに、だれもがアプリケーションルートを含めたくない理由を想像することはできません(通常、DNSエイリアス/デフォルトのWebサイトルートでWebサイトをインストールしない限り)。とにかく、実際にはそれがデフォルトのはずです。


私には最も単純な「解決策」のようです。他には、image:dataのように、副作用がある場合があります。
Fabrice、

@MohamedEmaishそれは機能します、あなたはおそらく何か間違ったことを持っています。リクエストをトレースする方法を学びます。たとえば、Fiddlerツールを使用して、ブラウザーによってリクエストされているURLを確認します。目標は、相対パス全体をハードコーディングすることではないため、同じサーバーの異なる場所(ルートパス)にWebサイトをインストールしたり、製品が多くのWebサイトを書き直さなくてもデフォルトのURLを変更したりできます。 (持っているポイントとアプリケーションのルート変数)。
Tony Wall

このオプションで行ったところ、うまくいきました。各バンドルに単一のフォルダーからのアイテムのみが含まれていることを確認する必要がありました(他のフォルダーまたはサブフォルダーからのアイテムを含めることはできません)。これは少し面倒ですが、機能する限りは満足​​です!投稿ありがとうございます。
hvaughan3

1
ありがとう。はぁ。Stackを参照するよりも、実際にコードを書くことに多くの時間を費やしたいと思っています。
Bruce Pierson 2016年

ネストされたフォルダーを持つカスタムjquery-uiで同様の問題が発生しました。上記のようにレベルを上げるとすぐにうまくいきました。ネストされたフォルダは好きではありません。
Andrei Bazanov 2016年

11

*.cssファイルを参照していて、関連付けられている*.min.cssファイルが同じフォルダーにある場合、CssRewriteUrlTransformの実行が失敗することがわかりました。

これを修正するには、*.min.cssファイルを削除するか、バンドルで直接参照します。

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

その後、URLは正しく変換され、画像は正しく解決されます。


1
ありがとうございました!2日間オンラインで検索した後、これは、CssRewriteUrlTransformが* .cssファイルを操作しているのを見た最初の言及ですが、デバッグで実行していないときにプルされた関連する* .min.cssファイルを操作していません環境。間違いなく私にはバグのようです。環境タイプを手動で確認して、デバッグ用の非縮小バージョンのバンドルを定義する必要がありますが、少なくとも今は回避策があります!
ショーン

1
これで問題が解決しました。これは確かにバグのようです。既存の.min.cssファイルが見つかった場合、CssRewriteUrlTransformを無視する必要はありません。
user1751825 2018年

10

たぶん私は偏見がありますが、変換や正規表現などを行わず、コードの量が最も少ないので、私の解決策はとても気に入っています:)

これは、IIS Webサイトの仮想ディレクトリおよびIISのルートWebサイトとしてホストされているサイトで機能します

そこで、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クラスは、アプリケーションがWebサイトのルートにない場合にのみ機能しないことを除いて、問題に対処します。このラッパーは、その欠点に簡潔に対処します。
ナインテイル

7

Chris Baxterの回答は元の問題には役立ちますが、アプリケーションが仮想ディレクトリでホストされている場合、私の場合は機能しません。オプションを調査した後、私は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
素晴らしい解決策ですが、CSSにデータURI(たとえば、 "data:image / png; base64、...")がある場合は(CssRewriteUrlTransformのように)失敗します。RebaseUrlToAbsolute()で「data:」で始まるURLを変更しないでください。
miles82

1
@ miles82もちろん!これを指摘してくれてありがとう。RebaseUrlToAbsolute()を変更しました。
nrodic '27年

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

実際、CSSの相対URLを絶対URLに置き換えるとどうなるでしょうか。
ファブリス2013年

6

次に示すのは、css urlをそのcssファイルに関連するURLに置き換えるBundle Transformです。バンドルに追加するだけで、問題が解決するはずです。

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

私はこれを間違って使用している可能性がありますが、foreachはすべての反復ですべてのURLを書き直し、最後に見たcssファイルからの相対で残しますか?
アンディルーガー、2015年

4

別のオプションは、IIS URL書き換えモジュールを使用して、仮想バンドルイメージフォルダーを物理イメージフォルダーにマップすることです。以下は、「〜/ 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>

このアプローチでは、少し余分なオーバーヘッドが発生しますが、バンドル名をより詳細に制御でき、1つのページで参照する必要があるバンドルの数も減少します。もちろん、相対画像パス参照を含む複数のサードパーティの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でWebアプリケーションを仮想アプリケーションとして使用している場合、これは役に立ちません。つまり、機能することはできますが、IIS仮想アプリにコードのように名前を付ける必要がありますが、これは適切ではありません。
psulek 2014

アプリがIISの仮想アプリケーションの場合も同じ問題が発生します。この答えは私を助けます。
BILL

2

バンドルに画像へのCssRewriteUrlTransformパスが..正しくなく、相対親パスが正しく解決されないというこの問題がありました(webfontsなどの外部リソースにも問題がありました)。これが私がこのカスタム変換を作成した理由です(上記のすべてを正しく行うように見えます)。

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がcharではなく文字列を想定しているため、機能しません。
jao 2014年

@jao私の悪いことに気づかずに自分の拡張メソッドをコードに含めました。
jahu 2014年

1
@jaoはそれらの拡張メソッドのソースコードを回答に追加しました。
jahu 2014年

1

少し調査したところ、次のことがわかりました。

  1. 変換で行きます。このための非常に 便利なパッケージ:https ://bundletransformer.codeplex.com/ すべての問題のあるバンドルに対して次の変換が必要です:

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

利点:このソリューションでは、バンドルに好きな名前を付けることができます=> cssファイルを組み合わせて、異なるディレクトリから1つのバンドルにすることができます。短所:問題のあるすべてのバンドルを変換する必要がある

  1. cssファイルが配置されている場所など、バンドルの名前には同じ相対ルートを使用します。利点:変換の必要がありません。短所:異なるディレクトリのcssシートを1つのバンドルに結合するには制限があります。

0

CssRewriteUrlTransform私の問題を修正しました。
コードを使用しても画像が読み込まれない場合は、CssRewriteUrlTransformCSSファイル名を次のように変更します。

.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()メソッドがサポートしていないため、1つの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()));
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.