MVC4 StyleBundle无法解析图像

Tom*_*all 288 css bundle jquery-ui asp.net-mvc-4 asp.net-optimization

我的问题与此类似:

ASP.NET MVC 4缩小和背景图像

除非我想坚持使用MVC自己的捆绑,如果可以的话.我正在脑力崩溃试图弄清楚用于指定样式包的正确模式是什么,例如jQuery UI工作的独立css和图像集.

我有一个典型的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"));
Run Code Online (Sandbox Code Playgroud)

渲染使用:

@Styles.Render("~/Content/styles/jquery-ui")
Run Code Online (Sandbox Code Playgroud)

我可以看到要求:

http://localhost/MySite/Content/styles/jquery-ui?v=nL_6HPFtzoqrts9nwrtjq0VQFYnhMjY5EopXsK8cxmg1
Run Code Online (Sandbox Code Playgroud)

这将返回正确的,缩小的CSS响应.但随后浏览器发送相对链接图像的请求,如下所示:

http://localhost/MySite/Content/styles/images/ui-bg_highlight-soft_100_eeeeee_1x100.png
Run Code Online (Sandbox Code Playgroud)

这是一个404.

我知道我的URL的最后一部分jquery-ui是一个无扩展名的URL,我的包的处理程序,所以我可以看到为什么相对的图像请求是简单的/styles/images/.

所以我的问题是处理这种情况的正确方法什么

Chr*_*ter 358

根据MVC4 css捆绑和图像引用上的这个线程,如果你将bundle定义为:

bundles.Add(new StyleBundle("~/Content/css/jquery-ui/bundle")
                   .Include("~/Content/css/jquery-ui/*.css"));
Run Code Online (Sandbox Code Playgroud)

如果您在与构成捆绑包的源文件相同的路径上定义捆绑包,则相对图像路径仍然有效.捆绑路径的最后一部分实际上是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()));
Run Code Online (Sandbox Code Playgroud)

  • 现在通过ItemTransforms,.Include("〜/ Content/css/jquery-ui/*.css",new CssRewriteUrlTransform()))支持这个; 在1.1Beta1应该解决这个问题 (27认同)
  • 如果你在IIS中有一个网站,那么新的CssRewriteUrlTransform()就可以了.但如果它的应用程序或子应用程序不起作用,你必须在与CSS相同的位置定义捆绑包. (12认同)
  • 是的,不幸的是,在捆绑支持重写css本身内部的嵌入式URL之前,你需要css包的虚拟目录在捆绑之前匹配css文件.这就是为什么默认模板包没有像〜/ bundles/themes这样的网址,而是看起来像目录结构:〜/ content/theemes/base/css (3认同)
  • 这是在Microsoft ASP.NET Web Optimization Framework 1.1.3中修复的吗?我发现了有关这方面的变化的任何信息? (2认同)

Aci*_*PAT 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);
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

然后将其包装在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());
    }
}
Run Code Online (Sandbox Code Playgroud)

样品用法:

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"
            ));
Run Code Online (Sandbox Code Playgroud)

这是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(@"\", "/");
    }
Run Code Online (Sandbox Code Playgroud)

  • @AcidPAT很棒的工作.如果url有一个查询字符串(一些第三方库添加它,就像FontAwesome的.woff引用一样)逻辑失败.但这是一个简单的修复.在调用`Path.GetFullPath()`之前,可以调整Regex或修复`relativeToCSS`. (2认同)
  • @ChrisMarisic你的代码似乎不起作用 - response.Files是一个BundleFiles数组,这些对象没有"Exists","DirectoryName"等属性. (2认同)
  • @ChrisMarisic是否有一个我应该导入的命名空间,为BundleFile类提供扩展方法? (2认同)

Gri*_*inn 20

更好的是(恕我直言)实现了一个修复图像路径的自定义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;
Run Code Online (Sandbox Code Playgroud)

...

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

}
Run Code Online (Sandbox Code Playgroud)

要使用它,请执行:

bundles.Add(new StyleImagePathBundle("~/bundles/css").Include(
  "~/This/Is/Some/Folder/Path/layout.css"));
Run Code Online (Sandbox Code Playgroud)

...代替...

bundles.Add(new StyleBundle("~/bundles/css").Include(
  "~/This/Is/Some/Folder/Path/layout.css"));
Run Code Online (Sandbox Code Playgroud)

它的作用是(当不处于调试模式时)寻找url(<something>)并替换它url(<absolute\path\to\something>).我在10秒前写了这个东西,所以可能需要稍微调整一下.我通过确保URL路径中没有冒号(:)来考虑完全限定的URL和base64 DataURI.在我们的环境中,图像通常与它们的css文件位于同一文件夹中,但我已经使用父文件夹(url(../someFile.png))和子文件夹(url(someFolder/someFile.png)进行了测试.


Ton*_*all 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/...')
Run Code Online (Sandbox Code Playgroud)

然后设置像...

BundleTable.Add(new StyleBundle("~/Bundles/Styles").Include("~/Content/Site.css"));
Run Code Online (Sandbox Code Playgroud)

并渲染它...

@Styles.Render("~/Bundles/Styles")
Run Code Online (Sandbox Code Playgroud)

并获得"行为"(bug),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")
Run Code Online (Sandbox Code Playgroud)

所以当然你可以说这是RTFM,但我很确定我和其他人从默认模板或MSDN或ASP.NET网站上的文档中的某处获取了这个"〜/ Bundles/..."路径,或者只是偶然发现它,因为实际上它是虚拟路径的一个非常合乎逻辑的名称,并且选择不与真实目录冲突的虚拟路径是有意义的.

无论如何,这就是它的方式.微软看不到任何错误.我不同意这一点,要么它应该按预期工作,要么抛出一些异常,或者添加一个额外的覆盖来添加选择包括应用程序root的bundle路径.我无法想象为什么有人不希望有一个应用程序根目录(通常除非您使用DNS别名/默认网站root安装您的网站).所以实际上这应该是默认值.


ajb*_*ven 10

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

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

bundles.Add(new Bundle("~/bundles/bootstrap")
    .Include("~/Libs/bootstrap3/css/bootstrap.min.css", new CssRewriteUrlTransform()));
Run Code Online (Sandbox Code Playgroud)

之后,您可以正确转换您的网址,并正确解析您的图片.


Sim*_*tes 8

也许我有偏见,但我非常喜欢我的解决方案,因为它没有做任何转换,正则表达式等,它的代码量最少:)

这适用于作为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()));
}
Run Code Online (Sandbox Code Playgroud)

好像对我有用吗?


nro*_*dic 7

尽管Chris Baxter的回答有助于解决原始问题,但在我的情况下,当应用程序托管在虚拟目录中时,它不起作用.在调查选项后,我完成了DIY解决方案.

ProperStyleBundleclass包括从原始借用的代码,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 ) + ")" ) );
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

使用它像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" ) );
Run Code Online (Sandbox Code Playgroud)

  • 很好的解决方案,但仍然失败(就像CssRewriteUrlTransform),如果你的CSS中有一个数据URI(例如"data:image/png; base64,...").您不应该在RebaseUrlToAbsolute()中以"data:"开头更改url. (2认同)

Ben*_*ter 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);
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


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

}
Run Code Online (Sandbox Code Playgroud)