[css] 이미지를 해석하지 않는 MVC4 StyleBundle

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

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/있습니다.

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



답변

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


답변

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


답변

더 나은 아직 (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)로 테스트했습니다 .


답변

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

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

나에게 더 많은 버그처럼 들리지만 어쨌든 현재 .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 별칭 / 기본 웹 사이트 루트로 웹 사이트를 설치하지 않은 경우). 실제로 이것은 기본값이되어야합니다.


답변

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

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

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

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


답변

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

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

나에게 잘 작동하는 것 같습니까?


답변

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