BundleCollection이 MVC4에서 캐시 된 스크립트 번들을 플러시하도록하는 방법


85

... 또는 내가 어떻게 걱정을 멈추고 마이크로 소프트의 완전히 문서화되지 않은 API에 대해 코드를 작성하는 법을 배웠습니다 . 공식 System.Web.Optimization릴리스에 대한 실제 문서가 있습니까? 'Cuz 나는 확실히 찾을 수없고, XML 문서도없고, 모든 블로그 게시물은 상당히 다른 RC API를 참조합니다. 아니 후 ..

자바 스크립트 종속성을 자동으로 해결하는 코드를 작성 중이며 해당 종속성에서 즉시 번들을 생성하고 있습니다. 스크립트를 편집하거나 응용 프로그램을 다시 시작하지 않고 번들에 영향을주는 변경 사항을 적용하는 경우를 제외하고 모든 것이 잘 작동합니다. 변경 사항이 반영되지 않습니다. 그래서 개발에 사용할 종속성 캐싱을 비활성화하는 옵션을 추가했습니다.

그러나 번들 컬렉션이 변경된 경우에도 분명히 BundleTablesURL을 캐시합니다 . 예를 들어 번들을 다시 만들고 싶을 때 내 코드에서 다음과 같이합니다.

// remove an existing bundle
BundleTable.Bundles.Remove(BundleTable.Bundles.GetBundleFor(bundleAlias));

// recreate it.
var bundle = new ScriptBundle(bundleAlias);

// dependencies is a collection of objects representing scripts, 
// this creates a new bundle from that list. 

foreach (var item in dependencies)
{
    bundle.Include(item.Path);
}

// add the new bundle to the collection

BundleTable.Bundles.Add(bundle);

// bundleAlias is the same alias used previously to create the bundle,
// like "~/mybundle1" 

var bundleUrl = BundleTable.Bundles.ResolveBundleUrl(bundleAlias);

// returns something like "/mybundle1?v=hzBkDmqVAC8R_Nme4OYZ5qoq5fLBIhAGguKa28lYLfQ1"

별칭이 같은 번들 제거하고 다시 만들 때마다 아무 일도 일어나지 않습니다. bundleUrl반환 된 항목 ResolveBundleUrl은 번들을 제거하고 다시 생성하기 전과 동일합니다. "동일"이란 번들의 새 콘텐츠를 반영하기 위해 콘텐츠 해시가 변경되지 않음을 의미합니다.

편집 ... 사실, 그것은 그것보다 훨씬 더 나쁩니다. 번들 자체가 어떻게 든 외부의 캐시 Bundles모음입니다. 브라우저가 스크립트를 캐싱하지 못하도록 내 임의의 해시를 생성하면 ASP.NET은 이전 스크립트를 반환 합니다 . 따라서 분명히 번들을 제거하는 BundleTable.Bundles것은 실제로 아무것도하지 않습니다.

이 문제를 해결하기 위해 별칭을 간단히 변경할 수 있으며 개발에는 문제가 없지만 페이지가로드 될 때마다 별칭을 폐기해야하거나 크기가 커지는 BundleCollection이 있어야하기 때문에 그 아이디어가 마음에 들지 않습니다. 모든 페이지로드. 프로덕션 환경에이 기능을 그대로두면 재앙이 될 것입니다.

따라서 스크립트가 제공 될 때 실제 BundleTables.Bundles객체와 독립적으로 캐시되는 것 같습니다 . 따라서 URL을 재사용하는 경우 재사용하기 전에 참조한 번들을 제거 했더라도 캐시에있는 모든 항목으로 응답하고 Bundles객체를 변경해도 캐시가 플러시되지 않으므로 항목 만 (또는 오히려 다른 이름의 새 항목)이 사용됩니다.

동작이 이상해 보입니다. 컬렉션에서 무언가를 제거하면 캐시에서 제거해야합니다. 하지만 그렇지 않습니다. 이 캐시를 플러시하고 BundleCollection해당 번들이 처음 액세스 될 때 캐시 된 내용 대신의 현재 내용을 사용하도록하는 방법이 있어야합니다 .

내가 어떻게 할 수 있을지 아십니까?

ResetAll목적을 알 수없는 이 방법이 있지만 어차피 부수기 때문에 그렇지 않습니다.


여기에도 같은 문제가 있습니다. 나는 내 것을 해결할 수 있다고 생각합니다. 그것이 당신을 위해 작동하는지보십시오. 전적으로 동의합니다. System.Web.Optimization에 대한 문서는 쓰레기이며 인터넷에서 찾을 수있는 모든 샘플은 구식입니다.
LeftyX

2
MS의 신뢰에 대한 기대에 대한 비판과 결합 된 상단의 훌륭한 참고 자료는 +1입니다. 그리고 제가 대답하고 싶은 질문을해서 도요.
Raif

답변:


33

문서화에 대한 귀하의 고통을 들었습니다. 안타깝게도이 기능은 여전히 ​​매우 빠르게 변경되고 있으며 문서 생성에는 약간의 지연이 있으며 거의 ​​즉시 구식이 될 수 있습니다. Rick의 블로그 게시물 이 최신 상태이며, 그 동안 현재 정보를 전파하기 위해 여기에서 질문에 답하려고 노력했습니다. 우리는 현재 항상 최신 문서가있는 공식 codeplex 사이트를 설정하는 중입니다.

이제 캐시에서 번들을 플러시하는 방법에 대한 특정 문제와 관련하여.

  1. 요청 된 번들 URL에서 생성 된 키를 사용하여 번들 된 응답을 ASP.NET 캐시 내부에 저장합니다. 즉, Context.Cache["System.Web.Optimization.Bundle:~/bundles/jquery"]이 번들을 생성하는 데 사용 된 모든 파일 및 디렉터리에 대한 캐시 종속성도 설정합니다. 따라서 기본 파일이나 디렉토리가 변경되면 캐시 항목이 플러시됩니다.

  2. 요청별로 BundleTable / BundleCollection의 라이브 업데이트를 실제로 지원하지 않습니다. 완전히 지원되는 시나리오는 앱 시작 중에 번들이 구성된다는 것입니다 (웹 팜 시나리오에서 모든 것이 제대로 작동하므로 일부 번들 요청이 잘못된 서버로 전송되면 404가됩니다). 코드 예제를 보면 특정 요청에 대해 번들 컬렉션을 동적으로 수정하려는 것 같습니다. 모든 종류의 번들 관리 / 재구성에는 모든 것이 올바르게 설정되었는지 확인하기 위해 appdomain 재설정이 수반되어야합니다.

따라서 앱 도메인을 재활용하지 않고 번들 정의를 수정하지 마십시오. 번들 내의 실제 파일을 자유롭게 수정할 수 있으며, 자동으로 감지되어 번들 URL에 대한 새 해시 코드를 생성해야합니다.


2
당신의 직접적인 지식을 여기에 가져와 주셔서 감사합니다! 예-번들 컬렉션을 동적으로 수정하려고합니다. 번들은 다른 스크립트에 설명 된 종속성 집합을 기반으로 빌드 됩니다 (즉, 번들의 일부가 아닐 수도 있음). 그래서이 문제가 발생합니다. 번들에있는 스크립트를 변경하면 강제로 플러시가 발생하므로 수행 할 수 있습니다. 수동 플러시 방법을 추가 할 가능성이 있습니까? 이것은 중요하지 않습니다. 이것은 개발 중 편의를위한 것입니다.하지만 실수로 prod에서 사용하면 문제를 일으킬 수있는 코드를 만드는 것이 싫습니다.
Jamie Treworgy 2012 년

또한 웹 팜 문제에 대해 자세히 설명 할 수 있습니까? 응용 프로그램 시작 후 번들을 추가하면 해당 번들이 생성 된 서버에서만 사용할 수 있습니까? 아니면 기존 번들을 변경하려고할까요? 이것은 종속성의 런타임 해결을 수행해야하기 때문에 내가하려는 작업에 대한 약간의 거래 킬러가 될 것입니다.
Jamie Treworgy 2012 년

물론 명시 적 캐시 플러시 등가 메서드를 추가 할 수 있습니다. 이미 내부에 있습니다. 웹 팜 문제와 관련하여 기본적으로 두 개의 웹 서버 A와 B가 있다고 가정하면 요청이 번들을 추가하고 응답을 보내는 A에게 가고 클라이언트는 이제 번들의 내용을 가져 오지만 요청은 다음으로 이동합니다. 서버 B 번들을 등록하고 404 거기하지 않은 사람
하오 쿵

1
캐시 업데이트는 지연되며, 번들이 처음 사용될 때 (일반적으로 번들에 대한 참조 렌더링을 통해) 캐시에 추가됩니다. 요청 처리를 시작하기 전에 모든 웹 서버에서 번들을 설정하는 동등한 앱 시작 후크가 있다면 괜찮습니다.
Hao Kung

2
내가 말할 수있는 한 이것은 작동하지 않습니다. 즉, 구성 파일을 변경하면 여기에 명시된대로 서버 캐시가 지워지지 않습니다. 변경 사항을 적용하려면 재활용해야합니다. 그 공식 문서가 실제로 어디에 있는지 아는 사람 있나요?
philw

21

비슷한 문제가 있습니다.
내 수업에서 BundleConfig나는 BundleTable.EnableOptimizations = true.

public class BundleConfig
{
    public static void RegisterBundles(BundleCollection bundles)
    {
        BundleTable.EnableOptimizations = true;

        bundles.Add(...);
    }
}

모든 것이 잘 작동했습니다.
어느 시점에서 디버깅을 수행하고 속성을 false로 설정했습니다.
나는 jquery (첫 번째) 번들이 해결되지 않고로드되지 않는 것처럼 보였기 때문에 무슨 일이 일어나고 있는지 이해하는 데 어려움을 겪었습니다 ( /bundles/jquery?v=).

욕을 좀하고 나서 (?!) 정리해 낸 것 같아요. 추가하려고 bundles.Clear()하고 bundles.ResetAll()등록과 사물의 시작 부분에 다시 작업을 시작해야합니다.

public class BundleConfig
{
    public static void RegisterBundles(BundleCollection bundles)
    {
        bundles.Clear();
        bundles.ResetAll();

        BundleTable.EnableOptimizations = false;

        bundles.Add(...);
    }
}

EnableOptimizations속성을 변경할 때만이 두 가지 방법을 실행해야한다는 것을 깨달았습니다 .

최신 정보:

파고 깊은 나는 것을 발견했습니다 BundleTable.Bundles.ResolveBundleUrl@Scripts.Url번들 경로를 해결하기 위해 문제를 갖고있는 것 같다.

간단하게하기 위해 몇 가지 이미지를 추가했습니다.

이미지 1

최적화를 해제하고 몇 가지 스크립트를 번들로 제공했습니다.

이미지 2

동일한 번들이 본체에 포함되어 있습니다.

이미지 3

@Scripts.Url@Scripts.Render적절한 경로를 생성하는 동안 번들의 "최적화 된"경로를 제공합니다 .
같은 일이 BundleTable.Bundles.ResolveBundleUrl.

Visual Studio 2010 + MVC 4 + Framework .Net 4.0을 사용하고 있습니다.


흠 ... 문제는 번들 테이블을 지우고 싶지 않다는 것입니다. 왜냐하면 다른 페이지 (다른 종속성 세트에서 생성 된)의 다른 많은 테이블을 포함하기 때문입니다. 그러나 이것은 실제로 개발 환경에서 작업하기위한 것이기 때문에 캐시를 플러시 할 경우 내용을 복사 한 다음 지우고 다시 추가 할 수 있다고 생각합니다. 끔찍하게 비효율적이지만 작동한다면 dev에 충분히 좋습니다.
Jamie Treworgy 2012 년

동의하지만 그게 내가 가진 유일한 옵션입니다. 나는 오후 내내 문제가 무엇인지 이해하려고 노력했습니다.
LeftyX 2012 년

2
방금 시도했지만 여전히 캐시를 플러시하지 않습니다! 나는 그것을 지우고 캐시를 재설정해야 할 때 시작과 인라인 모두에서 false로 ResetAll설정하려고 시도했지만 EnableOptimizations아무 일도 일어나지 않았습니다. 아아.
Jamie Treworgy 2012 년

개발자가 : 이러한 개체의 방법에 대한 심지어 한 줄에 빠른 블로그 게시물 해고 할 수 있다면 그것은 확실히 좋은 일 것입니다
제이미 Treworgy

6
따라서 이러한 메서드가 수행하는 작업을 설명하기 위해 Scripts.Url은 BundleTable.Bundles.ResolveBundleUrl의 별칭 일 뿐이며 번들이 아닌 URL도 확인하므로 번들에 대해 알고있는 일반적인 URL 확인자입니다. Scripts.Render는 EnableOptimizations 플래그를 사용하여 번들 또는 번들을 구성하는 구성 요소에 대한 참조를 렌더링할지 여부를 결정합니다.
Hao Kung

8

웹 팜 시나리오로 인해이 작업을 수행하지 말라는 Hao Kung의 권장 사항을 염두에두고이를 수행하고 싶은 시나리오가 많이 있다고 생각합니다. 해결책은 다음과 같습니다.

BundleTable.Bundles.ResetAll(); //or something more specific if neccesary
var bundle = new Bundle("~/bundles/your-bundle-virtual-path");
//add your includes here or load them in from a config file

//this is where the magic happens
var context = new BundleContext(new HttpContextWrapper(HttpContext.Current), BundleTable.Bundles, bundle.Path);
bundle.UpdateCache(context, bundle.GenerateBundleResponse(context));

BundleTable.Bundles.Add(bundle);

언제든지 위 코드를 호출 할 수 있으며 번들이 업데이트됩니다. 이는 EnableOptimizations가 true 또는 false 일 때 모두 작동합니다. 즉, 디버그 또는 라이브 시나리오에서 올바른 마크 업이 다음과 같이 표시됩니다.

@Scripts.Render("~/bundles/your-bundle-virtual-path")

여기 에서 캐싱에 대해 조금 이야기하는 추가 읽기GenerateBundleResponse
Zac

4

또한 다시 빌드하지 않고 번들을 업데이트하는 데 문제가 발생했습니다. 이해해야 할 중요한 사항은 다음과 같습니다.

  • 파일 경로가 변경되면 번들이 업데이트되지 않습니다.
  • 번들의 가상 경로가 변경되면 번들이 업데이트됩니다.
  • 디스크의 파일이 변경되면 번들이 업데이트됩니다.

따라서 동적 번들링을 수행하는 경우 번들의 가상 경로가 파일 경로를 기반으로하도록 코드를 작성할 수 있습니다. 파일 경로를 해시하고 해당 해시를 번들의 가상 경로 끝에 추가하는 것이 좋습니다. 이렇게하면 파일 경로가 변경 될 때 가상 경로도 변경되고 번들이 업데이트됩니다.

이 문제를 해결 한 코드는 다음과 같습니다.

    public static IHtmlString RenderStyleBundle(string bundlePath, string[] filePaths)
    {
        // Add a hash of the files onto the path to ensure that the filepaths have not changed.
        bundlePath = string.Format("{0}{1}", bundlePath, GetBundleHashForFiles(filePaths));

        var bundleIsRegistered = BundleTable
            .Bundles
            .GetRegisteredBundles()
            .Where(bundle => bundle.Path == bundlePath)
            .Any();

        if(!bundleIsRegistered)
        {
            var bundle = new StyleBundle(bundlePath);
            bundle.Include(filePaths);
            BundleTable.Bundles.Add(bundle);
        }

        return Styles.Render(bundlePath);
    }

    static string GetBundleHashForFiles(IEnumerable<string> filePaths)
    {
        // Create a unique hash for this set of files
        var aggregatedPaths = filePaths.Aggregate((pathString, next) => pathString + next);
        var Md5 = MD5.Create();
        var encodedPaths = Encoding.UTF8.GetBytes(aggregatedPaths);
        var hash = Md5.ComputeHash(encodedPaths);
        var bundlePath = hash.Aggregate(string.Empty, (hashString, next) => string.Format("{0}{1:x2}", hashString, next));
        return bundlePath;
    }

Aggregate누군가 반복적으로 사용 하는 Painter의 고유 한 Schlemiel 알고리즘 에 대해 생각하지 않을 위험이 있기 때문에 일반적으로 문자열 연결을 피하는 것이 좋습니다 +. 대신 string.Join("", filePaths). 이것은 매우 큰 입력의 경우에도 그 문제가 없습니다.
ErikE

3

( StyleBundle 또는 ScriptBundle ) 에서 파생 하여 생성자에 포함을 추가하지 않은 다음 재정의 해 보셨습니까?

public override IEnumerable<System.IO.FileInfo> EnumerateFiles(BundleContext context)

동적 스타일 시트에 대해이 작업을 수행하고 EnumerateFiles는 모든 요청에 ​​대해 호출됩니다. 아마도 가장 좋은 해결책은 아니지만 작동합니다.


0

죽은 스레드를 되살리는 것에 대해 사과했지만 Umbraco 사이트에서 번들 캐싱과 비슷한 문제가 발생했습니다. 사용자가 백엔드에서 예쁜 버전을 변경하면 스타일 시트 / 스크립트가 자동으로 축소되도록했습니다.

내가 이미 가지고있는 코드는 (스타일 시트의 onSaved 메서드에 있음) :

 BundleTable.Bundles.Add(new StyleBundle("~/bundles/styles.min.css").Include(
                           "~/css/main.css"
                        ));

및 (onApplicationStarted) :

BundleTable.EnableOptimizations = true;

내가 무엇을 시도해도 "~ / bundles / styles.min.css"파일이 변경되지 않은 것 같습니다. 내 페이지의 머리 부분에서 나는 원래 다음과 같이 스타일 시트를로드하고있었습니다.

<link rel="stylesheet" href="~/bundles/styles.min.css" />

그러나 이것을 다음과 같이 변경하여 작동하게했습니다.

@Styles.Render("~/bundles/styles.min.css")

Styles.Render 메서드는 위에서 Hao가 설명한 캐시 키라고 생각하는 파일 이름 끝에 쿼리 ​​문자열을 가져옵니다.

저에게는 그렇게 간단했습니다. 이것이 몇 시간 동안 인터넷 검색을하고 몇 년 된 게시물을 찾을 수 있었던 나와 같은 다른 사람에게 도움이되기를 바랍니다!

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.