내 질문은 다음과 비슷합니다.
내가 할 수 있다면 MVC의 번들을 고수하고 싶다는 것을 제외하고. jQuery UI와 같은 독립 실행 형 CSS 및 이미지 세트와 같은 스타일 번들을 지정하기위한 올바른 패턴이 무엇인지 파악하려고 두뇌 충돌이 발생했습니다.
/Content/css/
와 같은 기본 CSS를 포함 하는 일반적인 MVC 사이트 구조가 있습니다 styles.css
. 해당 CSS 폴더 내에 /jquery-ui
CSS 파일과 /images
폴더 가 포함 된 하위 폴더도 있습니다 . jQuery UI CSS의 이미지 경로는 해당 폴더와 관련이 있으며 폴더를 엉망으로 만들고 싶지 않습니다.
내가 이해할 때, 내가 지정할 때 StyleBundle
실제 콘텐츠 경로와 일치하지 않는 가상 경로를 지정해야합니다. 콘텐츠에 대한 경로를 무시한다고 가정하면 IIS는 해당 경로를 실제 파일로 해석하려고 시도하기 때문입니다. 그래서 나는 지정하고있다 :
bundles.Add(new StyleBundle("~/Content/styles/jquery-ui")
.Include("~/Content/css/jquery-ui/*.css"));
사용하여 렌더링 :
@Styles.Render("~/Content/styles/jquery-ui")
요청이 다음과 같이 진행되는 것을 볼 수 있습니다.
http://localhost/MySite/Content/styles/jquery-ui?v=nL_6HPFtzoqrts9nwrtjq0VQFYnhMjY5EopXsK8cxmg1
이것은 정확하고 축소 된 CSS 응답을 반환합니다. 그러나 브라우저는 다음과 같이 상대적으로 링크 된 이미지에 대한 요청을 보냅니다.
http://localhost/MySite/Content/styles/images/ui-bg_highlight-soft_100_eeeeee_1x100.png
어느 것입니다 404
.
내 URL의 마지막 부분은 jquery-ui
확장이없는 URL이며 내 번들의 핸들러이므로 이미지에 대한 상대 요청이 왜 간단한 지 알 수 /styles/images/
있습니다.
내 질문은 이 상황을 처리 하는 올바른 방법 은 무엇 입니까?
답변
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" ) );