我的问题与此类似:

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

除了我想坚持MVC自己的捆绑如果我可以的话。我有一个大脑崩溃试图找出什么是正确的模式是指定样式包,如独立的css和图像集,如jQuery UI工作。

我有一个典型的MVC网站结构与/Content/css包含我的基本css,如样式。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/。

所以我的问题是,怎样处理这种情况才是正确的?


当前回答

没有必要指定转换或使用疯狂的子目录路径。经过大量的故障排除后,我将其隔离到这个“简单”规则中(这是一个bug吗?)……

如果您的包路径不是以包含的项目的相对根开始,那么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/...')

然后像这样设置bundle…

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”文件夹,看看它是否与路径存在或不存在有关,但这并没有改变任何东西。该问题的解决方案实际上是要求包的名称必须以路径根开头。

这意味着这个例子通过像..这样注册和呈现bundle路径来修复。

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

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

不管怎样,事情就是这样。微软没有发现漏洞。我不同意这一点,要么它应该像预期的那样工作,要么抛出一些异常,要么额外重写添加bundle路径,选择包含应用程序根或不包含根。我无法想象为什么有人不希望包含一个应用程序根(通常情况下,除非你安装了一个DNS别名/默认的网站根)。这应该是默认值。

其他回答

更好的是(IMHO)实现一个自定义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"));

What it does is (when not in debug mode) looks for url(<something>) and replaces it with url(<absolute\path\to\something>). I wrote the thing about 10 seconds ago so it might need a little tweaking. I've taken into account fully-qualified URLs and base64 DataURIs by making sure there's no colons (:) in the URL path. In our environment, images normally reside in the same folder as their css files, but I've tested it with both parent folders (url(../someFile.png)) and child folders (url(someFolder/someFile.png).

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

    //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 Rewrite模块将虚拟包映像文件夹映射到物理映像文件夹。下面是一个重写规则的例子,你可以将其用于名为“~/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文件,你仍然不能绕过创建多个包。

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

我不喜欢它的新包括方法在捆绑,并在内容目录中创建临时文件。(它们最终被检入、部署,然后服务无法启动!)

因此,为了遵循捆绑的设计,我选择执行本质上相同的代码,但在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(@"\", "/");
    }

我发现CssRewriteUrlTransform失败运行,如果你引用一个*.css文件,你有关联的*.min.css文件在同一文件夹。

要解决这个问题,要么删除*.min.css文件,要么直接在你的bundle中引用它:

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

在你这样做之后,你的url将被正确地转换,你的图像应该被正确地解析。