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中的图像路径是相对于该文件夹的,我不想弄乱它们。

据我了解,当我指定a时,StyleBundle我需要指定一个虚拟路径,该路径也与真实内容路径不匹配,因为(假设我忽略了通往Content的路由)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

3
感谢您提供的链接,Cassette看起来不错,我肯定会检查一下。但是我想尽可能地使用所提供的方法,当然,每次发布新版本时,都必须做到这一点而不会弄乱第三方CSS文件中的图像路径。现在,我保留了ScriptBundles(效果很好),但是恢复为纯CSS链接,直到获得解决方案为止。干杯。
汤姆W厅

由于SEO原因而添加了可能的错误:找不到路径'/bundles/images/blah.jpg'的控制器或未实现IController。
路加·普普利特

Answers:


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位于具有自己的图像文件夹的CSS文件夹中,因此我只指定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”,新的CssRewriteUrlTransform())支持。1.1Beta1中的版本应解决此问题
郝公

2
Microsoft ASP.NET Web优化框架1.1.3是否已解决此问题?我发现有关此更改的任何信息?
Andrus 2014年

13
如果您在IIS中有一个网站,则新的CssRewriteUrlTransform()很好。但是如果它是一个应用程序或子应用程序,则将无法正常工作,并且您必须诉诸于在与CSS相同的位置定义包。
avidenic 2014年

34

Grinn / ThePirat解决方案效果很好。

我不喜欢它在bundle中添加了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 Implemetation中:

public class StyleImagePathBundle 
    : Bundle
{
    public StyleImagePathBundle(string virtualPath)
        : base(virtualPath)
    {
        base.Transforms.Add(new StyleRelativePathTransform());
        base.Transforms.Add(new CssMinify());
    }

    public StyleImagePathBundle(string virtualPath, string cdnPath)
        : base(virtualPath, cdnPath)
    {
        base.Transforms.Add(new StyleRelativePathTransform());
        base.Transforms.Add(new CssMinify());
    }
}

用法示例:

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

这对我来说似乎也是最干净的。谢谢。我赞成你们三个,因为这看起来像是团队合作。:)
Josh Mouch 2012年

您现在拥有的代码对我不起作用。我正在尝试修复它,但以为我会让您知道。context.HttpContext.RelativeFromAbsolutePath方法不存在。另外,如果url路径以“ /”开头(使其为绝对值),则路径组合逻辑已关闭。
Josh Mouch 2012年

2
@AcidPAT很棒的工作。如果该URL有一个查询字符串,则逻辑失败(某些第三方库添加了该字符串,例如FontAwesome作为其.woff参考。)尽管如此,这很容易解决。可以relativeToCSS在致电前调整正则表达式或进行修复Path.GetFullPath()
sergiopereira

2
@ChrisMarisic你的代码似乎没有工作- response.Files是BundleFiles的数组,这些对象不具有诸如“存在”,“目录名”等
尼克COAD

2
@ChrisMarisic也许我应该导入一个命名空间,该命名空间为BundleFile类提供扩展方法?
Nick Coad 2014年

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路径中没有冒号(:),我考虑了完全限定的URL和base64 DataURI。在我们的环境中,图像通常与其css文件位于同一文件夹中,但是我已经对父文件夹(url(../someFile.png))和子文件夹(url(someFolder/someFile.png)进行了测试。


这是一个很好的解决方案。我对您的Regex进行了少许修改,以便它也可以与LESS文件一起使用,但是原始概念正是我所需要的。谢谢。
蒂姆·库尔特

您也可以将正则表达式初始化放在循环之外。也许作为静态只读属性。
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 / ...”,具体取决于我是否添加了转换。

甚至尝试创建“ Bundles”文件夹,以查看是否与现有路径有关,但这并没有任何改变。解决问题的方法实际上是要求捆绑软件的名称必须以路径根开头。

表示此示例通过注册和渲染捆绑路径(如...)来解决。

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

因此,您当然可以说这是RTFM,但是我很确定我和其他人从默认模板或MSDN或ASP.NET网站的文档中的某个地方选择了此“〜/ Bundles / ...”路径,或者偶然发现它是因为它实际上是一个虚拟路径的逻辑名称,选择与真实目录不冲突的虚拟路径是有意义的。

无论如何,就是这样。微软没有发现错误。我不同意这一点,要么它应该可以按预期工作,要么应该引发一些异常,或者是添加捆绑路径的附加替代选择是否包含应用程序根。我无法想象为什么有人不希望包含应用程序根目录(通常除非您使用DNS别名/默认网站根目录安装了网站)。所以实际上无论如何这应该是默认值。


在我看来,最简单的“解决方案”。其他可能会有副作用,例如image:data。
Fabrice 2014年

@MohamedEmaish确实有效,您可能出了点问题。了解如何跟踪请求,例如使用Fiddler Tool查看浏览器正在请求哪些URL。目标不是硬编码整个相对路径,因此您的网站可以安装在同一服务器上的不同位置(根路径),或者您的产品可以更改默认URL,而无需重新编写很多网站(具有和应用程序根变量的要点)。
托尼·沃尔

选择了这个选项,效果很好。必须确保每个捆绑包中都只有一个文件夹中的项目(不能包括其他文件夹或子文件夹中的项目),这有点令人讨厌,但是只要有效,我很高兴!感谢您的帖子。
hvaughan3 '02

1
谢谢。叹。有一天,我想花更多的时间在编写代码上,而不是浏览Stack。
布鲁斯·皮尔森

我有一个类似的问题,其中具有嵌套文件夹的自定义jquery-ui。一旦我将事情升级到上面,它就起作用了。它不喜欢嵌套文件夹。
Andrei Bazanov

11

我发现如果您引用*.css文件并且关联*.min.css文件位于同一文件夹中,则CssRewriteUrlTransform无法运行。

要解决此问题,请删除*.min.css文件或直接在包中引用它:

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

之后,您的URL将正确转换,并且图像应正确解析。


1
谢谢!经过两天的在线搜索,这是我第一次看到CssRewriteUrlTransform使用* .css文件,但是不使用在调试中不运行时插入的相关* .min.css文件的情况。环境。在我看来,这绝对像是个虫子。将必须手动检查环境类型,以定义带有最小版本的捆绑包进行调试,但至少现在有了解决方法!
肖恩

1
这为我解决了问题。当然,这似乎是一个错误。如果找到已存在的.min.css文件,则应该忽略CssRewriteUrlTransform是没有意义的。
user1751825 '18

10

也许我有偏见,但是我很喜欢我的解决方案,因为它不进行任何转换,正则表达式等,并且它的代码量最少:)

这适用于作为IIS网站中虚拟目录托管的站点以及IIS上的根网站

因此,我创建了IItemTransform封装了的Implentation,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

尽管Chris Baxter的答案有助于解决原始问题,但是在应用程序托管在virtual directory中的情况下,它对我而言不起作用。在研究了选项之后,我完成了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 2015年

1
@ miles82当然!感谢您指出了这一点。我已经更改了RebaseUrlToAbsolute()。
nrodic 2015年

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中的相对URL,该怎么办。
Fabrice

6

这是一个Bundle Transform,它将用相对于该CSS文件的URL替换CSS URL。只需将其添加到您的捆绑包中即可解决该问题。

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

我可能使用的是这种错误,但是foreach是否在每次迭代时都重写所有的url,并使其相对于它看到的最后一个css文件?
Andyrooger,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>

这种方法会产生一些额外的开销,但可以让您更好地控制捆绑软件名称,还可以减少您可能在一页上引用的捆绑软件数量。当然,如果必须引用多个包含相对图像路径引用的第三方css文件,则仍然无法创建多个捆绑包。


4

Grinn解决方案很棒。

但是,当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"));

这是一种技术含量极低的答案,有点像骇客,但它有效且不需要任何预处理。考虑到其中一些答案的篇幅和复杂性,我更喜欢这样做。


当您将Web应用程序作为IIS中的虚拟应用程序时,这无济于事。我的意思是它可以工作,但是您必须按照代码中的名称来命名IIS虚拟应用程序,这不是您想要的,对吗?
psulek

当应用程序是IIS中的虚拟应用程序时,我遇到同样的问题。这个答案对我有帮助。
比尔2014年

2

我遇到了这样的问题:bundle的图像路径不正确,并且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。
2014年

@jao我不好,我在代码中包含了自己的扩展方法,而没有意识到。
jahu 2014年

1
@jao将这些扩展方法的源代码添加到了答案中。
jahu 2014年

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()));
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.