Jak zmusić BundleCollection do opróżnienia buforowanych pakietów skryptów w MVC4


85

... lub jak nauczyłem się przestać się martwić i po prostu pisać kod w całkowicie nieudokumentowanych interfejsach API firmy Microsoft . Czy istnieje rzeczywista dokumentacja oficjalnego System.Web.Optimizationwydania? Ponieważ na pewno nie mogę znaleźć żadnego, nie ma dokumentów XML, a wszystkie posty na blogu odnoszą się do RC API, które jest zasadniczo inne. Anyhoo ..

Piszę kod, aby automatycznie rozwiązywać zależności javascript i tworzę pakiety w locie z tych zależności. Wszystko działa świetnie, z wyjątkiem sytuacji, gdy edytujesz skrypty lub w inny sposób wprowadzasz zmiany, które wpłynęłyby na pakiet bez ponownego uruchamiania aplikacji, zmiany nie zostaną odzwierciedlone. Dodałem więc opcję wyłączenia buforowania zależności do wykorzystania w rozwoju.

Jednak najwyraźniej BundleTablesbuforuje adres URL, nawet jeśli kolekcja paczek uległa zmianie . Na przykład w moim własnym kodzie, gdy chcę ponownie utworzyć pakiet, robię coś takiego:

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

Za każdym razem, gdy usuwam i ponownie tworzę pakiet z tym samym aliasem , absolutnie nic się nie dzieje: bundleUrlzwracany plik ResolveBundleUrljest taki sam, jak przed usunięciem i odtworzeniem pakietu. Przez „ten sam” rozumiem, że skrót zawartości pozostaje niezmieniony, aby odzwierciedlić nową zawartość pakietu.

edytuj ... właściwie, jest znacznie gorzej. Sam pakiet jest w jakiś sposób buforowany poza Bundleskolekcją. Jeśli po prostu wygeneruję własny losowy skrót, aby zapobiec buforowaniu skryptu przez przeglądarkę, ASP.NET zwraca stary skrypt . Najwyraźniej usunięcie pakietu z BundleTable.Bundlesfaktycznie nic nie robi.

Mogę po prostu zmienić alias, aby obejść ten problem, i jest to w porządku w przypadku programowania, ale nie podoba mi się ten pomysł, ponieważ oznacza to, że muszę wycofywać aliasy po każdym załadowaniu strony lub mieć kolekcję BundleCollection, która rośnie w każde ładowanie strony. Jeśli zostawisz to włączone w środowisku produkcyjnym, byłaby to katastrofa.

Wygląda więc na to, że kiedy skrypt jest obsługiwany, jest buforowany niezależnie od rzeczywistego BundleTables.Bundlesobiektu. Więc jeśli ponownie użyjesz adresu URL, nawet jeśli usunąłeś pakiet, do którego się odnosił przed ponownym użyciem, odpowiada on tym, co jest w jego pamięci podręcznej, a zmiana Bundlesobiektu nie powoduje opróżnienia pamięci podręcznej - więc tylko nowe elementy (lub raczej nowe przedmioty o innej nazwie) byłyby kiedykolwiek używane.

Zachowanie wydaje się dziwne ... usunięcie czegoś z kolekcji powinno usunąć to z pamięci podręcznej. Ale tak nie jest. Musi istnieć sposób, aby opróżnić tę pamięć podręczną i użyć bieżącej zawartości plikuBundleCollection zamiast tego, co buforowała, gdy po raz pierwszy uzyskano dostęp do tego pakietu.

Masz jakiś pomysł, jak bym to zrobił?

Jest taka ResetAllmetoda, która ma nieznane przeznaczenie, ale i tak wszystko psuje, więc nie o to chodzi.


Mam ten sam problem. Myślę, że udało mi się rozwiązać swoje. Spróbuj i zobacz, czy to działa. Kompletnie się zgadzam. Dokumentacja System.Web.Optimization to śmieci, a wszystkie próbki, które można znaleźć w Internecie, są nieaktualne.
LeftyX,

2
+1 za świetne referencje u góry w połączeniu z kąśliwym komentarzem na temat oczekiwań zaufania MS. A także za zadanie pytania, na które chcę odpowiedzi.
Raif

Odpowiedzi:


33

Słyszymy, że cierpisz z powodu dokumentacji, niestety ta funkcja wciąż się zmienia dość szybko, a generowanie dokumentacji ma pewne opóźnienie i może być nieaktualne prawie natychmiast. Wpis na blogu Ricka jest aktualny. W międzyczasie próbowałem odpowiedzieć na pytania, aby rozpowszechniać aktualne informacje. Obecnie jesteśmy w trakcie tworzenia naszej oficjalnej witryny codeplex, która będzie zawsze mieć aktualną dokumentację.

Teraz w odniesieniu do twojego konkretnego problemu, jak opróżniać pakiety z pamięci podręcznej.

  1. Przechowujemy odpowiedź w pakiecie w pamięci podręcznej ASP.NET przy użyciu klucza wygenerowanego z żądanego adresu URL pakietu, tj. Context.Cache["System.Web.Optimization.Bundle:~/bundles/jquery"]Konfigurujemy również zależności pamięci podręcznej dla wszystkich plików i katalogów, które zostały użyte do wygenerowania tego pakietu. Więc jeśli którykolwiek z podstawowych plików lub katalogów ulegnie zmianie, wpis pamięci podręcznej zostanie opróżniony.

  2. Naprawdę nie obsługujemy aktualizacji na żywo BundleTable / BundleCollection na podstawie żądania. W pełni obsługiwany scenariusz polega na tym, że pakiety są konfigurowane podczas uruchamiania aplikacji (tak więc wszystko działa poprawnie w scenariuszu farmy internetowej, w przeciwnym razie niektóre żądania pakietów zakończyłyby się 404, jeśli zostałyby wysłane na niewłaściwy serwer). Patrząc na Twój przykład kodu, domyślam się, że próbujesz dynamicznie modyfikować kolekcję pakietów na określone żądanie? Każdemu rodzajowi administracji / rekonfiguracji pakietu powinien towarzyszyć reset domeny aplikacji, aby zagwarantować, że wszystko zostało poprawnie skonfigurowane.

Dlatego unikaj modyfikowania definicji pakietów bez recyklingu domeny aplikacji. Możesz modyfikować rzeczywiste pliki w swoich paczkach, które powinny być automatycznie wykrywane i generować nowe hashcody dla adresów URL pakietów.


2
dziękujemy za przekazanie tutaj swojej bezpośredniej wiedzy! Tak - próbuję dynamicznie modyfikować kolekcję paczek. Pakiety są budowane w oparciu o zestaw zależności opisanych w innym skrypcie (czyli sam niekoniecznie część pakietu) - dlatego mam ten problem. Skoro zmiana skryptu znajdującego się w paczce wymusi rzut, można to zrobić - czy istnieje możliwość dodania metody ręcznej? To nie jest kluczowe - to dla wygody podczas programowania - ale nienawidzę tworzenia kodu, który mógłby powodować problemy, jeśli zostałby przypadkowo użyty na prod.
Jamie Treworgy

Czy możesz też rozwinąć kwestię farmy internetowej? Czy dodanie nowego pakietu po uruchomieniu aplikacji spowodowałoby, że byłby on dostępny tylko na serwerze, na którym został utworzony - czy po prostu próba zmiany istniejącego? Byłoby to trochę dealkiller dla tego, co próbuję zrobić, ponieważ musi wykonywać rozwiązywanie zależności w czasie wykonywania.
Jamie Treworgy

Jasne, moglibyśmy dodać jawną równoważną metodę opróżniania pamięci podręcznej, jest już tam wewnętrznie. Jeśli chodzi o problem z farmą sieci Web, wyobraź sobie, że masz dwa serwery internetowe A i B, twoje żądanie trafia do A, który dodaje pakiet i wysyła odpowiedź, twój klient teraz pobiera zawartość pakietu, ale ojej żądanie trafia do serwer B, który nie zarejestrował pakietu, a jest Twój 404.
Hao Kung,

1
Aktualizacja pamięci podręcznej jest leniwa, gdy pakiet jest używany po raz pierwszy (zwykle poprzez renderowanie odwołania do pakietu), jest dodawany do pamięci podręcznej. Jeśli masz równoważny hak startowy aplikacji, w którym konfigurujesz swoje pakiety na wszystkich serwerach internetowych przed rozpoczęciem obsługi żądań, powinno to wystarczyć.
Hao Kung,

2
O ile wiem, to nie działa. Oznacza to, że jeśli zmienię plik (i) składowe, pamięć podręczna serwera nie zostanie wyczyszczona, jak podano tutaj. Musisz poddać je recyklingowi, aby wprowadzić zmiany. Czy ktoś wie, gdzie właściwie jest ta oficjalna dokumentacja?
philw

21

Mam podobny problem.
Na zajęciach BundleConfigstarałem się sprawdzić, jaki jest efekt używania BundleTable.EnableOptimizations = true.

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

        bundles.Add(...);
    }
}

Wszystko działało dobrze.
W pewnym momencie robiłem debugowanie i ustawiłem właściwość na false.
Starałem się zrozumieć, co się dzieje, ponieważ wydawało się, że pakiet jquery (pierwszy) nie zostanie rozwiązany i załadowany ( /bundles/jquery?v=).

Po kilku przekleństwach myślę, że (?!) udało mi się wszystko uporządkować. Spróbuj dodać bundles.Clear()i bundles.ResetAll()na początku rejestracji i wszystko powinno znowu zacząć działać.

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

        BundleTable.EnableOptimizations = false;

        bundles.Add(...);
    }
}

Zdałem sobie sprawę, że muszę uruchomić te dwie metody tylko wtedy, gdy zmienię EnableOptimizationswłaściwość.

AKTUALIZACJA:

Kopiąc głębiej, odkryłem to BundleTable.Bundles.ResolveBundleUrli @Scripts.Urlwydaje mi się, że mam problemy z rozwiązaniem ścieżki pakietu.

Dla uproszczenia dodałem kilka zdjęć:

zdjęcie 1

Wyłączyłem optymalizację i dołączyłem kilka skryptów.

zdjęcie 2

Ten sam pakiet znajduje się w korpusie.

zdjęcie 3

@Scripts.Urldaje mi „zoptymalizowaną” ścieżkę pakietu, podczas gdy @Scripts.Rendergeneruje właściwą.
To samo dzieje się z BundleTable.Bundles.ResolveBundleUrl.

Używam Visual Studio 2010 + MVC 4 + Framework .Net 4.0.


Hmm ... chodzi o to, że tak naprawdę nie chcę czyścić tabeli pakietów, ponieważ będzie ona zawierała wiele innych z różnych stron (utworzonych z różnych zestawów zależności). Ale ponieważ tak naprawdę jest to tylko do pracy w środowisku programistycznym, myślę, że mógłbym skopiować jego zawartość, a następnie wyczyścić, a następnie dodać je ponownie, jeśli to spowodowałoby opróżnienie pamięci podręcznej. Przerażająco nieefektywny, ale jeśli działa, jest wystarczająco dobry dla programistów.
Jamie Treworgy

Zgadzam się, ale to jedyna opcja, jaką miałem. Spędziłem całe popołudnie próbując zrozumieć, w czym tkwi problem.
LeftyX,

2
Właśnie spróbowałem, NADAL nie opróżniam pamięci podręcznej !! Wyczyszczam to ResetAlli próbowałem ustawić wartość EnableOptimizationsfalse zarówno podczas uruchamiania, jak i w linii, gdy muszę zresetować pamięć podręczną, nic się nie dzieje. Argh.
Jamie Treworgy

Na pewno byłoby miło, gdyby deweloper mógł odpalić szybki wpis na blogu z choćby jedną
linijką

6
Aby wyjaśnić, co robią te metody: Scripts.Url to tylko alias dla BundleTable.Bundles.ResolveBundleUrl, będzie również rozpoznawać adresy URL niezwiązane z pakietami, więc jest to ogólny program do rozpoznawania adresów URL, który wie o pakietach. Scripts.Render używa flagi EnableOptimizations, aby określić, czy renderować odwołanie do pakunków, czy do składników, które tworzą pakunek.
Hao Kung,

8

Mając na uwadze zalecenia Hao Kunga, aby tego nie robić ze względu na scenariusze farm internetowych, myślę, że istnieje wiele scenariuszy, w których możesz to zrobić. Oto rozwiązanie:

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

Możesz zadzwonić na powyższy kod w dowolnym momencie, a Twoje pakiety zostaną zaktualizowane. Działa to zarówno wtedy, gdy EnableOptimizations ma wartość true lub false - innymi słowy, spowoduje to wyrzucenie prawidłowego znacznika w scenariuszach debugowania lub na żywo, z:

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

Dalsze czytanie tutaj, które mówi trochę o buforowaniu iGenerateBundleResponse
Zac

4

Napotkałem również problemy z aktualizacją pakietów bez przebudowy. Oto ważne rzeczy do zrozumienia:

  • Pakiet NIE jest aktualizowany, jeśli zmieniają się ścieżki plików.
  • Pakiet zostanie zaktualizowany, jeśli zmieni się wirtualna ścieżka pakietu.
  • Pakiet zostanie zaktualizowany, jeśli pliki na dysku ulegną zmianie.

Wiedząc o tym, jeśli robisz dynamiczne tworzenie pakietów, możesz napisać kod, aby wirtualna ścieżka pakietu była oparta na ścieżkach plików. Zalecam haszowanie ścieżek plików i dołączanie tego skrótu na końcu ścieżki wirtualnej pakietu. W ten sposób, gdy zmieniają się ścieżki plików, zmienia się również ścieżka wirtualna i pakiet zostanie zaktualizowany.

Oto kod, który otrzymałem, a który rozwiązał problem:

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

Generalnie zalecam unikanie Aggregatekonkatenacji ciągów ze względu na ryzyko, że ktoś nie pomyśli o nieodłącznym algorytmie Schlemiel the Painter w wielokrotnym użyciu +. Zamiast tego po prostu zrób string.Join("", filePaths). To nie będzie miało tego problemu, nawet w przypadku bardzo dużych nakładów.
ErikE

3

Czy próbowałeś wyprowadzić z ( StyleBundle lub ScriptBundle ), nie dodając żadnych inkluzji w konstruktorze, a następnie przesłaniając

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

Robię to dla dynamicznych arkuszy stylów, a EnumerateFiles jest wywoływana na każde żądanie. To chyba nie najlepsze rozwiązanie, ale działa.


0

Przepraszamy za wskrzeszenie martwego wątku, jednak napotkałem podobny problem z buforowaniem pakietów na stronie Umbraco, gdzie chciałem, aby arkusze stylów / skrypty były automatycznie zmniejszane, gdy użytkownik zmienił ładną wersję w zapleczu.

Kod, który już miałem (w metodzie onSaved dla arkusza stylów):

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

i (onApplicationStarted):

BundleTable.EnableOptimizations = true;

Bez względu na to, co próbowałem, plik „~ / bundles / styles.min.css” nie zmienił się. W nagłówku mojej strony pierwotnie ładowałem arkusz stylów w następujący sposób:

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

Jednak udało mi się to zadziałać, zmieniając to na:

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

Metoda Styles.Render pobiera ciąg zapytania na końcu nazwy pliku, który, jak sądzę, jest kluczem pamięci podręcznej opisanym przez Hao powyżej.

Dla mnie to było takie proste. Mam nadzieję, że pomoże to każdemu takiemu jak ja, który szukał tego w Google przez wiele godzin i mógł znaleźć tylko posty sprzed kilku lat!

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.