MVC4 StyleBundle không phân giải hình ảnh


293

Câu hỏi của tôi tương tự như thế này:

ASP.NET MVC 4 Thu nhỏ & hình ảnh nền

Ngoại trừ việc tôi muốn gắn bó với gói riêng của MVC nếu có thể. Tôi đang gặp sự cố về não khi cố gắng tìm ra mẫu chính xác là gì để chỉ định các gói kiểu như bộ css và hình ảnh độc lập như UI UI hoạt động.

Tôi có một cấu trúc trang web MVC điển hình /Content/css/chứa CSS cơ sở của tôi như styles.css. Trong thư mục css đó, tôi cũng có các thư mục con như /jquery-uichứa tệp CSS của nó cộng với một /imagesthư mục. Đường dẫn hình ảnh trong CSS UI UI có liên quan đến thư mục đó và tôi không muốn gây rối với chúng.

Theo tôi hiểu, khi tôi chỉ định một StyleBundletôi cần chỉ định một đường dẫn ảo không khớp với đường dẫn nội dung thực sự, bởi vì (giả sử tôi bỏ qua các tuyến đường đến Nội dung) thì IIS sẽ cố gắng giải quyết đường dẫn đó dưới dạng tệp vật lý. Vì vậy, tôi đang chỉ định:

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

kết xuất bằng:

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

Tôi có thể thấy yêu cầu đi ra:

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

Điều này đang trả về phản hồi CSS chính xác, rút ​​gọn. Nhưng sau đó trình duyệt sẽ gửi yêu cầu cho một hình ảnh được liên kết tương đối là:

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

Đó là một 404.

Tôi hiểu rằng phần cuối của URL của tôi jquery-uilà một URL không mở rộng, một trình xử lý cho gói của tôi, vì vậy tôi có thể thấy lý do tại sao yêu cầu tương đối cho hình ảnh chỉ đơn giản là /styles/images/.

Vì vậy, câu hỏi của tôi là cách xử lý tình huống này là gì?


9
Sau khi thất vọng hết lần này đến lần khác với phần Gói và Giảm thiểu mới, tôi chuyển sang phù thủy Cassete bây giờ miễn phí và hoạt động tốt hơn!
balexandre

3
Cảm ơn vì liên kết, Cassette trông rất đẹp và tôi chắc chắn sẽ kiểm tra nó. Nhưng tôi muốn tuân theo cách tiếp cận được cung cấp nếu có thể, chắc chắn điều này phải có thể thực hiện được mà không làm rối loạn đường dẫn hình ảnh trong các tệp CSS của bên thứ 3 mỗi khi phiên bản mới được phát hành. bây giờ tôi đã giữ ScriptBundles của mình (hoạt động tốt) nhưng được hoàn nguyên về các liên kết CSS đơn giản cho đến khi tôi nhận được độ phân giải. Chúc mừng.
Hội trường Tom W

Thêm lỗi có khả năng vì lý do SEO: Bộ điều khiển cho đường dẫn '/bundles/images/blah.jpg' không được tìm thấy hoặc không triển khai IControll.
Luke Puplett

Câu trả lời:


361

Theo chủ đề này trên gói tham chiếu hình ảnh và css MVC4 , nếu bạn xác định gói của mình là:

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

Khi bạn xác định gói trên cùng một đường dẫn với các tệp nguồn tạo ra gói đó, các đường dẫn hình ảnh tương đối sẽ vẫn hoạt động. Phần cuối cùng của đường dẫn gói thực sự là phần file namedành cho gói cụ thể đó (nghĩa là /bundlecó thể là bất kỳ tên nào bạn thích).

Điều này sẽ chỉ hoạt động nếu bạn kết hợp CSS với nhau từ cùng một thư mục (mà tôi nghĩ có ý nghĩa từ góc độ gói).

Cập nhật

Theo nhận xét dưới đây của @Hao Kung, hiện tại có thể đạt được điều này bằng cách áp dụng một CssRewriteUrlTransformation( Thay đổi tham chiếu URL tương đối cho các tệp CSS khi được đóng gói ).

LƯU Ý: Tôi chưa xác nhận các nhận xét liên quan đến các vấn đề với việc viết lại các đường dẫn tuyệt đối trong một thư mục ảo, vì vậy điều này có thể không hiệu quả với tất cả mọi người (?).

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

1
Huyền thoại! Đúng, hoạt động hoàn hảo. Tôi có CSS ​​ở các cấp độ khác nhau nhưng mỗi cấp độ đều có thư mục hình ảnh riêng, ví dụ CSS trang web chính của tôi nằm trong thư mục CSS gốc và sau đó jquery-ui nằm trong đó với thư mục hình ảnh riêng, vì vậy tôi chỉ định 2 gói, một gói cho tôi CSS cơ bản và một cho UI UI - có thể không tối ưu về mặt yêu cầu, nhưng tuổi thọ thì ngắn. Chúc mừng!
Hội trường Tom W

3
Thật không may cho đến khi gói có hỗ trợ để viết lại các url được nhúng bên trong chính css, bạn cần thư mục ảo của gói css để khớp với các tệp css trước khi gói. Đây là lý do tại sao các gói mẫu mặc định không có các url như ~ / bó / chủ đề, và thay vào đó trông giống như cấu trúc thư mục: ~ / content / theeme / base / css
Hao Kung

27
Điều này hiện được hỗ trợ thông qua ItemTransforms, .Include ("~ / Content / css / jquery-ui / *. Css", CssRewriteUrlTransform ())); trong 1.1Beta1 nên khắc phục sự cố này
Hao Kung

2
Điều này có cố định trong Microsoft ASP.NET Web Optimization Framework 1.1.3 không? Tôi đã tìm thấy bất kỳ thông tin về những gì được thay đổi trong này?
Andrus

13
CssRewriteUrlTransform () mới là tốt nếu bạn có một trang web trong IIS. nhưng nếu đó là một ứng dụng hoặc ứng dụng phụ thì nó sẽ không hoạt động và bạn phải dùng đến việc xác định gói của bạn ở cùng vị trí với CSS của bạn.
avidenic

34

Giải pháp Grinn / ThePirat hoạt động tốt.

Tôi không thích rằng nó mới là phương thức Bao gồm trên gói và nó đã tạo các tệp tạm thời trong thư mục nội dung. (cuối cùng họ đã được đăng ký, triển khai, sau đó dịch vụ sẽ không bắt đầu!)

Vì vậy, để tuân theo thiết kế của Gói, tôi đã chọn thực hiện cùng một mã, nhưng trong triển khai 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);
            }
        }
    }
}

Và sau đó gói nó trong một Ý nghĩa gói:

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

Sử dụng mẫu:

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

Đây là phương pháp mở rộng của tôi cho 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(@"\", "/");
    }

Điều này có vẻ sạch sẽ nhất đối với tôi, quá. Cảm ơn. Tôi đang bầu chọn cả ba bạn vì đây có vẻ là một nỗ lực của nhóm. :)
Josh Mouch

Mã như bạn có bây giờ không hoạt động cho tôi. Tôi đang cố gắng sửa nó, nhưng nghĩ rằng tôi sẽ cho bạn biết. Phương thức.H.HContContext.RelativeFromAbsolutePath không tồn tại. Ngoài ra, nếu đường dẫn url bắt đầu bằng "/" (làm cho nó tuyệt đối), đường dẫn kết hợp logic của bạn sẽ bị tắt.
Josh Mouch

2
@AcidPAT công việc tuyệt vời. Logic không thành công nếu url có một chuỗi truy vấn (một số thư viện bên thứ 3 thêm nó, như FontAwgie cho tham chiếu .woff của nó.) Mặc dù vậy, đây là một sửa chữa dễ dàng. Người ta có thể điều chỉnh Regex hoặc sửa relativeToCSStrước khi gọi Path.GetFullPath().
sergiopereira

2
@ChrisMarisic mã của bạn dường như không hoạt động - answer.Files là một mảng của BundleFiles, các đối tượng này không có các thuộc tính như "Exists", "DirectoryName", v.v.
Nick Coad

2
@ChrisMarisic có lẽ tôi nên nhập một không gian tên cung cấp các phương thức mở rộng cho lớp BundleFile?
Nick Coad

20

Tốt hơn nữa (IMHO) triển khai Gói tùy chỉnh sửa các đường dẫn hình ảnh. Tôi đã viết một cho ứng dụng của tôi.

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

}

Để sử dụng nó, hãy làm:

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

...thay vì...

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

Những gì nó làm là (khi không ở chế độ gỡ lỗi) tìm kiếm url(<something>)và thay thế nó bằng url(<absolute\path\to\something>). Tôi đã viết điều này khoảng 10 giây trước vì vậy nó có thể cần một chút điều chỉnh. Tôi đã tính đến các URL đủ điều kiện và các cơ sở dữ liệu64 bằng cách đảm bảo không có dấu hai chấm (:) trong đường dẫn URL. Trong môi trường của chúng tôi, hình ảnh thường nằm trong cùng một thư mục với các tệp css của chúng, nhưng tôi đã kiểm tra nó với cả thư mục mẹ ( url(../someFile.png)) và thư mục con ( url(someFolder/someFile.png).


Đây là một giải pháp tuyệt vời. Tôi đã sửa đổi Regex của bạn một chút để nó cũng hoạt động với các tệp LESS, nhưng khái niệm ban đầu là chính xác những gì tôi cần. Cảm ơn.
Tim Coulter

Bạn cũng có thể đặt khởi tạo regex bên ngoài vòng lặp. Có lẽ như một tài sản chỉ đọc tĩnh.
Miha Markic

12

Không cần thiết phải chỉ định một biến đổi hoặc có các đường dẫn thư mục con điên. Sau nhiều lần khắc phục sự cố, tôi đã tách nó thành quy tắc "đơn giản" này (nó có phải là một lỗi không?) ...

Nếu đường dẫn gói của bạn không bắt đầu bằng gốc tương đối của các mục được bao gồm, thì gốc ứng dụng web sẽ không được tính đến.

Nghe có vẻ như là một lỗi với tôi, nhưng dù sao đó cũng là cách bạn sửa nó với phiên bản .NET 4.51 hiện tại. Có lẽ các câu trả lời khác là cần thiết trên các bản dựng ASP.NET cũ hơn, không thể nói rằng không có thời gian để kiểm tra lại tất cả những điều đó.

Để làm rõ, đây là một ví dụ:

Tôi có những tập tin này ...

~/Content/Images/Backgrounds/Some_Background_Tile.gif
~/Content/Site.css  - references the background image relatively, i.e. background: url('Images/...')

Sau đó thiết lập gói như ...

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

Và kết xuất nó như ...

@Styles.Render("~/Bundles/Styles")

Và nhận được "hành vi" (lỗi), bản thân các tệp CSS có gốc ứng dụng (ví dụ: "http: // localhost: 1234 / MySite / Content / Site.css") nhưng hình ảnh CSS trong tất cả bắt đầu "/ Content / Images / ... "Hoặc" / Hình ảnh / ... "tùy thuộc vào việc tôi có thêm biến đổi hay không.

Thậm chí đã thử tạo thư mục "Gói" để xem liệu nó có liên quan đến đường dẫn hiện có hay không, nhưng điều đó không thay đổi gì cả. Giải pháp cho vấn đề thực sự là yêu cầu tên của gói phải bắt đầu bằng đường dẫn gốc.

Có nghĩa là ví dụ này được sửa bằng cách đăng ký và hiển thị đường dẫn gói như ..

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

Vì vậy, tất nhiên bạn có thể nói đây là RTFM, nhưng tôi khá chắc chắn rằng tôi và những người khác đã chọn đường dẫn "~ / Gói / ..." này từ mẫu mặc định hoặc ở đâu đó trong tài liệu tại trang web MSDN hoặc ASP.NET, hoặc chỉ vấp phải nó bởi vì thực sự đó là một cái tên khá logic cho một đường dẫn ảo và thật hợp lý khi chọn những đường dẫn ảo như vậy không xung đột với các thư mục thực.

Dù sao, đó là cách nó được. Microsoft thấy không có lỗi. Tôi không đồng ý với điều này, nó sẽ hoạt động như mong đợi hoặc một số ngoại lệ nên được ném hoặc ghi đè bổ sung để thêm đường dẫn gói có thể bao gồm gốc ứng dụng hay không. Tôi không thể tưởng tượng được tại sao mọi người không muốn root ứng dụng đi kèm khi có một (thông thường trừ khi bạn cài đặt trang web của mình với bí danh DNS / gốc trang web mặc định). Vì vậy, thực sự đó nên là mặc định.


Dường như với tôi "giải pháp" đơn giản nhất. Những cái khác có thể có tác dụng phụ, như với hình ảnh: dữ liệu.
Fabrice

@MohamedEmaish nó hoạt động, bạn có thể có một cái gì đó sai. Tìm hiểu cách theo dõi các yêu cầu, ví dụ: sử dụng Công cụ Fiddler để xem URL nào đang được trình duyệt yêu cầu. Mục tiêu không phải là mã cứng toàn bộ đường dẫn tương đối để trang web của bạn có thể được cài đặt ở các vị trí khác nhau (đường dẫn gốc) trên cùng một máy chủ hoặc sản phẩm của bạn có thể thay đổi URL mặc định mà không phải viết lại nhiều trang web (điểm có và biến ứng dụng gốc).
Tony Wall

Đã đi với tùy chọn này và nó đã làm việc tuyệt vời. Phải đảm bảo mỗi gói chỉ có các mục từ một thư mục duy nhất (không thể bao gồm các mục từ các thư mục hoặc thư mục con khác), điều này hơi khó chịu nhưng miễn là nó hoạt động tôi rất vui! Cảm ơn vì bài đăng.
hvaughan3

1
Cảm ơn. Thở dài. Một ngày nào đó tôi muốn dành nhiều thời gian hơn để viết mã hơn là duyệt Stack.
Bruce Pierson

Tôi gặp vấn đề tương tự khi một jquery-ui tùy chỉnh có các thư mục lồng nhau. Ngay khi tôi tăng cấp mọi thứ như trên, nó đã hoạt động. Nó không thích các thư mục lồng nhau.
Andrei Bazanov

11

Tôi thấy rằng CssRewriteUrlTransform không chạy được nếu bạn đang tham chiếu một *.csstệp và bạn có *.min.csstệp được liên kết trong cùng một thư mục.

Để khắc phục điều này, hãy xóa *.min.csstệp hoặc tham chiếu trực tiếp trong gói của bạn:

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

Sau đó, bạn làm điều đó, URL của bạn sẽ được chuyển đổi chính xác và hình ảnh của bạn sẽ được giải quyết chính xác.


1
Cảm ơn bạn! Sau hai ngày tìm kiếm trực tuyến, đây là lần đầu tiên tôi thấy bất cứ nơi nào về CssRewriteUrlTransform hoạt động với các tệp * .css, nhưng không phải với tệp * .min.css được kéo vào khi bạn không chạy trong gỡ lỗi Môi trường. Chắc chắn có vẻ như là một lỗi với tôi. Sẽ phải kiểm tra thủ công loại môi trường để xác định một gói với phiên bản chưa hoàn thành để gỡ lỗi, nhưng ít nhất bây giờ tôi có một cách giải quyết!
Sean

1
Điều này đã khắc phục vấn đề cho tôi. Điều này chắc chắn có vẻ như là một lỗi. Không có nghĩa là nó nên bỏ qua CssRewriteUrlTransform nếu nó tìm thấy tệp .min.css tồn tại trước đó.
dùng1751825

10

Có thể tôi thiên vị, nhưng tôi khá thích giải pháp của mình vì nó không thực hiện bất kỳ chuyển đổi nào, regex, v.v. và nó có số lượng mã ít nhất :)

Điều này hoạt động cho một trang web được lưu trữ dưới dạng Thư mục ảo trong Trang web IIS và như một trang web gốc trên IIS

Vì vậy, tôi đã tạo ra một Implentation của IItemTransformđóng gói CssRewriteUrlTransformvà được sử dụng VirtualPathUtilityđể sửa đường dẫn và gọi mã hiện có:

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

Có vẻ làm việc tốt cho tôi?


1
Đây là bộ hoàn hảo cho tôi. giải pháp tuyệt vời. phiếu bầu của tôi là +1
imdadhusen

1
Đây là câu trả lời chính xác. Lớp CssUrlTransformWrapper được cung cấp bởi khung giải quyết vấn đề, ngoại trừ nó không hoạt động chỉ khi ứng dụng không ở gốc trang web. Bao bọc này ngắn gọn giải quyết thiếu sót.
Cửu Vĩ

7

Mặc dù câu trả lời của Chris Baxter giúp giải quyết vấn đề ban đầu, nhưng nó không hoạt động trong trường hợp của tôi khi ứng dụng được lưu trữ trong thư mục ảo . Sau khi điều tra các tùy chọn, tôi đã hoàn thành với giải pháp DIY.

ProperStyleBundlelớp bao gồm mã được mượn từ bản gốc CssRewriteUrlTransformđể chuyển đổi chính xác các đường dẫn tương đối trong thư mục ảo. Nó cũng ném nếu tệp không tồn tại và ngăn chặn sắp xếp lại các tệp trong gói (mã được lấy từ 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 ) + ")" ) );
            }
        }
    }
}

Sử dụng nó như 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" ) );

2
Giải pháp tuyệt vời, nhưng vẫn thất bại (giống như CssRewriteUrlTransform) nếu bạn có URI dữ liệu trong CSS (ví dụ: "data: image / png; base64, ..."). Bạn không nên thay đổi url bắt đầu bằng "data:" trong RebaseUrlToAbsolute ().
miles82

1
@ miles82 Dĩ nhiên! Cảm ơn đã chỉ ra điều này. Tôi đã thay đổi RebaseUrlToAbsolute ().
nrodic

6

Kể từ v1.1.0-alpha1 (gói phát hành trước), khung sử dụng VirtualPathProviderđể truy cập các tệp thay vì chạm vào hệ thống tệp vật lý.

Các biến áp cập nhật có thể được nhìn thấy dưới đây:

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

Trên thực tế, điều này sẽ làm gì nếu thay thế các URL tương đối trong CSS bằng các URL tuyệt đối.
Fabrice

6

Đây là một Bundle Transform sẽ thay thế các url css bằng các url liên quan đến tệp css đó. Chỉ cần thêm nó vào gói của bạn và nó sẽ khắc phục vấn đề.

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

}

Làm thế nào để sử dụng nó?, Nó cho tôi thấy một ngoại lệ:cannot convert type from BundleFile to FileInfo
Stiger

@Stiger thay đổi css.FullName.Replace (thành css.VirtualFile.VirtualPath.Replace (
lkurylo 17/11/14

Tôi có thể đang sử dụng sai, nhưng điều đó có thể viết lại tất cả các url trên mỗi lần lặp và để chúng liên quan đến tệp css cuối cùng mà nó đã thấy không?
Andyrooger

4

Một tùy chọn khác là sử dụng mô-đun Viết lại URL của IIS để ánh xạ thư mục hình ảnh bó ảo vào thư mục hình ảnh vật lý. Dưới đây là một ví dụ về quy tắc viết lại từ đó bạn có thể sử dụng cho gói có tên "~ / bundles / yourpage / style" - lưu ý regex khớp với các ký tự chữ và số cũng như dấu gạch ngang, dấu gạch dưới và dấu chấm, phổ biến trong tên tệp hình ảnh .

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

Cách tiếp cận này tạo thêm một chút chi phí, nhưng cho phép bạn có nhiều quyền kiểm soát hơn đối với tên gói của mình và cũng giảm số lượng gói bạn có thể phải tham khảo trên một trang. Tất nhiên, nếu bạn phải tham chiếu nhiều tệp css của bên thứ 3 có chứa các tham chiếu đường dẫn hình ảnh tương đối, bạn vẫn không thể bắt đầu tạo nhiều gói.


4

Giải pháp Grinn là tuyệt vời.

Tuy nhiên, nó không hoạt động với tôi khi có các tham chiếu tương đối của thư mục cha trong url. I Eurl('../../images/car.png')

Vì vậy, tôi đã thay đổi một chút Includephương thức để giải quyết các đường dẫn cho từng đối sánh regex, cho phép các đường dẫn tương đối và cũng có thể tùy ý nhúng các hình ảnh vào css.

Tôi cũng đã thay đổi IF DEBUG để kiểm tra BundleTable.EnableOptimizationsthay vì 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;
    }

Hy vọng nó sẽ giúp, liên quan.


2

Bạn chỉ có thể thêm một mức độ sâu khác vào đường dẫn gói ảo của mình

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

Đây là một câu trả lời siêu công nghệ thấp và một loại hack nhưng nó hoạt động và sẽ không yêu cầu bất kỳ quá trình tiền xử lý nào. Với độ dài và độ phức tạp của một số câu trả lời này, tôi thích làm theo cách này.


Điều này không giúp ích gì khi bạn có ứng dụng web là ứng dụng ảo trong IIS. Ý tôi là nó có thể hoạt động nhưng bạn phải đặt tên cho ứng dụng ảo IIS của bạn như trong mã của bạn, đây không phải là điều bạn muốn, phải không?
psulek

Tôi cũng gặp vấn đề tương tự khi ứng dụng là ứng dụng ảo trong IIS. Câu trả lời này giúp tôi.
HÓA ĐƠN

2

Tôi gặp vấn đề này với các gói có đường dẫn không chính xác đến hình ảnh và CssRewriteUrlTransformkhông giải quyết ..chính xác các đường dẫn cha mẹ tương đối (cũng có vấn đề với các tài nguyên bên ngoài như webfont). Đó là lý do tại sao tôi đã viết biến đổi tùy chỉnh này (dường như thực hiện chính xác tất cả các điều trên):

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

Chỉnh sửa: Tôi đã không nhận ra nó, nhưng tôi đã sử dụng một số phương thức mở rộng tùy chỉnh trong mã. Mã nguồn của chúng là:

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

Tất nhiên nó nên có thể thay thế String.StartsWith(char)bằng String.StartsWith(string).


Tôi không có quá tải String.Count () chấp nhận một chuỗi ( m.Groups[2].Value.Count("..")không hoạt động.) Và Value.StartsWith('/')cũng không hoạt động vì StartsWith mong đợi một chuỗi thay vì char.
Jao

@jao xấu của tôi Tôi đã bao gồm các phương thức mở rộng của riêng tôi trong mã mà không nhận ra nó.
2014

1
@jao đã thêm mã nguồn của các phương thức mở rộng đó vào câu trả lời.
2014

1

Sau khi điều tra ít, tôi đã kết luận như sau: Bạn có 2 lựa chọn:

  1. đi với biến đổi. Gói rất hữu ích cho việc này: https://bundletransformer.codeplex.com/ bạn cần chuyển đổi sau cho mỗi gói có vấn đề:

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

Ưu điểm: của giải pháp này, bạn có thể đặt tên cho gói của mình bất cứ điều gì bạn muốn => bạn có thể kết hợp các tệp css thành một gói từ các thư mục khác nhau. Nhược điểm: Bạn cần chuyển đổi mọi gói có vấn đề

  1. Sử dụng cùng một gốc tương đối cho tên của gói giống như vị trí của tệp css. Ưu điểm: không cần chuyển đổi. Nhược điểm: Bạn có giới hạn trong việc kết hợp các tờ css từ các thư mục khác nhau thành một gói.

0

CssRewriteUrlTransformđã khắc phục sự cố của tôi.
Nếu mã của bạn vẫn không tải hình ảnh sau khi sử dụng CssRewriteUrlTransform, thì hãy thay đổi tên tệp css của bạn từ:

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

Đến:

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

Một lúc nào đó (dấu chấm) không nhận ra trong url.


0

Chỉ cần nhớ sửa nhiều vùi CSS trong một gói, chẳng hạn như:

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

Bạn không thể chỉ thêm new CssRewriteUrlTransform()vào cuối như bạn có thể với một tệp CSS vì phương thức này không hỗ trợ, vì vậy bạn phải sử dụng Includenhiều lần :

bundles.Add(new StyleBundle("~/Content/styles/jquery-ui")
    .Include("~/Content/css/path1/somestyle1.css", new CssRewriteUrlTransform())
    .Include("~/Content/css/path2/somestyle2.css", new CssRewriteUrlTransform()));
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.