이미지를 해석하지 않는 MVC4 StyleBundle


293

내 질문은 다음과 비슷합니다.

ASP.NET MVC 4 축소 및 배경 이미지

내가 할 수 있다면 MVC의 번들을 고수하고 싶다는 것을 제외하고. jQuery UI와 같은 독립 실행 형 CSS 및 이미지 세트와 같은 스타일 번들을 지정하기위한 올바른 패턴이 무엇인지 파악하려고 두뇌 충돌이 발생했습니다.

/Content/css/와 같은 기본 CSS를 포함 하는 일반적인 MVC 사이트 구조가 있습니다 styles.css. 해당 CSS 폴더 내에 /jquery-uiCSS 파일과 /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/있습니다.

내 질문은 이 상황을 처리 하는 올바른 방법무엇 입니까?


9
새로운 Bundling and Minification 부분 으로 계속해서 좌절 겪은 후 , 나는 Cassete 마녀 로 옮겼습니다. 이제 무료이며 더 잘 작동합니다!
balexandre

3
링크에 감사드립니다. Cassette는 멋져 보이고 확실히 확인할 것입니다. 그러나 가능한 경우 제공된 접근 방식을 고수하고 싶습니다. 새로운 버전이 출시 될 때마다 타사 CSS 파일의 이미지 경로를 망칠 필요없이 가능해야합니다. 지금은 ScriptBundles를 훌륭하게 유지했지만 해결책을 얻을 때까지 일반 CSS 링크로 되돌 렸습니다. 건배.
Tom W Hall

SEO 이유로 인해 발생할 수있는 오류 추가 : '/bundles/images/blah.jpg'경로의 컨트롤러를 찾을 수 없거나 IController를 구현하지 않습니다.
Luke Puplett 2016 년

답변:


361

MVC4 CSS 번들링 및 이미지 참조 의이 스레드에 따르면 번들을 다음과 같이 정의하면

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

번들을 구성하는 소스 파일과 동일한 경로에 번들을 정의하는 경우 상대 이미지 경로는 계속 작동합니다. 번들 경로의 마지막 부분은 실제로 file name해당 특정 번들에 대한 것입니다 (예 : /bundle원하는 이름이 될 수 있음).

이것은 동일한 폴더에서 CSS를 함께 묶을 때만 작동합니다 (번들 관점에서 의미가 있다고 생각합니다).

최신 정보

@Hao Kung의 아래 주석에 따라 또는 대안으로 CssRewriteUrlTransformation( 번들로 묶을 때 CSS 파일에 대한 상대 URL 참조 변경) 을 적용 하면됩니다 .

참고 : 가상 디렉터리 내에서 절대 경로를 다시 쓰는 문제에 대한 의견을 확인하지 않았으므로 모든 사람에게 적용되지 않을 수 있습니다 (?).

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

1
전설! 네, 그것은 완벽하게 작동합니다. CSS에는 다른 수준이 있지만 각각 고유 한 이미지 폴더가 있습니다. 예를 들어 내 기본 사이트 CSS는 루트 CSS 폴더에 있고 jquery-ui는 자체 이미지 폴더가있는 내부에 있으므로 2 개의 번들을 지정합니다. 기본 CSS와 jQuery UI 용 하나-요청 측면에서 최적이 아니지만 수명이 짧습니다. 건배!
Tom W Hall

3
예, 불행히도 번들링이 CSS 자체에 포함 된 URL을 다시 작성하는 기능을 지원할 때까지 번들링하기 전에 CSS 파일과 일치하도록 CSS 번들의 가상 디렉토리가 필요합니다. 이것이 기본 템플릿 번들에 ~ / bundles / themes와 같은 URL이없고 대신 디렉토리 구조처럼 보이는 이유입니다. ~ / content / theemes / base / css
Hao Kung

27
이것은 이제 ItemTransforms, .Include ( "~ / Content / css / jquery-ui / *. css", 새로운 CssRewriteUrlTransform ())); 1.1Beta1에서이 문제를 해결해야합니다
Hao Kung

2
이것은 Microsoft ASP.NET Web Optimization Framework 1.1.3에서 수정 되었습니까? 이 변경 사항에 대한 정보를 찾았습니까?
Andrus

13
IIS에 웹 사이트가 있으면 새로운 CssRewriteUrlTransform ()이 좋습니다. 그러나 응용 프로그램이나 하위 응용 프로그램의 경우 작동하지 않으므로 CSS와 동일한 위치에 번들을 정의해야합니다.
avidenic

34

Grinn / ThePirat 솔루션이 잘 작동합니다.

번들에서 Include 메소드를 새로 도입하고 컨텐츠 디렉토리에 임시 파일을 작성하는 것이 마음에 들지 않았습니다. (체크인되고 배포 된 다음 서비스가 시작되지 않았습니다!)

따라서 Bundling의 디자인을 따르기 위해 본질적으로 동일한 코드를 수행하지만 IBundleTransform 구현에서는 다음과 같이 선택했습니다.

class StyleRelativePathTransform
    : IBundleTransform
{
    public StyleRelativePathTransform()
    {
    }

    public void Process(BundleContext context, BundleResponse response)
    {
        response.Content = String.Empty;

        Regex pattern = new Regex(@"url\s*\(\s*([""']?)([^:)]+)\1\s*\)", RegexOptions.IgnoreCase);
        // open each of the files
        foreach (FileInfo cssFileInfo in response.Files)
        {
            if (cssFileInfo.Exists)
            {
                // apply the RegEx to the file (to change relative paths)
                string contents = File.ReadAllText(cssFileInfo.FullName);
                MatchCollection matches = pattern.Matches(contents);
                // Ignore the file if no match 
                if (matches.Count > 0)
                {
                    string cssFilePath = cssFileInfo.DirectoryName;
                    string cssVirtualPath = context.HttpContext.RelativeFromAbsolutePath(cssFilePath);
                    foreach (Match match in matches)
                    {
                        // this is a path that is relative to the CSS file
                        string relativeToCSS = match.Groups[2].Value;
                        // combine the relative path to the cssAbsolute
                        string absoluteToUrl = Path.GetFullPath(Path.Combine(cssFilePath, relativeToCSS));

                        // make this server relative
                        string serverRelativeUrl = context.HttpContext.RelativeFromAbsolutePath(absoluteToUrl);

                        string quote = match.Groups[1].Value;
                        string replace = String.Format("url({0}{1}{0})", quote, serverRelativeUrl);
                        contents = contents.Replace(match.Groups[0].Value, replace);
                    }
                }
                // copy the result into the response.
                response.Content = String.Format("{0}\r\n{1}", response.Content, contents);
            }
        }
    }
}

그리고 이것을 번들 구현에 싸서

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(@"\", "/");
    }

이것은 나에게도 가장 깨끗한 것 같습니다. 감사. 팀 노력 인 것처럼 보이기 때문에 여러분 모두에게 투표하고 있습니다. :)
Josh Mouch

현재 코드가 작동하지 않습니다. 나는 그것을 고치려고 노력하고 있지만, 당신에게 알려 주겠다고 생각했다. context.HttpContext.RelativeFromAbsolutePath 메소드가 존재하지 않습니다. 또한 URL 경로가 "/"로 시작하면 (절대적으로) 경로 결합 논리가 해제됩니다.
Josh Mouch

2
@AcidPAT 위대한 작품. URL에 쿼리 문자열이 있으면 논리가 실패했습니다 (일부 타사 라이브러리는 .woff 참조로 FontAwesome과 같이 추가합니다).하지만 쉽게 해결할 수 있습니다. relativeToCSS전화하기 전에 정규식이나 수정을 조정할 수 있습니다 Path.GetFullPath().
sergiopereira

2
@ChrisMarisic 코드가 작동하지 않는 것 같습니다. response.Files는 BundleFiles의 배열입니다.이 개체에는 "Exists", "DirectoryName"등과 같은 속성이 없습니다.
Nick Coad

2
@ChrisMarisic BundleFile 클래스의 확장 메소드를 제공하는 네임 스페이스가 있어야합니까?
Nick Coad

20

더 나은 아직 (IMHO) 이미지 경로를 수정하는 사용자 지정 번들을 구현하십시오. 내 앱용으로 썼습니다.

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(<something>)그것을 찾아서 바꿉니다 url(<absolute\path\to\something>). 나는 약 10 초 전에 물건을 썼으므로 약간 조정해야 할 수도 있습니다. URL 경로에 콜론 (:)이 없는지 확인하여 정규화 된 URL 및 base64 DataURI를 고려했습니다. 우리 환경에서 이미지는 일반적으로 CSS 파일과 동일한 폴더에 있지만 부모 폴더 ( url(../someFile.png))와 자식 폴더 ( url(someFolder/someFile.png)로 테스트했습니다 .


이것은 훌륭한 솔루션입니다. LESS 파일과 함께 작동하도록 Regex를 약간 수정했지만 원래 개념은 정확히 필요했습니다. 감사.
Tim Coulter

루프 외부에 정규식 초기화를 넣을 수도 있습니다. 정적 읽기 전용 속성 일 수 있습니다.
Miha Markic

12

변환을 지정하거나 하위 디렉토리 경로를 지정할 필요는 없습니다. 많은 문제 해결 후이 "단순한"규칙으로 분리했습니다 (버그입니까?) ...

번들 경로가 포함되는 항목의 상대 루트로 시작하지 않으면 웹 응용 프로그램 루트가 고려되지 않습니다.

나에게 더 많은 버그처럼 들리지만 어쨌든 현재 .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 / ... "

심지어 "번들"폴더를 만들어 기존 경로와 관련이 있는지 확인했지만 아무 것도 변경하지 않았습니다. 문제의 해결책은 실제로 번들 이름이 경로 루트로 시작해야한다는 요구 사항입니다.

이 예제는 번들 경로를 등록하고 렌더링하여 수정됩니다.

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

물론 이것이 RTFM이라고 말할 수는 있지만 저와 다른 사람들은 기본 템플릿이나 MSDN 또는 ASP.NET 웹 사이트의 문서에서이 "~ / Bundles / ..."경로를 선택했다고 확신합니다. 실제로 가상 경로의 논리적 이름이므로 실제 디렉토리와 충돌하지 않는 가상 경로를 선택하는 것이 좋습니다.

어쨌든, 그 방법입니다. 마이크로 소프트는 버그가 없다. 동의하지 않습니다. 예상대로 작동하거나 예외가 발생해야하거나 응용 프로그램 루트를 포함하도록 선택하거나 번들 경로를 추가하는 추가 재정의가 필요하지 않습니다. 응용 프로그램 루트가있을 때 왜 누군가가 응용 프로그램 루트를 포함하지 않으려는지 상상할 수 없습니다 (일반적으로 DNS 별칭 / 기본 웹 사이트 루트로 웹 사이트를 설치하지 않은 경우). 실제로 이것은 기본값이되어야합니다.


가장 단순한 "솔루션"인 것 같습니다. 다른 것들은 image : data와 같은 부작용을 가질 수 있습니다.
Fabrice

@MohamedEmaish 작동하지만 뭔가 잘못되었을 수 있습니다. 요청을 추적하는 방법에 대해 알아보십시오 (예 : Fiddler 도구를 사용하여 브라우저에서 요청한 URL 확인). 웹 사이트를 동일한 서버의 다른 위치 (루트 경로)에 설치하거나 많은 웹 사이트를 다시 쓰지 않고도 제품이 기본 URL을 변경할 수 있도록 전체 상대 경로를 하드 코딩하지 않는 것이 목표입니다. (응용 프로그램 루트 변수의 요점 및 지점).
Tony Wall

이 옵션을 사용하여 훌륭하게 작동했습니다. 각 번들에 단일 폴더의 항목 (다른 폴더 또는 하위 폴더의 항목은 포함 할 수 없음) 만 있어야합니다. 이는 약간 성가 시지만 작동하는 한 행복합니다! 게시물 주셔서 감사합니다.
hvaughan3

1
감사. 한숨. 언젠가 나는 스택을 탐색하는 것보다 실제로 코드를 작성하는 데 더 많은 시간을 보내고 싶습니다.
Bruce Pierson

폴더를 중첩 한 사용자 정의 jquery-ui와 비슷한 문제가 있습니다. 위와 같이 레벨을 올리 자마자 효과가있었습니다. 중첩 된 폴더를 좋아하지 않습니다.
Andrei Bazanov

11

*.css파일을 참조 *.min.css하고 동일한 폴더에 관련 파일 이 있으면 CssRewriteUrlTransform이 실행되지 않습니다 .

이 문제를 해결하려면 *.min.css파일을 삭제 하거나 번들에서 직접 참조하십시오.

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

그 후에는 URL이 올바르게 변환되고 이미지가 올바르게 분석되어야합니다.


1
감사합니다! 온라인 검색 이틀 후, 이것은 * .css 파일로 작업하는 CssRewriteUrlTransform의 어느 곳에서나 처음 보았던 언급이지만 디버그에서 실행되지 않을 때 가져온 관련 * .min.css 파일에서는 그렇지 않습니다. 환경. 분명히 나에게 벌레처럼 보인다. 디버깅을 위해 최소화되지 않은 버전의 번들을 정의하기 위해 환경 유형을 수동으로 확인해야하지만 적어도 해결 방법이 있습니다!
Sean

1
이것은 나를 위해 문제를 해결했습니다. 이것은 분명히 버그처럼 보입니다. 기존의 .min.css 파일을 찾으면 CssRewriteUrlTransform을 무시해야한다는 것은 의미가 없습니다.
user1751825

10

어쩌면 나는 편견이 있지만 변환, 정규식 등을하지 않고 코드가 가장 적기 때문에 솔루션을 좋아합니다. :)

이것은 IIS 웹 사이트에서 가상 디렉터리 로 호스팅되는 사이트와 IIS에서 루트 웹 사이트로 작동합니다.

그래서 IItemTransform캡슐화 된 Implentation of the를 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
이것은 나에게 완벽하게 어울립니다. 탁월한 솔루션. 내 투표는 +1입니다
imdadhusen

1
이것이 정답입니다. 프레임 워크에서 제공하는 CssUrlTransformWrapper 클래스는 응용 프로그램이 웹 사이트 루트에 있지 않은 경우에만 작동하지 않는 것을 제외하고는 문제를 해결합니다. 이 래퍼는 그 단점을 간결하게 해결합니다.
Nine Tails

7

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

2
좋은 해결책이지만 CSS에 데이터 URI (예 : "data : image / png; base64, ...")가 있으면 여전히 실패합니다 (CssRewriteUrlTransform과 동일). RebaseUrlToAbsolute ()에서 "data :"로 시작하는 URL을 변경해서는 안됩니다.
miles82

1
물론 @ miles82! 이것을 지적 해 주셔서 감사합니다. RebaseUrlToAbsolute ()를 변경했습니다.
음욕

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로 바꾸면 어떻게됩니까?
Fabrice

6

다음은 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

@Stiger 변경 css.FullName.Replace (를 css.VirtualFile.VirtualPath.Replace (
lkurylo

나는 이것을 잘못 사용하고 있지만 매번 반복 할 때마다 모든 URL을 다시 작성하고 마지막으로 본 CSS 파일을 기준으로 남겨 둡니다.
Andyrooger

4

또 다른 옵션은 IIS URL 다시 쓰기 모듈을 사용하여 가상 번들 이미지 폴더를 실제 이미지 폴더에 맵핑하는 것입니다. 아래는 "~ / bundles / yourpage / styles"라는 번들에 사용할 수있는 다시 쓰기 규칙의 예입니다. 정규식은 이미지 파일 이름에서 일반적으로 사용되는 하이픈, 밑줄 및 마침표뿐만 아니라 영숫자 문자와 일치합니다. .

<rewrite>
  <rules>
    <rule name="Bundle Images">
      <match url="^bundles/yourpage/images/([a-zA-Z0-9\-_.]+)" />
      <action type="Rewrite" url="Content/css/jquery-ui/images/{R:1}" />
    </rule>
  </rules>
</rewrite>

이 방법을 사용하면 약간의 추가 오버 헤드가 발생하지만 번들 이름을보다 강력하게 제어 할 수 있으며 한 페이지에서 참조해야하는 번들 수도 줄어 듭니다. 물론 상대 이미지 경로 참조가 포함 된 여러 타사 CSS 파일을 참조해야하는 경우에도 여러 번들을 만들 수 없습니다.


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

도움이되기를 바랍니다.


2

가상 번들 경로에 다른 깊이를 추가 할 수 있습니다.

    //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에서 웹 응용 프로그램을 가상 응용 프로그램으로 사용하는 경우에는 도움이되지 않습니다. 나는 그것이 작동 할 수는 있지만 IIS 가상 응용 프로그램의 이름을 코드에서와 같이 지정해야합니다.
psulek

응용 프로그램이 IIS의 가상 응용 프로그램 인 경우에도 동일한 문제가 있습니다. 이 답변이 도움이됩니다.
BILL

2

이미지에 대한 경로가 올바르지 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(char)String.StartsWith(string).


문자열을 받아들이는 String.Count () 오버로드 m.Groups[2].Value.Count("..")가 없습니다 (작동하지 않습니다). Value.StartsWith('/')StartsWith는 char 대신 문자열을 기대하기 때문에 작동하지 않습니다.
jao

@ jao 내 나쁜 나는 그것을 실현하지 않고 코드에 내 자신의 확장 메소드를 포함시켰다.
jahu

1
@jao는 이러한 확장 메소드의 소스 코드를 답변에 추가했습니다.
jahu

1

조금만 조사한 결과 다음과 같은 결론을 얻었습니다. 두 가지 옵션이 있습니다.

  1. 변화와 함께 가십시오. 이것에 매우 유용한 패키지 : https://bundletransformer.codeplex.com/ 문제가있는 모든 번들에 대해 다음과 같은 변환이 필요합니다.

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

장점 :이 솔루션 중에서 원하는대로 번들 이름을 지정할 수 있습니다. => CSS 파일을 다른 디렉토리에서 하나의 번들로 결합 할 수 있습니다. 단점 : 문제가있는 모든 번들을 변환해야합니다.

  1. css 파일이있는 위치와 같이 번들 이름에 동일한 상대 루트를 사용하십시오. 장점 : 변환 할 필요가 없습니다. 단점 : 다른 디렉토리의 CSS 시트를 하나의 번들로 결합하는 데 제한이 있습니다.

0

CssRewriteUrlTransform내 문제를 해결했다.
를 사용한 후에도 코드에서 이미지를로드하지 않으면 CssRewriteUrlTransformCSS 파일 이름을 다음에서 변경하십시오.

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

에:

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

어쨌든. (점)이 URL에서 인식되지 않습니다.


0

다음 과 같은 번들로 여러 CSS 포함 을 수정 해야합니다.

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

new CssRewriteUrlTransform()메소드가 지원하지 않기 때문에 하나의 CSS 파일로 할 수있는 것처럼 끝에 추가 할 수 없으므로 여러 번 사용해야 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()));
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.