Як змусити BundleCollection очистити кешовані набори сценаріїв у MVC4


85

... або як я навчився перестати хвилюватися і просто писати код на повністю недокументовані API від Microsoft . Чи існує якась документація офіційного System.Web.Optimizationвипуску? Тому що я впевнений, що не можу знайти жодного, немає XML-документів, і всі публікації в блозі посилаються на RC API, який істотно відрізняється. Anyhoo ..

Я пишу код для автоматичного вирішення залежностей javascript і на ходу створюю набори з цих залежностей. Все працює чудово, за винятком випадку, якщо ви редагуєте сценарії або іншим чином вносите зміни, які впливають на пакет без перезапуску програми, зміни не відображатимуться. Тому я додав опцію вимкнення кешування залежностей для використання в розробці.

Однак, мабуть, BundleTablesкешує URL-адресу, навіть якщо колекція наборів змінилася . Наприклад, у власному коді, коли я хочу повторно створити пакет, я роблю щось подібне:

// 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
+1 для чудової довідки вгорі в поєднанні з жалюгідними коментарями про очікування довіри від MS А також за те, що я задав питання, на яке я хочу отримати відповідь.
Райф,

Відповіді:


33

Ми чуємо ваш біль щодо документації, на жаль, ця функція все ще змінюється досить швидко, і створення документації має певні затримки і може бути застарілим майже відразу. Повідомлення блогу Ріка оновлено, і я намагався відповісти на запитання і тут, щоб тим часом поширити поточну інформацію. Наразі ми перебуваємо в процесі створення нашого офіційного сайту codeplex, який матиме завжди актуальну документацію.

Щодо вашого конкретного питання про те, як змити пакети з кешу.

  1. Ми зберігаємо пакетну відповідь усередині кешу ASP.NET, використовуючи ключ, згенерований із вказаної URL-адреси пакета, тобто Context.Cache["System.Web.Optimization.Bundle:~/bundles/jquery"]ми також встановлюємо залежності кешу щодо всіх файлів та каталогів, які використовувались для створення цього пакета. Отже, якщо будь-який з базових файлів або каталогів зміниться, запис кешу буде очищений.

  2. Ми насправді не підтримуємо оновлення BundleTable / BundleCollection у режимі реального часу на основі кожного запиту. Повністю підтримуваний сценарій полягає в тому, що набори налаштовуються під час запуску програми (це означає, що все працює належним чином у сценарії веб-ферми, інакше деякі запити набору в кінцевому підсумку становлять 404, якщо їх відправити на неправильний сервер). Дивлячись на приклад коду, я здогадуюсь, що ви намагаєтесь динамічно модифікувати колекцію наборів за певним запитом? Будь-який тип адміністрування / реконфігурації набору повинен супроводжуватися скиданням домену програми, щоб гарантувати, що все було налаштовано правильно.

Тому уникайте модифікацій визначень наборів без переробки домену програми. Ви можете змінювати фактичні файли всередині ваших пакетів, які слід автоматично виявляти та генерувати нові хеш-коди для ваших URL-адрес пакетів.


2
дякуємо, що принесли сюди свої прямі знання! Так - я намагаюся динамічно модифікувати колекцію наборів. Пакети будуються на основі набору залежностей, описаних в іншому сценарії (тобто, сам по собі, не обов'язково частина пакета) - саме тому у мене ця проблема. Оскільки зміна сценарію, що знаходиться в наборі, призведе до змиву, це можна зробити - чи є можливість додати метод ручного змиву? Це не має вирішального значення - це для зручності під час розробки, але я ненавиджу створювати код, який може спричинити проблеми при випадковому використанні на prod.
Jamie Treworgy,

Також ви можете детальніше розповісти про проблему веб-ферми? Чи додасть новий пакет після запуску програми, що призведе до того, що він буде доступний лише на сервері, на якому він був створений, - або просто намагається змінити існуючий? Це було б дещо розгалужувачем для того, що я намагаюся зробити, оскільки йому потрібно виконати вирішення залежностей під час виконання.
Jamie Treworgy,

Звичайно, ми могли б додати явний еквівалентний метод кеш-пам’яті, він уже є там внутрішньо. Що стосується проблеми веб-ферми, уявіть, що у вас є два веб-сервери A і B, ваш запит надходить до A, який додає пакет, і надсилає відповідь, ваш клієнт тепер йде за вмістом пакета, але запит переходить до сервер B, який не зареєстрував пакет, і ось ваш 404.
Хао Кунг

1
Оновлення кешу є ледачим, при першому використанні набору (як правило, шляхом надання посилання на набір) воно додається до кешу. Якщо у вас є еквівалентний хук запуску програми, де ви налаштовуєте свої пакети на всіх веб-серверах перед початком обробки запитів, це має бути добре.
Хао Кунг

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.


Хм ... річ у тому, що я насправді не хочу очищати таблицю пакетів, оскільки вона буде містити багато інших з різних сторінок (створених з різних наборів залежностей). Але оскільки це насправді лише для роботи в середовищі розробки, я думаю, що я міг би скопіювати його вміст, потім очистити його, а потім додати ще раз, якщо це очистить кеш. Жахливо неефективний, але якщо він працює, це досить добре для розробників.
Jamie Treworgy,

Погодьтеся, але це єдиний варіант, який я мав. Я провів увесь день, намагаючись зрозуміти, в чому проблема.
LeftyX

2
Я щойно спробував, ВСЕ ЩЕ не очищаючи кеш !! Я очищаю це, ResetAllі намагався встановити EnableOptimizationsзначення false як під час запуску, так і вбудовано, коли мені потрібно скинути кеш, нічого не відбувається. Арг.
Jamie Treworgy,

Звичайно, було б непогано, якби розробник міг звільнити короткий допис у блозі навіть із одностороннім
вкладом

6
Отже, щоб пояснити, що роблять ці методи: Scripts.Url - це просто псевдонім для BundleTable.Bundles.ResolveBundleUrl, він також буде вирішувати URL-адреси, що не є пакетами, тому це загальний засіб розв’язання URL-адрес, який, як трапляється, знає про пакети. Scripts.Render використовує прапор EnableOptimizations, щоб визначити, чи надавати посилання на пакети або компоненти, що складають пакет.
Хао Кунг

8

Беручи до уваги рекомендації Хао Кунга не робити цього через сценарії веб-ферм, я думаю, що існує багато сценаріїв, коли ви можете зробити це. Ось рішення:

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
Зак

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конкатенації рядків через ризик того, що хтось не замислюється про невід'ємний алгоритм Шлеміеля Художника при багаторазовому використанні +. Натомість просто зробіть string.Join("", filePaths). Це не матиме цієї проблеми, навіть для дуже великих входів.
ErikE

3

Ви пробували походити з ( StyleBundle або ScriptBundle ), не додаючи ніяких включень у ваш конструктор, а потім замінюючи

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

Я роблю це для динамічних таблиць стилів, і EnumerateFiles викликається при кожному запиті. Це, мабуть, не найкраще рішення, але воно працює.


0

Вибачаюся за пожвавлення мертвої нитки, однак я зіткнувся з подібною проблемою з кешуванням Bundle на сайті 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 вище.

Для мене це було так просто. Сподіваюся, це допомагає будь-кому іншому, як я, хто гуглив це годинами і міг знайти лише кількарічні повідомлення!

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.