MVC4 StyleBundle无法解析图像

本文翻译自:MVC4 StyleBundle not resolving images

My question is similar to this: 我的问题与此类似:

ASP.NET MVC 4 Minification & Background Images ASP.NET MVC 4缩小和背景图像

Except that I want to stick with MVC's own bundling if I can. 除非我想坚持使用MVC自己的捆绑,如果可以的话。 I'm having a brain crash trying to figure out what the correct pattern is for specifying style bundles such that standalone css and image sets such as jQuery UI work. 我正在脑力崩溃试图弄清楚用于指定样式包的正确模式是什么,例如jQuery UI工作的独立css和图像集。

I have a typical MVC site structure with /Content/css/ which contains my base CSS such as styles.css . 我有一个典型的MVC站点结构/Content/css/ ,它包含我的基本CSS,如styles.css Within that css folder I also have subfolders such as /jquery-ui which contains its CSS file plus an /images folder. 在该css文件夹中,我还有/jquery-ui等子文件夹,其中包含CSS文件和/images文件夹。 Image paths in the jQuery UI CSS are relative to that folder and I don't want to mess with them. jQuery UI CSS中的图像路径是相对于该文件夹的,我不想搞砸它们。

As I understand it, when I specify a StyleBundle I need to specify a virtual path which does not also match a real content path, because (assuming I'm ignoring routes to Content) IIS would then try to resolve that path as a physical file. 据我所知,当我指定一个StyleBundle我需要指定一个虚拟路径,它也不匹配实际内容路径,因为(假设我忽略了到内容的路由)然后IIS会尝试将该路径解析为物理文件。 So I'm specifying: 所以我指的是:

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

rendered using: 渲染使用:

@Styles.Render("~/Content/styles/jquery-ui")

I can see the request going out to: 我可以看到要求:

http://localhost/MySite/Content/styles/jquery-ui?v=nL_6HPFtzoqrts9nwrtjq0VQFYnhMjY5EopXsK8cxmg1

This is returning the correct, minified CSS response. 这将返回正确的,缩小的CSS响应。 But then the browser sends a request for a relatively linked image as: 但随后浏览器发送相对链接图像的请求,如下所示:

http://localhost/MySite/Content/styles/images/ui-bg_highlight-soft_100_eeeeee_1x100.png

Which is a 404 . 这是404

I understand that the last part of my URL jquery-ui is an extensionless URL, a handler for my bundle, so I can see why the relative request for the image is simply /styles/images/ . 我知道我的URL jquery-ui的最后一部分是一个无扩展名的URL,我的包的处理程序,所以我可以看到为什么对图像的相对请求只是/styles/images/

So my question is what is the correct way of handling this situation? 所以我的问题是处理这种情况的正确方法什么


#1楼

参考:https://stackoom.com/question/LEcf/MVC-StyleBundle无法解析图像


#2楼

According to this thread on MVC4 css bundling and image references , if you define your bundle as: 根据MVC4 css捆绑和图像引用上的这个线程,如果你将bundle定义为:

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

Where you define the bundle on the same path as the source files that made up the bundle, the relative image paths will still work. 如果您在与构成捆绑包的源文件相同的路径上定义捆绑包,则相对图像路径仍然有效。 The last part of the bundle path is really the file name for that specific bundle (ie, /bundle can be any name you like). 捆绑路径的最后一部分实际上是该特定捆绑包的file name (即/bundle可以是您喜欢的任何名称)。

This will only work if you are bundling together CSS from the same folder (which I think makes sense from a bundling perspective). 这只有在你从同一个文件夹中捆绑CSS时才会起作用(我认为从捆绑的角度看是有意义的)。

Update 更新

As per the comment below by @Hao Kung, alternatively this may now be achieved by applying a CssRewriteUrlTransformation ( Change relative URL references to CSS files when bundled ). 根据@Hao Kung下面的评论,现在可以通过应用CssRewriteUrlTransformation在捆绑时更改CSS文件的相对URL引用)来实现

NOTE: I have not confirmed comments regarding issues with rewriting to absolute paths within a virtual directory, so this may not work for everyone (?). 注意:我还没有确认有关重写到虚拟目录中的绝对路径的问题的注释,因此这可能对每个人都不起作用(?)。

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

#3楼

Another option would be to use the IIS URL Rewrite module to map the virtual bundle image folder to the physical image folder. 另一种选择是使用IIS URL重写模块将虚拟包图像文件夹映射到物理图像文件夹。 Below is an example of a rewrite rule from that you could use for a bundle called "~/bundles/yourpage/styles" - note the regex matches on alphanumeric characters as well as hyphens, underscores and periods, which are common in image file names. 下面是一个重写规则的示例,您可以使用它作为名为“〜/ 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>

This approach creates a little extra overhead, but allows you to have more control over your bundle names, and also reduces the number of bundles you may have to reference on one page. 这种方法会产生一些额外的开销,但允许您对包名称进行更多控制,并且还可以减少您在一个页面上引用的包的数量。 Of course, if you have to reference multiple 3rd party css files that contain relative image path references, you still can't get around creating multiple bundles. 当然,如果您必须引用包含相对图像路径引用的多个第三方css文件,您仍然无法绕过创建多个包。


#4楼

Better yet (IMHO) implement a custom Bundle that fixes the image paths. 更好的是(恕我直言)实现了一个修复图像路径的自定义Bundle。 I wrote one for my app. 我为我的应用写了一个。

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

}

To use it, do: 要使用它,请执行:

bundles.Add(new StyleImagePathBundle("~/bundles/css").Include(
  "~/This/Is/Some/Folder/Path/layout.css"));

...instead of... ...代替...

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>) . 它的作用是(当不处于调试模式时)查找url(<something>)并将其替换为url(<absolute\\path\\to\\something>) I wrote the thing about 10 seconds ago so it might need a little tweaking. 我在10秒前写了这个东西,所以可能需要稍微调整一下。 I've taken into account fully-qualified URLs and base64 DataURIs by making sure there's no colons (:) in the URL path. 我通过确保URL路径中没有冒号(:)来考虑完全限定的URL和base64 DataURI。 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 ). 在我们的环境中,图像通常与它们的css文件位于同一文件夹中,但我已经使用父文件夹( url(../someFile.png) )和子文件夹( url(someFolder/someFile.png ))对其进行了测试。


#5楼

Grinn solution is great. Grinn解决方案很棒。

However it doesn't work for me when there are parent folder relative references in the url. 但是,当url中存在父文件夹相对引用时,它对我不起作用。 ie url('../../images/car.png') url('../../images/car.png')

So, I slightly changed the Include method in order to resolve the paths for each regex match, allowing relative paths and also to optionally embed the images in the css. 因此,我稍微更改了Include方法,以便为每个正则表达式匹配解析路径,允许相对路径以及可选地将图像嵌入到css中。

I also changed the IF DEBUG to check BundleTable.EnableOptimizations instead of HttpContext.Current.IsDebuggingEnabled . 我还更改了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;
    }

Hope it helps, regards. 希望它有所帮助,问候。


#6楼

Grinn / ThePirat solution works well. Grinn / ThePirat解决方案效果很好。

I did not like that it new'd the Include method on bundle, and that it created temporary files in the content directory. 我不喜欢它在bundle上新的Include方法,并且它在内容目录中创建了临时文件。 (they ended up getting checked in, deployed, then the service wouldn't start!) (他们最终被检入,部署,然后服务无法启动!)

So to follow the design of Bundling, I elected to perform essentially the same code, but in an IBundleTransform implementation:: 因此,为了遵循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);
            }
        }
    }
}

And then wrapped this up in a Bundle Implemetation: 然后将其包装在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());
    }
}

Sample Usage: 样品用法:

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

Here is my extension method for RelativeFromAbsolutePath: 这是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(@"\", "/");
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值