【问题标题】:MVC4 StyleBundle not resolving imagesMVC4 StyleBundle 无法解析图像
【发布时间】:2012-07-06 13:01:16
【问题描述】:

我的问题与此类似:

ASP.NET MVC 4 Minification & Background Images

如果可以的话,我想坚持使用 MVC 自己的捆绑。我想弄清楚指定样式包的正确模式是什么,以便独立的 css 和图像集(如 jQuery UI)工作,我陷入了大脑崩溃。

我有一个典型的 MVC 站点结构,带有 /Content/css/,其中包含我的基本 CSS,例如 styles.css。在该 css 文件夹中,我还有诸如 /jquery-ui 之类的子文件夹,其中包含其 CSS 文件和一个 /images 文件夹。 jQuery UI CSS 中的图像路径是相对于该文件夹的,我不想弄乱它们。

据我了解,当我指定 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/

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

【问题讨论】:

  • 在对新的捆绑和缩小部分一次又一次感到沮丧之后,我转到了Cassete,女巫现在免费并且工作得更好!
  • 感谢您的链接,卡带看起来不错,我一定会去看看。但如果可能的话,我想坚持使用所提供的方法,这肯定是可能的,而且每次发布新版本时都不会弄乱第 3 方 CSS 文件中的图像路径。现在我保留了我的 ScriptBundles(它工作得很好),但在我得到解决方案之前恢复为纯 CSS 链接。干杯。
  • 由于 SEO 原因添加可能的错误:找不到路径“/bundles/images/blah.jpg”的控制器或未实现 IController。

标签: css jquery-ui asp.net-mvc-4 bundle asp.net-optimization


【解决方案1】:

根据MVC4 css bundling and image references 上的这个帖子,如果您将捆绑包定义为:

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

如果您在与组成包的源文件相同的路径中定义包,则相对图像路径仍然有效。捆绑包路径的最后一部分实际上是该特定捆绑包的 file name(即,/bundle 可以是您喜欢的任何名称)。

这只有在您将同一个文件夹中的 CSS 捆绑在一起时才有效(我认为从捆绑的角度来看这是有意义的)。

更新

根据@Hao Kung 下面的评论,或者现在可以通过应用CssRewriteUrlTransformation (Change relative URL references to CSS files when bundled) 来实现。

注意:我尚未确认 cmets 关于重写虚拟目录中的绝对路径的问题,因此这可能不适用于所有人 (?)。

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

【讨论】:

  • 传奇!是的,这很完美。我有不同级别的 CSS,但它们每个都有自己的图像文件夹,例如我的主站点 CSS 位于根 CSS 文件夹中,然后 jquery-ui 位于其中,并带有自己的图像文件夹,所以我只指定了 2 个包,一个用于我的基本 CSS,一个用于 jQuery UI - 这可能不是超级优化的有求必应,但人生苦短。干杯!
  • 是的,不幸的是,直到捆绑支持重写 css 本身内部的嵌入式 url,您需要 css 捆绑包的虚拟目录在捆绑之前匹配 css 文件。这就是为什么默认的模板包没有像 ~/bundles/themes 这样的 url,而是看起来像目录结构:~/content/theemes/base/css
  • 现在通过 ItemTransforms 支持,.Include("~/Content/css/jquery-ui/*.css", new CssRewriteUrlTransform()));在 1.1Beta1 中应该会解决这个问题
  • 这在 Microsoft ASP.NET Web 优化框架 1.1.3 中是否已修复?我还没有找到有关此更改的任何信息?
  • new CssRewriteUrlTransform() 如果您在 IIS 中有网站,则可以。但如果它是一个应用程序或子应用程序,这将不起作用,你必须求助于在与 CSS 相同的位置定义你的包。
【解决方案2】:

另一种选择是使用 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>

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

【讨论】:

    【解决方案3】:

    更好的是(恕我直言)实现一个自定义 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(&lt;something&gt;) 并将其替换为url(&lt;absolute\path\to\something&gt;)。我大约 10 秒前写了这件事,所以可能需要稍微调整一下。通过确保 URL 路径中没有冒号 (:),我考虑了完全限定的 URL 和 base64 DataURI。在我们的环境中,图像通常与其 css 文件位于同一个文件夹中,但我已经使用父文件夹 (url(../someFile.png)) 和子文件夹 (url(someFolder/someFile.png) 对其进行了测试。

    【讨论】:

    • 这是一个很好的解决方案。我稍微修改了您的正则表达式,以便它也适用于 LESS 文件,但最初的概念正是我所需要的。谢谢。
    • 您也可以将正则表达式初始化放在循环之外。也许作为一个静态只读属性。
    【解决方案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;
        }
    

    希望对你有帮助,问候。

    【讨论】:

      【解决方案5】:

      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 实现中:

      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 路径以“/”开头(使其成为绝对路径),则您的路径组合逻辑关闭。
      • @AcidPAT 干得好。如果 url 有一个查询字符串(一些 3rd 方库添加它,例如 FontAwesome 的 .woff 参考),逻辑就会失败。不过,这很容易解决。可以在调用Path.GetFullPath() 之前调整正则表达式或修复relativeToCSS
      • @ChrisMarisic 您的代码似乎不起作用 - response.Files 是一个 BundleFiles 数组,这些对象没有“Exists”、“DirectoryName”等属性。
      • @ChrisMarisic 是否有我应该导入的命名空间为 BundleFile 类提供扩展方法?
      【解决方案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,会发生什么。
      【解决方案7】:

      这是一个捆绑转换,它将用相对于该 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 将 css.FullName.Replace( 改为 css.VirtualFile.VirtualPath.Replace(
      • 我可能用错了,但是 foreach 是否会在每次迭代时重写所有 url 并使其相对于它看到的最后一个 css 文件?
      【解决方案8】:

      您可以简单地为您的虚拟捆绑路径添加另一个层次的深度

          //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"));
      

      这是一个超低技术含量的答案和一种 hack,但它可以工作并且不需要任何预处理。鉴于其中一些答案的长度和复杂性,我更喜欢这样做。

      【讨论】:

      • 当您将 Web 应用程序作为 IIS 中的虚拟应用程序时,这无济于事。我的意思是它可以工作,但你必须在你的代码中命名你的 IIS 虚拟应用程序,这不是你想要的,对吧?
      • 当应用程序是 IIS 中的虚拟应用程序时,我遇到了同样的问题。这个答案对我有帮助。
      【解决方案9】:

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

      如果您的包路径不是以所包含项目的相对根目录开头,则不会考虑 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”文件夹以查看它是否与现有路径有关,但这并没有改变任何东西。问题的解决方案其实就是要求bundle的名字必须以路径root开头。

      意思是这个例子是通过注册和渲染捆绑路径来修复的..

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

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

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

      【讨论】:

      • 在我看来是最简单的“解决方案”。其他的可能有副作用,比如 image:data。
      • @MohamedEmaish 它确实有效,你可能搞错了。了解如何跟踪请求,例如使用 Fiddler Tool 查看浏览器正在请求哪些 URL。目标不是硬编码整个相对路径,以便您的网站可以安装在同一服务器上的不同位置(根路径),或者您的产品可以更改默认 URL 而无需重新编写大量网站(拥有和应用程序根变量的点)。
      • 选择了这个选项,效果很好。必须确保每个捆绑包中只有一个文件夹中的项目(不能包括其他文件夹或子文件夹中的项目),这有点烦人,但只要它有效,我就很高兴!感谢您的帖子。
      • 谢谢。叹。总有一天,我想花更多的时间实际编写代码而不是浏览 Stack。
      • 我遇到了类似的问题,即自定义 jquery-ui 具有嵌套文件夹。一旦我按照上面的方法进行升级,它就起作用了。它不喜欢嵌套文件夹。
      【解决方案10】:

      我遇到了这个问题,捆绑包的图像路径不正确,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(string)替换String.StartsWith(char)

      【讨论】:

      • 我没有接受字符串的 String.Count() 重载(m.Groups[2].Value.Count("..") 不起作用。)并且 Value.StartsWith('/') 也不起作用,因为 StartsWith 需要一个字符串而不是一个字符。
      • @jao 我的错,我在代码中包含了我自己的扩展方法而没有意识到。
      • @jao 将这些扩展方法的源代码添加到答案中。
      【解决方案11】:

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

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

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

      完成此操作后,您的 URL 将被正确转换,并且您的图像应该被正确解析。

      【讨论】:

      • 谢谢!经过两天的在线搜索,这是我第一次看到 CssRewriteUrlTransform 与 *.css 文件一起使用,但不是与在您未在调试中运行时拉入的关联 *.min.css 文件一起使用环境。对我来说绝对是一个错误。将不得不手动检查环境类型以定义带有未压缩版本的捆绑包以进行调试,但至少我现在有一个解决方法!
      • 这解决了我的问题。这当然看起来像一个错误。如果找到预先存在的 .min.css 文件,它应该忽略 CssRewriteUrlTransform 是没有意义的。
      【解决方案12】:

      虽然 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" ) );
      

      【讨论】:

      • 不错的解决方案,但如果您的 CSS 中有数据 URI(例如“data:image/png;base64,...”),仍然会失败(就像 CssRewriteUrlTransform 一样)。您不应该在 RebaseUrlToAbsolute() 中更改以“data:”开头的 url。
      • @miles82 当然!感谢您指出了这一点。我已经改变了 RebaseUrlToAbsolute()。
      【解决方案13】:

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

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

      所以我创建了一个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
      • 这是正确答案。框架提供的 CssUrlTransformWrapper 类解决了这个问题,但它仅在应用程序不在网站根目录时才起作用。这个包装器简洁地解决了这个缺点。
      【解决方案14】:

      CssRewriteUrlTransform 解决了我的问题。
      如果您的代码在使用CssRewriteUrlTransform 后仍然没有加载图像,那么 更改您的 css 文件名:

      .Include("~/Content/jquery/jquery-ui-1.10.3.custom.css", new CssRewriteUrlTransform())
      

      收件人:

      .Include("~/Content/jquery/jquery-ui.css", new CssRewriteUrlTransform())
      

      在 url 中无法识别 .(点)。

      【讨论】:

        【解决方案15】:

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

        1. 进行转换。非常有用的软件包:https://bundletransformer.codeplex.com/ 您需要对每个有问题的捆绑包进行以下转换:

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

        优点:在此解决方案中,您可以随意命名您的捆绑包 => 您可以将不同目录中的 css 文件组合成一个捆绑包。 缺点:你需要改造每一个有问题的包

        1. 使用与 css 文件所在位置相同的相对根作为捆绑名称。优点:无需改造。 缺点:您在将不同目录中的 css 表单合并到一个包中时受到限制。

        【讨论】:

          【解决方案16】:

          只需记住修复 多个 CSS 包含在一个包中,例如:

          bundles.Add(new StyleBundle("~/Content/styles/jquery-ui")
              .Include("~/Content/css/path1/somestyle1.css", "~/Content/css/path2/somestyle2.css"));
          

          您不能像使用一个 CSS 文件那样只在末尾添加new CssRewriteUrlTransform(),因为该方法不支持它,因此您必须多次使用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()));
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2020-04-15
            • 2017-07-23
            • 2010-10-24
            • 1970-01-01
            • 1970-01-01
            • 2019-01-18
            • 2022-01-03
            相关资源
            最近更新 更多