[css] MVC4 StyleBundle不解析图像



Answers

格林/ 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);
            }
        }
    }
}

然后将它包裹在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(@"\", "/");
    }
Question

我的问题与此类似:

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/

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




您可以简单地为您的虚拟包路径添加另一个深度级别

    //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 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文件,你仍然无法绕过创建多个bundle。




没有必要指定转换或具有疯狂的子目录路径。 经过很多故障排除之后,我将它隔离到了这个“简单”规则(这是一个错误?)...

如果您的包路径不是以包含的项目的相对根目录开头,那么Web应用程序根目录将不被考虑。

听起来更像是一个bug,但无论如何,这就是你用当前的.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")

并获得“行为”(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")

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

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




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



虽然克里斯·巴克斯特的答案有助于解决原始问题,但在应用程序托管在虚拟目录中时 ,它并不适用于我的情况。 在调查了选项之后,我完成了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" ) );



经过一番调查,我得出以下结论:你有两个选择:

  1. go with transformations. Very usefull package for this: https://bundletransformer.codeplex.com/ you need following transformation for every problematic bundle:

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

Advantages: of this solution, you can name your bundle whatever you want => you can combine css files into one bundle from different directories. Disadvantages: You need to transform every problematic bundle

  1. Use the same relative root for the name of the bundle like where the css file is located. Advantages: there is no need for transformation. Disadvantages: You have limitation on combining css sheets from different directories into one bundle.



Related